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
Dictionnaire de philosophie/Sujet
0
221
768699
753869
2026-06-26T04:33:51Z
PandaMystique
119061
Bot : ajout du paramètre lecture=oui au modèle {{DicoPhilo}}
768699
wikitext
text/x-wiki
{{DicoPhilo|Sujet|lecture=oui}}
== Quelques distinctions ==
Le mot sujet s'emploie en différents domaines :
* en grammaire : ''voir {{w|sujet (grammaire)|article de Wikipédia}}'' ;
* en logique, le sujet désigne ce à quoi l'on attribue un prédicat dans une proposition logique de type sujet-prédicat ;
* en métaphysique, le sujet désigne l'être réel, porteur de qualités et auteur d'actes.
Le sujet se définit à la fois comme ce qui constitue l'objet de la pensée et de la connaissance (on parle d'un « sujet de dissertation ») et comme le support de certaines réalités fondamentales (actes, conscience, [[Philosophie/Perception|perception]], droits, etc.).
Cette seconde acception constitue l'objet de notre analyse, bien que toutes ces significations entretiennent des liens si étroits qu'il devient possible de formuler une critique du sujet en l'assimilant à un être purement logique, voire à une fiction logique dérivée d'une habitude grammaticale trompeuse. Par exemple, le fait de dire « je » dans une phrase ne constituerait nullement une preuve que nous sommes un être auquel on attribue une qualité. Cet examen critique de la notion de sujet trouvera naturellement sa place plus loin dans cet article.
Pour commencer, considérons l'idée de sujet en tant qu'elle renvoie à une réalité dont on peut dire :
* qu'on lui attribue certaines qualités (corporelles, psychiques, morales, juridiques, etc.). Le sujet désigne une personne dont on parle et que l'on décrit. Cette personne existe dans le temps et l'espace comme un objet, alors que certaines de ses qualités demeurent absolument invisibles, inattestables. C'est par exemple {{w|autrui}}, dont on peut se demander qui il est proprement et pour moi. On attribue également au sujet des qualités morales et des défauts, ainsi que des droits : le sujet peut être porteur de droits (droit de vivre, de penser, de se déplacer, etc.). On lui attribue des devoirs : le sujet peut être soumis à une hiérarchie politique (par exemple, le sujet doit obéissance à son souverain) ou à une loi morale, c'est-à-dire à un devoir-être (ce qui implique la responsabilité et la [[Philosophie/Liberté|liberté]] du sujet). Dans ce cas, le fait d'être ne suffit pas à constituer un sujet : il doit être, pour être ;
* qu'elle a la faculté de parler ''en première personne'', c'est-à-dire de se désigner elle-même comme référence de son discours. La personne qui se considère en tant que sujet rapporte à elle-même certains actes, des pensées, des perceptions, des sentiments, des [[Philosophie/Désir|désirs]], etc., ce qui introduit l'idée du « je », du « mien », de ce qui m'est propre. De la question de ce qui est propre à un sujet, il est aisé de passer ensuite à une question constitutive du sujet : ''par quoi ou pour qui le propre est-il dit propre ?''
En résumé, le sujet constitue une réalité à la fois métaphysique, existentielle, morale et politique. Mais son sens fondamental demeure métaphysique. À ce titre, le sujet forme la notion fondatrice de l'{{w|humanisme}}, de la {{w|modernité}} et de l'ensemble des valeurs occidentales. Nous verrons pourquoi, sans sujet, il n'y aurait — entre autres exemples — ni science, ni valeur morale, ni démocratie.
Les thèses philosophiques qui nient la validité de la notion de sujet sont bien souvent qualifiées d'« anti-humanisme ». Nous étudierons plus loin cette négation et ses conséquences.
== Le sujet logique ==
En logique classique, le sujet désigne ce à quoi l'on attribue ou l'on nie un prédicat. Dans la proposition « Socrate est un homme », « Socrate » constitue le sujet et « être un homme » le prédicat. Cette structure logique, formalisée par {{w|Aristote}} dans sa théorie du {{w|syllogisme}}, établit la base de tout raisonnement déductif.
La logique des prédicats distingue quatre types de propositions selon deux critères : la qualité (affirmative ou négative) et la quantité (universelle ou particulière). Ainsi « Tous les hommes sont mortels » constitue une proposition universelle affirmative, tandis que « Certains hommes ne sont pas sages » forme une proposition particulière négative.
Cette conception logique du sujet pose la question suivante : le sujet logique correspond-il au sujet métaphysique ? Le fait qu'une proposition attribue un prédicat à un sujet grammatical suffit-il à prouver l'existence d'un sujet réel, d'une substance pensante ? Certains philosophes, dont {{w|David Hume}}, ont contesté ce passage du logique au métaphysique, suggérant que l'habitude grammaticale de placer un sujet avant un verbe nous conduit à supposer à tort l'existence d'une entité permanente derrière nos actes et nos pensées.
Cette critique trouve son prolongement dans les analyses contemporaines qui dénoncent le « sujet » comme une fiction grammaticale élevée abusivement au rang de réalité ontologique. Néanmoins, la tradition philosophique occidentale, de {{w|Descartes}} à {{w|Emmanuel Kant|Kant}}, a maintenu la distinction entre le sujet logique (le « je » grammatical) et le sujet transcendantal (condition de possibilité de toute pensée).
== La notion et ses problèmes ==
=== Une conception générale ===
L'une des conceptions les plus influentes de la philosophie occidentale moderne consiste à identifier le sujet à l'identité de la [[Philosophie/Conscience|conscience]] à travers le [[Philosophie/Existence et temps|temps]] et dans la saisie immédiate de soi par soi en tant qu'étant. Cette définition soulève deux thèses problématiques :
* ''l'identité du sujet'' : peut-on penser un sujet qui ne soit pas identique à soi ? La non-identité ne constitue-t-elle pas la négation même de l'idée de sujet ? Le sujet peut-il exister dans le temps ?
* ''la conscience de soi'' : cette conscience signifie-t-elle que nous nous connaissons seulement en tant que sujet ? L'idée de sujet ne constitue-t-elle pas l'objet d'une simple croyance ?
Cette conscience opère la synthèse entre le sujet comme propre (ou moi-même en personne, le fait que j'existe) et le sujet de la connaissance (sujet qui connaît, qui se représente ''ce que je suis''). Le sujet, chez {{w|René Descartes|Descartes}} par exemple, désigne ce '''pour quoi ou pour qui il y a une représentation''', et donc également ce pour quoi ou pour qui il y a connaissance, y compris connaissance de soi-même. C'est là la racine de l'{{w|idéalisme}} moderne : le sujet est pensant, connaissant et, se sachant connaissant (identité du « je »), il existe dans la certitude de ce savoir. Le sujet se confond alors avec la {{w|raison}} et avec le « je ». Dans une autre perspective, l'on dirait que le sujet correspond à l'universel, ou plus exactement, chez Descartes, qu'il tend à l'universel.
De cette conception, on peut dégager au moins deux grands courants de pensée :
* Le « je » constitue l'élément fondateur, le fondement ultime, indépassable : il y a alors absorption de l'absolu dans le « je ». Le particulier, l'individuel se trouve valorisé.
* L'absolu seul constitue le sujet, et, en particulier, le sujet de l'histoire : il y a alors absorption de l'individuel dans l'absolu. Cette conception s'oriente vers le {{w|panthéisme}} : l'individu n'est pas proprement le sujet de la pensée qu'il perçoit comme sienne, mais ce sujet correspond plutôt à l'Idée, à {{w|Dieu}} ou à la nature. Cette dernière conception montre que l'on ne doit pas associer par habitude le sujet et l'individu, car il est possible de nier à ce dernier cette qualité de sujet : je ne suis pas un sujet, mais une certaine variation accidentelle de ce qui existe. À l'inverse, si l'on conçoit l'individu comme sujet, cet individu doit alors présenter les caractéristiques d'une réalité consistante et peut-être même nécessaire.
=== Difficultés de cette conception ===
La réunion dans le sujet de la conscience et de la pensée a été dénoncée par plusieurs philosophes, dont {{w|Gottfried Wilhelm Leibniz|Leibniz}} par exemple. Un problème parmi d'autres que soulève cette conception tient au fait que la représentation ne se confond pas avec le sujet, que la conscience ne s'identifie pas à la pensée ; la représentation constitue la représentation ''du'' sujet, et le cas possessif rend compte de cette ambiguïté :
* La représentation appartient à un sujet qui la reconnaît comme telle, il ne lui est pas extérieur. Quel est le rapport du sujet à ''ses'' représentations ? Que signifie avoir des représentations ? Le sujet en est-il inévitablement l'auteur, ou, à l'inverse, peut-il y avoir des représentations sans sujet ?
* La représentation représente un sujet qui se représente en tant que sujet... Le sujet se trouve alors dans la représentation. Comment le sujet existe-t-il dans la représentation ? Quel est alors le mode d'existence d'un sujet ?
À la suite de ces questions, il devient difficile de ne pas demander qui constitue le véritable sujet de la représentation. Qui a conscience de ? Si c'est le sujet qui se trouve dans la représentation, alors il faut admettre que ce sujet est ''à la fois'' dans la représentation en tant que représenté qui se représente et hors d'elle en tant que sujet à proprement parler. On peut en venir alors à cette conclusion qu'il n'y a pas du tout de sujet véritable de ou dans la représentation : il n'y a seulement que de la représentation sans sujet. Nous concevons et ne concevons pas le sujet de la représentation...
Il ne pourrait donc y avoir de connaissance de soi par le moyen de la conscience. Autrement dit, la conscience de soi, l'auto-présentation de nous-même, ne constitue qu'une conséquence du sujet, ou que l'une de ses facultés, de sorte que le sujet n'y apparaît pas véritablement en tant que cause. Appréhender une identité par la conscience (la conscience stable que je pense avoir de moi au cours de ma vie), c'est appréhender une série d'effets ou de symptômes d'une cause ou d'une série de causes dont je n'ai pas nécessairement une intuition immédiate. Une autre conséquence importante pour l'interprétation du sujet réside dans le fait que nous avons une expérience de la conscience de soi alors que cette conscience de soi, perçue comme telle, est impossible : nous n'avons, à propos de la conscience de nous-même, que la ''croyance'' qu'une certaine réalité que nous percevons constitue un soi, un sujet, un noyau intime ; nous n'en avons pas le savoir.
Le sujet en lui-même se dérobe donc à la conscience, et il ne reste plus alors que l'idée formelle d'un sujet-cause-substrat d'actes et de pensées que nous rapportons particulièrement à un nous-même bien difficile à penser. On pense alors à un sujet transcendantal, c'est-à-dire un sujet comme simple condition ''a priori'' de l'action et de la pensée, ce qui implique que :
* le sujet ne se situe nulle part dans le monde, il n'est pas donné, il n'est pas empirique ;
* il n'est pas déterminé : on ne peut définir ce qu'est ce sujet, ni lui appliquer les catégories de l'être (pour le dire trivialement : le sujet n'est ni grand ni petit, il n'a pas de couleur, etc.) ;
* le sujet constitue la cause première de la causalité de la volition ;
* le sujet est ce qui pense.
=== Autre conception : le sujet transcendantal ===
{{w|Emmanuel Kant|Kant}} développe une conception du sujet qui rompt avec la tradition cartésienne tout en conservant l'idée d'une subjectivité fondatrice. Le sujet transcendantal kantien désigne la condition de possibilité de toute expérience et de toute connaissance. Il ne s'agit pas d'un sujet psychologique, empirique, doté de telle ou telle caractéristique particulière, mais d'une structure formelle qui permet la synthèse de nos [[Philosophie/Perception|perceptions]].
Dans la ''Critique de la raison pure'', Kant distingue le « je pense » transcendantal (l'{{w|apperception}} transcendantale) du « moi » empirique. Le premier constitue l'unité synthétique de la conscience qui accompagne toutes mes représentations et les unifie dans une expérience cohérente. Sans cette unité, mes perceptions demeureraient dispersées, fragmentées, sans lien les unes avec les autres. Le sujet transcendantal n'est donc pas un objet de connaissance (on ne peut le connaître comme on connaît un objet du monde), mais la condition même de toute connaissance possible.
Cette conception soulève néanmoins des difficultés. Si le sujet transcendantal ne peut être connu, comment puis-je affirmer son existence ? Kant répond que nous en avons une conscience formelle, non une connaissance théorique. Le « je pense » accompagne nécessairement toutes mes représentations, mais ce « je » demeure vide de contenu déterminé. Il s'agit d'une forme pure, d'une fonction unificatrice, non d'une substance.
De plus, cette conception distingue radicalement le sujet transcendantal (condition de possibilité de l'expérience) du sujet phénoménal (le moi tel qu'il apparaît dans l'expérience). Le premier relève de l'ordre nouménal (les choses en soi), inaccessible à la connaissance théorique ; le second appartient à l'ordre phénoménal (les choses telles qu'elles nous apparaissent). Cette dualité pose la question de l'unité du sujet : comment le sujet transcendantal et le sujet empirique peuvent-ils constituer un seul et même être ?
=== Résumé des difficultés posées par la notion de sujet ===
Sujet et moi semblent bien constituer deux « choses » différentes. En quelque sorte, ''mon'' moi ne constitue pas la cause de ''mes'' actes, car cette cause serait un sujet qui n'est pas un ''moi''. Cette idée de sujet se trouve ainsi dotée de facultés diverses dont l'unité ou l'unicité fait problème : la volonté, l'entendement, etc. C'est le cas notamment en [[Philosophie/Morale|morale]], lorsque nous attribuons des mérites ou des blâmes : la responsabilité que nous supposons présuppose à son tour un sujet « en soi » capable de répondre de ses actes et de ses pensées. Mais on voit là le caractère circulaire d'une telle conception : pour comprendre le sujet, on se rapporte à des facultés du... sujet !
Cette difficulté fait apparaître l'obscurité inhérente du concept de sujet, obscurité déjà perceptible dans l'idée d'un acte de discours auto-référent : en disant « je », je produis un acte de discours qui ne se rapporte pas à l'objet de la même manière que celui que j'ai à l'esprit, ou aussi clairement, que si je dis par exemple « elle » ou plus trivialement encore « table ». Je suis l'objet même auquel je me réfère, et cette référence se constitue par le fait que c'est moi qui parle.
=== Obscurités du sujet ===
''Pour une analyse détaillée des aspects obscurs du sujet : voyez [[Philosophie/Inconscient|inconscient]] et [[Philosophie/Désir|désir]].''
* Obscurité quant à nos perceptions : nous ne nous percevons pas entièrement ou même pas du tout. Le sujet, même s'il tend vers la raison (qui, dans une conception classique, constitue son essence) n'est pas intégralement transparent à lui-même.
* Obscurité quant au monde : le sujet, en tant que sujet de perception et de connaissance, se trouve au sein d'un univers qui demeure pour lui en grande partie lacunaire, opaque et inexpliqué.
* Obscurité du sujet quant aux motifs de son action ([[Philosophie/Désir|désir]], [[Philosophie/Liberté|liberté]], [[Philosophie/Inconscient|inconscient]]).
* Obscurité quant à son origine et sa nature : problème de l'[[Philosophie/Inconscient|inconscient]].
Il apparaît ainsi que la conscience, loin de s'identifier immédiatement au sujet lui-même, entretient avec lui un rapport problématique. L'aspect le plus significatif réside dans le [[Philosophie/Désir|désir]], cette tendance qui peut sembler irrationnelle (le sujet de l'action n'est plus la raison) et échapper au contrôle de la conscience, notamment lorsque cette conscience est morale.
== Rejet de la notion de sujet ==
L'obscurité de la notion de sujet étonne : c'est nous le sujet — croyons-nous — et nous ne sommes pourtant pas capables d'en produire une description claire et évidente. Se peut-il que cette déficience exprime une illusion ? Nous ne parvenons pas à élucider complètement la notion de sujet parce que le sujet n'existe pas véritablement et que sa notion ne constitue qu'un mot, un mot qui ne se réfère à rien de réel. Examinons cette dernière hypothèse.
Le point de départ tient à ce que le sujet n'existe pas, et que nous n'avons fait jusqu'ici que tenter de construire une notion. Nous commencerons par examiner ce que peut signifier la phrase « le sujet n'existe pas » dans tous les domaines qui intéressent la philosophie et nous l'illustrerons ensuite pour bien faire comprendre de quoi il retourne dans ce problème.
En premier lieu, si le sujet n'existe pas, il serait alors absurde de conserver les notions morales qui s'y rattachent : en tant que l'individu est un sujet, nous avons supposé qu'il est libre, capable de répondre de ses actes et doté d'une dignité inhérente et inaliénable. Mais s'il n'y a pas de sujet, alors l'individu ne constitue pas un sujet.
L'individu, l'être humain en général, n'est donc ni libre, ni responsable de ses actes et il ne possède aucune dignité au sens où la notion de sujet permettait de lui attribuer de manière essentielle toutes ces qualités. Considérons les conséquences de cette réfutation '''anti-humaniste''' du sujet.
* Si le sujet n'existe pas, l'homme n'est pas libre : cela signifie qu'il ne constitue pas la cause de ses actes et de ses pensées.
** La volonté, en tant que faculté de se déterminer soi-même d'après des principes rationnels, constitue donc également une notion dénuée de sens. L'homme n'a pas de volonté même s'il peut avoir des volitions (cf. {{w|Baruch Spinoza|Spinoza}}).
** La pensée n'appartient pas à un sujet. L'homme ne pense pas en vertu d'une faculté et il n'est pas forcément libre de penser ce qu'il pense, mais il subit ses pensées.
* Si le sujet n'existe pas, il n'est pas responsable de ses actes : l'individu ne peut donc avoir aucun mérite ni ne peut être blâmé. L'homme ne constitue pas alors essentiellement un être moral.
* Si le sujet n'existe pas, il n'y a pas de dignité humaine : l'existence humaine n'a donc pas de « valeur en soi ». Elle n'a pas de valeur morale ou juridique qui lui soit naturellement et surtout inconditionnellement attachée.
Pour faciliter la compréhension des conséquences d'un rejet de la notion de sujet, nous donnerons maintenant quelques exemples.
L'individu n'est pas libre. Il faut donc qu'il soit déterminé par d'autres causes que ce que l'on nomme sa volonté. Mais nous ne voyons pas alors d'autre causalité que celle des lois de la nature. Ainsi, tout homme constitue-t-il un être essentiellement déterminé par la nature. Il n'y a pas de transcendance humaine. Il faut donc que l'être humain ne soit qu'un être biologique vivant en société, être que l'on peut intégralement étudier par les sciences ({{w|physiologie}}, {{w|neurophysiologie}}, {{w|théorie de l'évolution}}, {{w|sociologie}}, etc.). Il n'y a pas d'autre explication disponible du phénomène humain qu'une explication naturaliste ou déterministe.
L'individu n'est pas responsable. Ses crimes ne peuvent lui être justement imputés comme s'il en était le véritable auteur. Tout homme est innocent de ses crimes. Mais aussi, en un sens contraire, personne n'est vraiment responsable de ses talents, de sa réussite, etc.
L'individu n'a pas de dignité. Sa valeur morale serait alors imaginaire et il n'aurait pas de droit naturel. Par exemple, les droits de l'homme ne porteraient sur rien d'autre qu'une fiction de l'homme. L'esclavage ne serait condamnable ni moralement ni juridiquement (pour ce qui concerne le droit naturel), et la torture ne le serait pas davantage. La soumission des femmes, par une société ou une religion, ne serait pas plus répréhensible. Pas plus que l'homme, la femme n'aurait de valeur spécifique, et l'usage de la force pour asservir les femmes à un ordre contraignant, quel qu'il soit, ne constituerait qu'un simple fait naturel.
Mais les conséquences de cette pensée ne s'arrêtent pas là. Si la notion de sujet est vide ou inintelligible, elle n'en existe pas moins en tant que pensée. Mais c'est une pensée qui ne réfère à rien d'extérieur. D'où vient alors cette idée ?
Plusieurs réponses ont été proposées. Pour {{w|Friedrich Nietzsche|Nietzsche}}, la notion de sujet provient d'une illusion grammaticale : parce que nos phrases ont une structure sujet-verbe-complément, nous supposons qu'il existe une entité substantielle (le sujet) derrière chaque action. Mais cette supposition constitue une erreur : « Il n'y a pas de "être" derrière l'agir, l'effectuer, le devenir ; "l'agent" n'est qu'une fiction ajoutée à l'action — l'action est tout » (''Généalogie de la morale''). De même, pour {{w|David Hume}}, lorsque nous introspectoons notre esprit, nous ne trouvons jamais un « moi » substantiel, mais seulement un faisceau de perceptions qui se succèdent avec rapidité. L'idée d'un sujet permanent serait donc une construction illusoire de notre imagination, qui unifie artificiellement cette diversité changeante.
Ces critiques débouchent sur ce que l'on appelle l'anti-humanisme théorique, courant philosophique qui rejette l'idée d'une essence humaine et d'un sujet autonome. Cette position a été développée notamment par {{w|Louis Althusser}}, {{w|Michel Foucault}} et {{w|Jacques Lacan}}, qui ont montré comment le « sujet » constitue en réalité le produit de structures sociales, linguistiques ou inconscientes qui le déterminent à son insu.
== Dilemme ==
Parvenus à ce point de la réflexion, nous nous trouvons dans la plus grande difficulté. La pensée du sujet et sa négation ne nous conduisent à rien de satisfaisant :
* La notion de sujet présente des lacunes telles que nous ne savons sur quoi faire reposer notre pensée.
* La négation du sujet entraîne des conséquences extrêmes qui ne sont généralement pas acceptées.
En ce qui concerne le premier point, nous pourrions nous demander si l'argument selon lequel le sujet constitue une notion peu intelligible ou creuse ne relève pas d'une fausse évidence. En ce qui concerne le second point, on peut se demander si les conséquences que l'on déduit de la négation du sujet sont véritablement nécessaires.
Commençons par le second point. En admettant que l'être humain n'est pas un sujet et qu'il n'a pas, en conséquence, de valeur morale en soi, nous avons alors fait comme si toute dignité et toute valeur devaient se comprendre d'après la notion de sujet. Mais c'est à l'évidence un sophisme, car le sujet étant supprimé, il n'en reste pas moins que l'homme, en tant qu'être naturel, fuit la douleur, recherche le plaisir, et attribue aussi des valeurs à ses semblables et aux choses qui l'entourent. Ainsi, dans une communauté, des hommes peuvent-ils faire preuve d'un respect mutuel sans avoir la moindre idée de ce que peut être un sujet au sens « métaphysique » que nous avons tenté de développer jusqu'à présent. Nier le sujet ne revient donc pas nécessairement à affirmer que « tout est permis », ou que « tout être humain est sans valeur ».
On peut à partir de là se demander s'il est possible, quelle que soit la forme d'anti-humanisme que l'on soutient (avec le {{w|relativisme}} moral qui lui est souvent attaché), de sacrifier intégralement les valeurs qui s'attachent à notre idée de l'homme. Il pourrait donc y avoir un abus à lier de manière essentielle la notion de sujet et ces notions morales que sont la liberté, la dignité, etc.
Cependant, il ne suit pas de là que la négation du sujet implique par principe les violences que nous avons citées plus haut. Bien au contraire, et l'histoire en fournit de nombreux exemples : il peut bien régner dans une communauté donnée des valeurs d'une grande humanité, cela ne signifie absolument pas que cette communauté s'interdira de réduire en esclavage des classes entières ou de tenir les femmes pour des biens dont on dispose pour le plaisir et la reproduction ainsi que de faire souffrir pour le seul plaisir du spectacle.
Mais, malgré tout, nous avons là une possibilité de concevoir le sujet qui peut nous aider à résoudre le problème soulevé par le premier point. En effet, nous parlions de communauté, ce qui implique la relation à l'[[Philosophie/Autrui|autre]]. Et nous pouvons nous rendre compte maintenant que nous avons surtout traité le sujet comme une abstraction métaphysique, comme une essence sans existence. Or, l'existence sociale et historique nous fait voir qu'il n'y a pas de sujet réel sans la reconnaissance d'un autre que soi. C'est cet entrelacement des individus que nous allons maintenant tenter d'éclaircir, entrelacement dont l'intérêt pourrait être de supprimer les faux problèmes de l'humanisme et de l'anti-humanisme.
== Le sujet et autrui ==
''Voyez l'article sur [[Philosophie/Autrui|autrui]] pour une analyse d'ensemble de cette notion.''
Ce qui se trouve ainsi mis en valeur, ce n'est plus le sujet isolé, ni même l'autre en tant que sujet, mais la relation de plusieurs sujets, relation qui pourrait bien être constitutive de ce que nous pensons lorsque nous nous pensons comme des sujets.
En effet, plusieurs philosophes contemporains ont montré que la conscience de soi ne se forme pas dans la solitude d'un {{w|cogito}} solitaire, mais dans et par la relation à autrui. {{w|Georg Wilhelm Friedrich Hegel|Hegel}}, dans la célèbre dialectique du maître et de l'esclave (''Phénoménologie de l'esprit''), montre que la conscience de soi exige la reconnaissance par une autre conscience. Je ne peux me constituer comme sujet que si un autre me reconnaît comme tel, et réciproquement. Cette reconnaissance mutuelle constitue le fondement de toute subjectivité véritable.
De même, {{w|Jean-Paul Sartre}} affirme que « autrui est le médiateur indispensable entre moi et moi-même » (''L'Être et le Néant''). C'est par le regard d'autrui que je prends conscience de moi-même comme objet dans le monde, que je découvre des aspects de moi-même qui m'échappaient. Autrui me révèle à moi-même, me constitue comme sujet visible, objectivable.
Plus récemment, {{w|Emmanuel Levinas}} a développé une conception encore plus approfondie du sujet constitué par la relation à autrui. Pour Levinas, le sujet ne se définit pas d'abord par la conscience de soi ou par la liberté, mais par la responsabilité envers autrui. C'est le visage d'autrui, dans sa vulnérabilité et son étrangeté absolue, qui m'assigne à la responsabilité et me constitue comme sujet éthique. Le sujet n'est donc pas d'abord un « je pense », mais un « me voici », une réponse à l'appel d'autrui.
Ces conceptions intersubjectivistes du sujet permettent de dépasser le dilemme entre l'affirmation dogmatique d'un sujet métaphysique isolé et la négation pure et simple du sujet. Elles montrent que le sujet se constitue dans la relation, dans l'échange, dans la reconnaissance mutuelle. Le sujet n'est ni une substance donnée d'avance, ni une pure illusion, mais une réalité qui se construit dans l'interaction sociale et l'engagement éthique.
== Sujet, existence et temps ==
''Cette section reste à développer.''
La question du rapport du sujet au temps constitue l'un des problèmes les plus complexes de la philosophie du sujet. Comment le sujet peut-il demeurer identique à lui-même alors que le temps transforme incessamment ses états, ses pensées, ses désirs ? Cette question traverse toute l'histoire de la philosophie, de {{w|Augustin d'Hippone|saint Augustin}} à {{w|Paul Ricœur}}, en passant par {{w|John Locke}} et {{w|Henri Bergson}}.
== Bibliographie ==
*''Catégories'', [[w:Aristote|Aristote]]
*''[[w:Méditations Métaphysiques|Méditations Métaphysiques]]'', [[w:René Descartes|Descartes]]
*''Introduction à la psychanalyse'', [[w:Freud|Freud]]
*''Soi-même comme un autre'', Paul Ricœur
[[Catégorie:Dictionnaire de philosophie (livre)]]
{{AutoCat}}
3pt9k2uhc6yro2e0rliydz3m0nn2436
Dictionnaire de philosophie/Morale
0
225
768688
765679
2026-06-26T04:32:01Z
PandaMystique
119061
Bot : ajout du paramètre lecture=oui au modèle {{DicoPhilo}}
768688
wikitext
text/x-wiki
{{DicoPhilo|Morale|lecture=oui}}
La morale est un ensemble de règles qui déterminent quelles conduites ou quels propos les hommes doivent tenir ou adopter dans le but de bien faire.
Les questions que l'on se pose à propos de la morale peuvent être réduites à un petit nombre, chacune ayant pour objet une notion morale importante : Pourquoi dois-je suivre certaines règles ? qui pose la question du ''devoir'', ou ''obligation'', moral. Que dois-je faire ? c'est-à-dire quel ''bien'' dois-je viser et comment puis-je le savoir ? Dans quels cas mes actions et mes paroles peuvent-elles être moralement qualifiées, qui pose la question de la ''responsabilité'' et de la ''liberté''. Enfin, la morale est-elle contraire au bien-être ou est-elle une condition, sinon la condition, du ''bonheur''.
Le devoir, le bien, la liberté, la responsabilité et le bonheur sont ainsi des notions fondamentales de la morale.
== Introduction ==
La définition de la morale proposée au début de cet article n'est qu'un point de départ intuitif. Par « intuitif », nous entendons simplement le genre d'idées, que l'on a cependant rassemblées en une seule proposition, dont on peut penser qu'elles viendraient à l'esprit d'un individu du XXI{{ème}} siècle vivant en Occident lorsqu'il tente de comprendre ce qu'il entend par morale. Il y a donc là une part d'arbitraire et de généralisation vague que nous assumons afin de pouvoir commencer quelque part l'analyse de la notion.
Nous commencerons par examiner les idées qui apparaissent dans cette définition de la morale et nous proposerons également un certain nombre de distinctions en vue de comprendre du mieux possible ce qu'il peut y avoir là de spécifique par rapport à d'autres domaines. L'ensemble de ces analyses conduira ou non à une révision de la définition de départ. Nous en reprendrons les résultats dans une section récapitulative, reprise qui pourra servir, sinon de définition, du moins de description plus ou moins fiable.
== Premiers éléments d'analyse ==
=== Règle ===
La notion de règle semble liée intuitivement à celle de morale, sans qu'il soit nécessaire de préciser au préalable la nature de ce lien. Supprimons en effet cette idée de règle ; on supprime du même coup l'idée de prescription, tout semble alors permis et c'est la morale même qui disparaît. L'idée de règle est donc de manière évidente une idée constitutive de la morale, c'est-à-dire que l'on ne peut penser la morale sans la règle.
En quoi consiste cette idée de règle ? Une règle est une formule qui indique ou prescrit ce que l'on doit faire dans tel ou tel cas. Il y a des règles à suivre en logique, mais aussi en art, dans nos relations sociales, dans les sciences en général. Toutes ces règles ne sont pas morales ; les règles de formation d'une proposition logique ne sont pas morales ; leur acquisition et leur mise en œuvre relève d'un savoir technique dont la finalité est la distinction du vrai et du faux. De même, les règles de l'art sont des techniques dont la finalité est le beau, le sublime, et d'autres effets esthétiques. Qu'est-ce qui distingue la règle morale de la technique artistique, du raisonnement, de l'expérience scientifique ?
La règle morale suppose connaissance et conscience d'un sujet ; conscience et sentiment d'une prescription, ce que l'on appelle une obligation morale ; une détermination de la volonté ; une fin à atteindre.
=== Conduites ===
=== Bien ===
== Distinguer la morale du droit ==
La morale n'est pas le seul ensemble de règles qui nous dit ce que nous devons faire et dire, ou ne pas faire ni dire. Le droit le fait également par le moyen des lois. D'ailleurs, en morale aussi on parle de lois pour désigner les devoirs qui s'imposent à nous. Morale et droit se ressemblent donc beaucoup et il faut voir si on peut les distinguer.
=== Différence par rapport aux sanctions ===
D'abord, le droit sanctionne les comportements et les propos contraires à la loi. Sans ces sanctions, les lois apparaissent sans force, et donc complètement inutiles. En revanche, la morale ne sanctionne pas toujours, et pourtant l'absence de sanction ne signifie pas que nos devoirs soient inutiles ni que la morale soit abolie. Ainsi l'absence de sanction abolit le droit, mais laisse intacte la morale.
=== Différence par rapport aux sentiments ===
La différence entre droit et morale peut également être constatée par la différence des sentiments que nous pouvons avoir ou non à leurs égards. Nous pouvons craindre la sanction de la loi, ou ne pas la craindre. Dans les deux cas, la loi reste valide tant que sa violation est sanctionnée ; peu importe les sentiments des individus, la loi règne tant qu'ils s'y conforment. En revanche, on ne peut parler de morale indépendamment de la manière dont les individus accomplissent leur devoir : obéir par contrainte, par habitude, par plaisir, ne permet pas de dire qu'une action est morale, c'est même tout le contraire, puisque de telles actions ne sont pas morales du tout, et peuvent même être immorales malgré les apparences contraires.
=== Différence par rapport à la finalité ===
Le droit vise à réguler des comportements humains au sein d'une société ; en dehors de ce contexte, les lois ne semblent pas avoir de sens. Il faut en effet que des rapports aux autres soient institutionnalisés pour pouvoir établir un droit qui régule ces rapports. Le but de cette régulation est souvent appelé ''justice''. La morale a en revanche un sens même en l'absence d'autrui : j'ai des devoirs envers moi-même, qui me dictent par exemple de me respecter. Ainsi, en toutes circonstances, dois-je agir non seulement suivant la justice, mais également suivant le ''bien''.
Autrui n'est bien sûr pas absent de la morale, mais cette remarque permet de voir que la morale s'adresse d'abord à des individus dans leurs conduites singulières, par rapport à eux-mêmes et par rapport aux autres, tandis que le droit s'adresse à ces mêmes individus dans leurs conduites réciproques, en laissant souvent de côté bon nombre de relations de l'individu à lui-même.
=== Conclusion ===
Le droit et la morale se distinguent tant par les caractéristiques qui en font des obligations et par la manière dont ces obligations peuvent et doivent être appréhendées, que par leurs finalités. Il s'agit donc bien de deux choses tout à fait différentes.
== Comment sait-on ce qui est bien et mal ? ==
Les distinctions précédentes sont utiles pour bien délimiter le domaine de la morale et identifier ce dont on parle. Toutefois, jusqu'ici, nous nous sommes surtout attachés à élucider une conception intuitive de la morale. Ces élucidations auront peut-être permis au lecteur de se faire une idée plus claire des notions qu'il associe à la morale. Mais, pour en acquérir une connaissance véritablement justifiée, il convient maintenant d'aborder les choses de manière plus fondamentale, et la question qui doit se présenter en premier lieu est celle de notre connaissance même des valeurs morales, car nous avons fait jusqu'à présent comme si cette connaissance allait de soi.
=== Raison et sentiment ===
=== Les différentes sources de la morale ===
D'une manière très générale, il existe deux types de conception des fondements de la morale :
*'''une conception objectiviste''', qui affirme que les lois morales ne dépendent pas de l'homme, mais :
**sont des lois de la nature (philosophie grecque en général);
**sont des commandements divins ;
**sont des lois de la raison, en tant que tout être raisonnable (donc outre l'homme) doit y obéir.
*'''une conception relativiste''', pour laquelle les valeurs morales ont une origine humaine :
**parce qu'elles sont imposées par la société ou par un groupe quelconque ;
**parce qu'il appartient à l'individu en tant que tel de les définir.
Dans la conception objectiviste (ou réaliste), les valeurs morales sont éternelles et universelles, ou, au moins, absolues ; on ne peut donc les changer ni les détruire. Au contraire, dans la seconde conception, les valeurs morales sont variables d'une société, d'un groupe ou d'un individu à l'autre. Cette seconde conception est souvent présentée de manière descriptive, alors que la première est normative. Dans le deuxième, il est difficile de condamner des pratiques qui appartiennent à d'autres sociétés (peine de mort, soumission des femmes, etc.), alors que la morale du premier type prétend s'imposer à tout être raisonnable, dans tous les temps et dans tous les lieux.
== Qu'est-ce que le bien ? ==
=== Morale de la perfection ===
Cette morale définit le bien désirable comme perfection ; cette perfection n'est pas subjective, mais peut se décrire objectivement. Par exemple le [[savoir]], la réussite, etc. Ce bien est conçu comme le fondement du bonheur, mais sans impliquer la satisfaction subjective. Ce bien représente souvent la réalisation optimale de la nature humaine, et se trouve être de ce fait inégalitaire. Il définit en effet une [[hiérarchie]] des perfections à atteindre, hiérarchie d'où découle le mérite des [[individu]]s.<br />
"Toutes les actions de notre âme qui nous acquièrent quelque perfection sont vertueuses, et tout notre contentement ne consiste qu'au témoignage intérieur que nous avons d'avoir quelque perfection." ([[Descartes]], Lettre à Elisabeth).<br />
=== Morale de la vertu ===
Ce type de morale insiste moins sur les règles à suivre en matière de morale, que sur les bonnes habitudes que nous devons prendre pour nous perfectionner et acquérir un bon caractère (dont les traits sont par exemple : générosité, bienveillance, etc.). C'est sans doute la morale philosophique la plus ancienne de notre civilisation. Ainsi, pour Platon, notre perfection consiste en l'acquisition de quatre vertus cardinales: la [[sagesse]], le [[courage]], la [[tempérance]] et la [[justice]]. Pour Aristote, la vertu est une bonne habitude acquise dans le but de réguler les émotions (par exemple, ne pas fuir face au danger), et consiste en un juste milieu qui nous fait éviter les extrêmes qui sont des vices, par exemple : lâcheté <-- courage --> témérité.
La détermination de ce juste milieu (ou médiété) n'est pas une chose facile, et demande un examen de la raison. À ces vertus, les théologiens ajoutèrent la foi, l'espérance et l'amour.
Cette conception de la vertu implique également que l'on évite les mauvaises habitudes, i.e. les vices, telles que la lâcheté, l'injustice et la vanité. Ce genre de morale exige une éducation morale développée dès le plus jeune âge, ce qui rend les adultes responsables de la conduite morale des enfants et du développement de leurs vertus.
Le concept de vertu a joué un grand rôle jusqu'au milieu du XIX{{ème}} siècle, puis déclina face à de nouvelles morales (évolutionnisme, utilitarisme, etc.). Mais, au milieu du XX{{ème}} siècle, cette idée a retrouvé une nouvelle jeunesse avec des philosophes comme [[Anscombe]] et [[MacIntyre]] estimant que les philosophes avaient trop négligé le développement du caractère humain et que la vertu joue un rôle fondamentale dans la vie sociale.
=== Morale du devoir ===
Les morales du devoir fondent le caractère moral de nos actions par le concept d'obligation. Ce type de morale se conçoit indépendamment de toute conséquence qui pourrait résulter de nos actions. Par exemple, selon [[Kant]], on ne doit pas mentir pour éviter un meurtre, car l'obligation de dire la vérité est absolue et ne tolère aucune condition particulière.
Il existe plusieurs théories des devoirs :
*[[Pufendorf]] distingue trois type de devoir :
**devoirs envers Dieu (dévotion interne et externe) ;
**devoirs envers soi-même (devoirs envers l'âme : par exemple développer ses talents, et devoirs envers le corps -ne pas se tuer, ne pas se nuire) ;
**devoirs envers autrui (devoirs absolus : ne pas nuire, etc. et devoirs conditionnels : tenir sa parole, etc.).
*'''théorie des droits''' (par exemple [[Locke]]), dans laquelle :
**les droits sont naturels (par exemple, vivre, être libre, rechercher le bonheur) ;
**ils sont universels ;
**ils sont les mêmes pour tous ;
**ils sont inaliénables.
Il faut souligner que tous droit appelle un devoir.
*'''l'impératif catégorique''' : c'est la théorie [[Kant|kantienne]] de la morale. Kant distingue plusieurs types d'impératifs :
**l'impératif hypothétique nous dit que si nous voulons ceci, nous devons faire telle ou telle chose ;
**l'impératif catégorique nous dit seulement que nous devons faire telle chose, quoique nous voulions ou désirons.
Les théories du devoir n'exposent pas seulement le ou les principes qui rendent morale une action, mais s'efforcent également de résoudre les conflits qui résultent de nos devoirs eux-mêmes.
=== Morales conséquentialistes ===
Dans nos actions, nous prenons souvent en compte les conséquences de nos actes. Ces conséquences peuvent donc être considérées comme des critères possibles de notre comportement, ce qui fait de ce type de morale, un type normatif. Pour une morale de ce genre, une conduite est morale si les conséquences d'un acte sont plutôt bénéfiques que défavorables. L'évaluation de la moralité d'une conduite se fait donc sur la base de ce qui est observable, plutôt que sur l'intention qui a un caractère privé et difficile à appréhender.
Plusieurs types de conséquentialisme peuvent être distingués, selon le critère que l'on choisit pour déterminer ce qui est bénéfique et ce qui est nuisible :
*l'altruisme : les conséquences de l'action favorables à quiconque excepté l'agent déterminent ce qui est bien et ce qui est mal
*l'égoïsme : les conséquences de l'action favorables à l'agent et à lui seul déterminent ce qui est bien et ce qui est mal ;
*l'[[utilitarisme]] : les conséquences de l'action favorables à tous déterminent ce qui est bien et ce qui est mal. [[Bentham]] est l'un des premiers philosophes utilitaristes. Il propose d'une part de considérer les conséquences de nos actions, et, d'autre part, de mesurer le plaisir et la peine qui en résultent, d'où le nom d'[[hédonisme]] utilitariste de cette doctrine.
== Débats contemporains ==
*bioéthique
*l'éthique des affaires
*les droits de l'homme
*la tolérance
== Bibliographie ==
De nombreux classiques sont sur internet. Nous nous efforcerons d'en donner les liens. Les titres en bleu clair dirigent vers les textes disponibles sur [[s:Accueil|Wikisource]].
=== Ouvrages abordables ===
*''[[s:Lettre à Ménécée|Lettre à Ménécée]]'', Épicure
*''[[s:Manuel d’Épictète|Manuel]]'', ''Entretiens'', Épictète
*''[[s:De la vie heureuse|De la vie heureuse]]'', Sénèque
*''Le Fondement de la morale'', Schopenhauer
=== Bibliographie générale ===
*''Phédon'', [[Platon]]
*''Éthique à Nicomaque'', Aristote
*''Les politiques'', Aristote
*''La République'', Cicéron
*''Des Lois'', Cicéron
*''Des biens et des maux'', Cicéron
*''Des devoirs'', Cicéron
*''Contre les moralistes'', Sextus Empiricus
*''Contre le mensonge'', Augustin d'Hippone
*''Traité des passions'', Descartes
*''Lettres à la princesse Élizabeth'', Descartes
*''Traité de morale'', Malebranche
*''Éthique'', Spinoza
*''Essais de théodicée'', Leibniz
*''Essai philosophique concernant l'entendement humain'', Locke
*''Traité de la nature humaine'', Livres II et III, Hume
*''Enquête sur les principes de la morale'', Hume
*''Critique de la raison pratique'', Kant
*''Métaphysique des mœurs'', Kant
*''Déontologie ou science de la morale'', Bentham
*''Fondements de la morale et de la religion'', Maine de Biran
*''Système de l'éthique'', Fichte
*''Principes de la philosophie du droit'', Hegel
*''L'Unique et sa propriété'', Stirner
*''L'Utilitarisme'', Mill
*''Aurore, réflexions sur les préjugés moraux'', [[Nietzsche]]
*''Par-delà bien et mal'', Nietzsche
*''Généalogie de la morale'', Nietzsche
*''Les Bases de la morale évolutionniste'', Spencer
*''Les Deux Sources de la morale et de la religion'', Henri Bergson
*''L'Éthique protestante et l'esprit du capitalisme'', Max Weber
*''Principia Ethica'', Moore
*''Le Formalisme en éthique et l'éthique matérielle des valeurs'', Scheller
*''La Conquête du bonheur'', Bertrand Russell
{{DEFAULTSORT:Morale}}
oa744j7izphtbzmfqpaz8rcg5wdv3m9
Dictionnaire de philosophie/Mort
0
13740
768689
758799
2026-06-26T04:32:11Z
PandaMystique
119061
Bot : ajout du paramètre lecture=oui au modèle {{DicoPhilo}}
768689
wikitext
text/x-wiki
{{DicoPhilo|Mort|lecture=oui}}
== I. Définitions et approches fondamentales ==
=== 1.1 Définitions ontologiques et biologiques ===
La mort est souvent décrite, de façon très générale, comme le passage de la vie à la non-vie. Cette formule a l’avantage de la simplicité, mais elle reste vague, car elle ne dit ni ce qu’est « vivre » au sens pertinent (biologique, psychologique, moral, juridique), ni à partir de quel seuil on doit parler de mort plutôt que d’agonie ou de défaillance grave. C’est pourquoi les discussions sur la mort se distribuent habituellement entre deux plans. D’un côté, la médecine et le droit cherchent des critères opératoires, permettant de constater un décès de manière fiable et publique. De l’autre, la philosophie demande ce que ces critères présupposent : s’agit-il de la fin d’un organisme, de la fin d’une personne, de la fin d’une conscience, ou de la fin d’un certain type de relation sociale et symbolique ?
Dans les pratiques contemporaines, la mort est généralement constatée à partir d’un état tenu pour irréversible. Selon les cadres juridiques et médicaux, on retient soit la cessation irréversible des fonctions circulatoires et respiratoires, soit la cessation irréversible de l’ensemble des fonctions de l’encéphale (ce que l’on appelle couramment « mort encéphalique »). Il faut éviter ici une confusion fréquente : la « mort clinique » désigne classiquement la phase initiale d’un arrêt cardio-respiratoire, phase qui peut, dans certains cas, être réversible grâce à une réanimation rapide ; elle ne coïncide donc pas, par elle-même, avec la mort au sens du décès constaté.
==== 1.1.1 La mort : un phénomène biologique ====
La mort n’est pas un interrupteur qui ferait tomber instantanément toutes les fonctions du vivant. La décomposition est un processus, et la disparition des fonctions se fait à des vitesses différentes selon les tissus. C’est ce décalage qui explique que certaines activités cellulaires puissent persister un temps après l’arrêt de la circulation, sans que cela suffise à dire que « l’organisme », comme totalité vivante, continue d’exister.
Il faut également corriger une idée reçue très répandue : les cheveux et les ongles ne « poussent » pas après la mort. L’impression de croissance vient surtout de la déshydratation et de la rétraction des tissus cutanés, qui découvrent davantage la tige du poil ou le bord de l’ongle et donnent une illusion d’allongement.
Enfin, la notion de mort encéphalique soulève une difficulté conceptuelle bien connue. En pratique, l’état dit de « mort encéphalique » n’est possible, comme état clinique observable, que sous assistance (notamment ventilation et prise en charge de réanimation), puisque l’organisme ne respire plus spontanément. Des publications ont néanmoins signalé des cas de maintien somatique prolongé après un diagnostic de mort encéphalique, parfois pendant des semaines ou davantage, avec persistance de certaines fonctions (thermorégulation altérée mais partielle, croissance chez l’enfant, gestation maintenue sous assistance, etc.). Ces situations, rares et discutées, nourrissent la controverse sur l’idée selon laquelle le cerveau serait, à lui seul, « l’intégrateur » indispensable de l’organisme.
==== 1.1.2 La mort comme fin ou comme cessation : clarifier le vocabulaire ====
Pour éviter les malentendus, on peut distinguer deux manières de parler, qui ne se recouvrent pas toujours. La première décrit la mort comme une fin : le vivant « n’est plus », au sens où le sujet a cessé d’exister. La seconde décrit la mort comme une cessation : certaines fonctions vitales, identifiées comme décisives, cessent définitivement. Ces deux descriptions se croisent souvent, mais elles ne répondent pas exactement à la même question. La première demande : « qu’est-ce qui disparaît ? » La seconde demande : « quels signes publics permettent d’établir que la disparition est advenue ? »
Cette distinction éclaire un point important : les définitions opératoires (médicales et juridiques) ne sont pas, à elles seules, des définitions métaphysiques. Le droit doit pouvoir trancher, mais trancher ne revient pas à épuiser le sens de ce qui est tranché. C’est ce décalage qui explique, par exemple, qu’un même cadre légal puisse être jugé satisfaisant du point de vue de la sécurité des pratiques, tout en restant contesté du point de vue de la signification de la mort.
On peut illustrer ce rôle des critères opératoires avec l’exemple, souvent cité, du droit américain : l’Uniform Determination of Death Act propose une formulation qui admet deux voies de constatation, l’une par la cessation irréversible des fonctions circulatoires et respiratoires, l’autre par la cessation irréversible de toutes les fonctions de l’ensemble du cerveau, tronc cérébral compris, la détermination devant suivre les standards médicaux reconnus.
==== 1.1.3 Les critères de la mort : problèmes contemporains ====
La question philosophique de la définition de la mort a acquis une urgence pratique nouvelle avec les progrès de la médecine moderne, notamment l'invention des respirateurs artificiels et des techniques de réanimation. Traditionnellement, la mort s'identifiait par le critère cardio-respiratoire : une personne était déclarée morte lorsque son cœur cessait de battre et qu'elle cessait de respirer de manière irréversible. Ce critère demeurait suffisant à une époque où l'arrêt cardiaque entraînait rapidement des dommages cérébraux irréversibles.
Depuis les années 1960, un nouveau critère s'est progressivement imposé dans la plupart des juridictions : le critère de mort cérébrale (ou mort encéphalique). Selon ce critère, une personne meurt lorsque toutes les fonctions de son cerveau — ou du tronc cérébral, selon les juridictions — ont cessé de manière irréversible, même si son cœur continue de battre grâce au soutien artificiel<ref>Président's Commission for the Study of Ethical Problems in Medicine, ''Defining Death: Medical, Legal and Ethical Issues'', Washington D.C., 1981</ref>. Cette redéfinition a été motivée en partie par des considérations pratiques (notamment la possibilité de prélever des organes pour transplantation), mais elle soulève des questions philosophiques fondamentales.
Plusieurs philosophes et bioéthiciens ont contesté le critère de mort cérébrale. D'une part, certains patients déclarés en état de mort cérébrale continuent à manifester des fonctions intégratives significatives : régulation de la température corporelle, cicatrisation des blessures, réactions immunitaires, et dans quelques cas documentés, maturation sexuelle et croissance<ref>Alan Shewmon, « Brain Death: Can It Be Resuscitated? », dans ''Hastings Center Report'', vol. 39, n° 2, 2009, p. 18-24</ref>. Ces observations suggèrent que le cerveau ne constitue peut-être pas le seul organe responsable de l'intégration organisationnelle, ce qui ébranle le fondement biologique du critère de mort cérébrale.
D'autre part, les considérations neuroscientifiques récentes ont montré que la conscience peut persister dans des états cérébraux autrefois jugés incompatibles avec toute forme de conscience<ref>Calixto Machado, « Neuroscience and Brain Death Controversies », dans ''Journal of Religion and Health'', vol. 57, n° 5, 2018, p. 2001-2015</ref>. Si la mort d'une personne doit se définir comme la perte irréversible de sa capacité de conscience (comme certains philosophes le défendent), alors le critère actuel de mort cérébrale risque à la fois d'être trop restrictif — certaines personnes déclarées mortes pourraient garder une conscience résiduelle — et trop permissif — certaines personnes vivantes selon le critère actuel auraient perdu toute capacité de conscience.
Paweł Górka Nowak et Tomasz Żuradzki ont récemment soutenu qu'il n'existe point une seule définition biologiquement correcte de la mort, mais plutôt une pluralité de concepts d'organisme en biologie théorique, chacun donnant lieu à une conception différente de ce qui constitue la mort d'un organisme<ref>Nowak et Żuradzki, « How Many Ways Can You Die? », p. 4-19</ref>. Selon ces auteurs, la question « Quand une personne meurt-elle ? » n'a pas de réponse univoque indépendante du contexte et des objectifs pratiques que nous poursuivons en posant cette question.
=== 1.2 La mort comme privation et le problème du mal de mourir ===
L'une des grandes énigmes philosophiques consiste à déterminer en quoi consiste précisément le caractère nuisible de la mort. Pourquoi la mort se considère-t-elle comme un mal ? Cette question, qui peut sembler évidente, a engendré des débats philosophiques intenses depuis l'Antiquité.
==== 1.2.1 L'argument épicurien : la mort n'est rien pour nous ====
Épicure, philosophe grec du IVe siècle avant notre ère, a formulé l'un des arguments les plus célèbres et les plus troublants concernant la mort. Dans sa ''Lettre à Ménécée'', il affirme que « la mort n'est rien pour nous » (ὁ θάνατος οὐδὲν πρὸς ἡμᾶς)<ref>Épicure, ''Lettre à Ménécée'', dans ''Lettres et Maximes'', traduction de Marcel Conche, PUF, 1987, p. 217-229</ref>. Son raisonnement peut se reconstituer comme suit :
# Seul ce qui s'expérimente peut être bon ou mauvais pour nous (principe hédoniste).
# La mort n'implique aucune expérience, puisque nous n'existons plus pour percevoir.
# Par conséquent, la mort ne peut être ni bonne ni mauvaise pour nous.
Lucrèce, poète et philosophe romain disciple d'Épicure, a développé cet argument dans son poème ''De Rerum Natura'' (''De la nature des choses''). Il soutient que puisque nous n'avons pas souffert de ne pas exister avant notre naissance, nous ne devrions pas craindre de ne pas exister après notre mort<ref>Lucrèce, ''De la nature'', livre III, vers 830-869, traduction de José Kany-Turpin, GF Flammarion, 1997, p. 253-257</ref>. Cet argument, souvent appelé « argument de symétrie », affirme que notre attitude envers la mort future devrait être symétrique à notre attitude (indifférente) envers notre non-existence passée.
L'argument épicurien repose sur ce que les philosophes contemporains nomment « la condition d'existence » : quelque chose ne peut être bon ou mauvais pour un sujet que si ce sujet existe<ref>Fred Feldman, « Some Puzzles About the Evil of Death », dans ''The Philosophical Review'', vol. 100, n° 2, 1991, p. 205-227</ref>. Si cette condition s'accepte, et si la mort entraîne la cessation de l'existence (terminationnisme), il semble alors impossible que la mort soit nuisible pour celui qui meurt.
Pourtant, l'argument épicurien heurte immédiatement nos intuitions les plus profondes. Nous jugeons généralement que la mort prématurée d'un jeune adulte constitue un grave malheur, qu'il est pire de mourir à vingt ans qu'à quatre-vingt-dix ans, et que tuer quelqu'un lui cause du tort. Si la mort n'était rien pour nous, toutes ces intuitions se trouveraient invalides.
==== 1.2.2 La théorie de la privation (Deprivation Account) ====
La réponse dominante à l'argument épicurien dans la philosophie analytique contemporaine est la ''théorie de la privation'' (''deprivation account''). Cette théorie affirme que la mort s'avère nuisible pour celui qui meurt non en vertu de ses propriétés intrinsèques — la mort en elle-même n'implique effectivement aucune souffrance —, mais en vertu de ses propriétés extrinsèques : la mort nous prive des biens et des expériences que nous aurions connus si nous avions continué à vivre<ref>Thomas Nagel, « Death », dans ''Mortal Questions'', p. 1-10</ref>.
Selon cette approche, la mort constitue un mal ''comparatif'' : elle est nuisible parce qu'elle place la personne dans une situation pire que celle dans laquelle elle aurait été si elle n'était pas morte. Comme le formule Fred Feldman : « La mort au moment ''t'' est nuisible pour une personne ''S'' dans la mesure où elle rend la valeur de la vie de ''S'' inférieure à celle qu'elle aurait eue si ''S'' n'était pas morte à ''t'' »<ref>Feldman, ''Confrontations with the Reaper'', p. 133</ref>.
La théorie de la privation repose sur une distinction cruciale entre deux types de valeur : la valeur intrinsèque et la valeur extrinsèque. Un état de choses possède une valeur intrinsèque en vertu de ses propriétés intrinsèques — la douleur est intrinsèquement mauvaise, le plaisir intrinsèquement bon. Un état de choses possède une valeur extrinsèque en vertu de ses relations avec d'autres états de choses — une vaccination douloureuse s'avère extrinsèquement bonne parce qu'elle prévient une maladie future. La mort, selon la théorie de la privation, est extrinsèquement nuisible : elle est mauvaise non pour ce qu'elle est, mais pour ce qu'elle empêche<ref>Christopher Wareham, « Deprivation and the See-saw of Death », dans ''South African Journal of Philosophy'', vol. 28, n° 2, 2009, p. 247-262</ref>.
Cette approche parvient à esquiver l'objection épicurienne car elle ne requiert point que le sujet expérimente la mort comme nuisible. De même qu'une vaccination peut être bonne pour moi même si je ne perçois pas cette bonté au moment de la vaccination, la mort peut être nuisible pour moi même si je ne peux l'éprouver comme telle. L'erreur d'Épicure, selon les défenseurs de la théorie de la privation, consiste à avoir négligé la possibilité qu'un événement soit extrinsèquement nuisible.
== II. La mort et l'identité personnelle ==
=== 2.1 La continuité du moi et la survivance personnelle ===
La mort pose immédiatement une question métaphysique fondamentale : que devient ce qui me constitue lorsque je cesse de vivre ? Cette interrogation apparemment simple ouvre sur des problèmes ontologiques redoutables. Les théories de l'identité personnelle se divisent historiquement selon deux grands axes : d'un côté les théories du critère psychologique, de l'autre les théories dites somatiques. Ces deux approches proposent des réponses divergentes quant à la manière de concevoir ma relation à ma propre mort.
==== 2.1.1 Les théories psychologiques : la conscience comme fondement de l'identité ====
Les théories psychologiques, notamment chez John Locke et David Hume, font reposer l'identité personnelle sur une base psychologique : la continuité de la conscience et de la mémoire. Locke affirme explicitement dans son ''Essai concernant l'entendement humain'' (auquel il ajoute ce chapitre dans la deuxième édition de 1694) que « l'identité personnelle ne consiste pas dans l'identité de substance, mais dans l'identité de conscience »<ref>John Locke, ''An Essay Concerning Human Understanding'', Book II, Chapter XXVII, édition critique de P. H. Nidditch, Oxford University Press, 1975, p. 346</ref>. Cette formulation révolutionnaire introduit une distinction capitale entre trois types d'identité : l'identité de la personne, l'identité de l'homme (c'est-à-dire l'organisme biologique), et l'identité de substance.
Selon Locke, seule la continuité de conscience garantit l'identité personnelle à travers le temps. Je suis la même personne que celle qui a accompli telle action passée dans la mesure où je peux me souvenir d'avoir accompli cette action. Ce qui importe décisivement, ce n'est ni la persistance du même corps, ni la continuité de la même âme immatérielle, mais la chaîne ininterrompue de mémoire<ref>Locke, ''Essay'', Book II.XXVII.9-10, p. 346-347</ref>. Cette théorie psychologique possède des implications profondes pour notre compréhension de la mort : si l'identité personnelle consiste en continuité de conscience, alors la mort — entendue comme cessation irréversible de toute conscience — représente bien la fin du moi, la destruction de ce qui me rend moi-même.
David Hume, philosophe écossais du XVIIIe siècle, a transformé la théorie lockienne en l'approfondissant. Contrairement à Locke, Hume soutient que le moi n'est pas une entité substantielle stable mais plutôt un « faisceau de perceptions » (''bundle of perceptions''), c'est-à-dire une succession constante de pensées, de sensations et d'émotions<ref>David Hume, ''A Treatise of Human Nature'' (1739-40), Book I, Part IV, Section VI, traduction française de Jean-Pierre Clero, Garnier Flammarion, 1995, p. 301-314</ref>. Pour Hume, ce que nous appelons l'identité personnelle n'est qu'une fiction utile, une illusion née de la ressemblance et de la causalité entre nos perceptions successives. L'unité que nous attribuons à notre moi n'est qu'une construction artificielle de l'esprit.
Le débat entre Locke et Hume demeure fondamental dans la philosophie contemporaine de l'identité personnelle. Raphaela Boeker a montré que la théorie lockienne ne repose pas sur une simple analyse métaphysique, mais s'enracine dans des préoccupations morales et religieuses spécifiques : Locke développe sa théorie de l'identité en liaison directe avec sa conception de la responsabilité morale et du jugement divin<ref>Raphaela Boeker, « Locke and Hume on Personal Identity: Moral and Religious Background Assumptions », dans ''History of Philosophy Quarterly'', vol. 32, n° 2, 2015, p. 145-165</ref>. La mémoire, pour Locke, constitue le critère d'identité non pas comme simple fait métaphysique brut, mais parce que la responsabilité morale — et particulièrement la possibilité du jugement et de la récompense ou du châtiment divins — exige que celui qui subit les conséquences de ses actes soit identique à celui qui les a commis.
==== 2.1.2 Derek Parfit et la réduction de l'identité personnelle : explications détaillées ====
===== Le problème fondamental : réductionnisme vs. non-réductionnisme =====
Pour comprendre la contribution majeure de Derek Parfit à la philosophie de l'identité personnelle, il faut d'abord saisir une distinction conceptuelle capitale qu'il établit : celle entre réductionnisme et non-réductionnisme.
'''La conception non-réductionniste''' affirme que l'existence d'une personne constitue une « réalité supplémentaire » (''further fact'') qui s'ajoute à l'ensemble des faits physiques et psychologiques. Selon cette vision, même si nous connaissions la totalité absolue des faits concernant mon cerveau, mon corps et tous mes états mentaux, il resterait encore un fait supplémentaire à déterminer : suis-je la même personne ou une personne différente ? Ce fait additionnel serait une réalité métaphysique fondamentale, irréductible — peut-être une âme immatérielle ou un « moi » cartésien pur<ref>Derek Parfit, ''Reasons and Persons'', Oxford University Press, 1984, p. 199-215</ref>.
'''La conception réductionniste''', par contraste, soutient que l'existence d'une personne ne constitue point une réalité supplémentaire. L'existence d'une personne se réduit entièrement à l'existence de son cerveau, de son corps et à la manifestation de certains événements physiques et psychologiques. Parfit offre une analogie élucidante : de même que l'existence d'une nation n'ajoute aucune réalité supplémentaire au-delà des faits concernant les individus qui la composent et leurs interrelations mutuelles, l'existence d'une personne n'ajoute rien au-delà des faits psychophysiques<ref>Parfit, ''Reasons and Persons'', p. 210</ref>. Une nation constitue une réalité complexe mais réductible ; il en va identiquement pour une personne.
Parfit défend le réductionnisme en soulignant l'absence totale de preuves probantes en faveur de l'existence de ces supposées « réalités supplémentaires ». Les cas de fission cérébrale — où le cerveau est divisé en deux — montrent qu'aucune entité métaphysique simple et indivisible ne détermine univoquement qui je suis. Aucune preuve scientifique ne corrobore l'existence d'une âme ou d'un moi cartésien<ref>Parfit, ''Reasons and Persons'', p. 207-210</ref>.
===== La théorie réductionniste de l'identité personnelle : la Relation R =====
Parfit propose que si nous adoptons le réductionnisme, nous devons spécifier les faits plus élémentaires en lesquels l'identité personnelle se réduit. Il examine deux candidates principales : la continuité physique et la continuité psychologique. Après analyse rigoureuse, il conclut que c'est la continuité psychologique qui importe<ref>Parfit, ''Reasons and Persons'', p. 201-215</ref>.
Parfit introduit alors le concept central de sa théorie : '''la Relation R''' (continuité et connectivité psychologiques). Cette relation se compose de deux éléments distincts :
1. '''La connectivité psychologique directe''' (''psychological connectedness'') : les connexions immédiates et directes de mémoire, de désir, d'intention et autres états mentaux entre mes états psychologiques à deux moments différents. Par exemple, je me souviens d'avoir pris hier une décision particulière.
2. '''La continuité psychologique''' (''psychological continuity'') : une chaîne causale connectée d'états psychologiques reliés les uns aux autres, même si les états situés aux extrémités de cette chaîne ne sont pas directement connectés l'un à l'autre. Par exemple, je me souviens d'hier, hier je me souvenais d'avant-hier, et ainsi de suite — même si je ne me souviens peut-être pas directement d'événements remontant à mon enfance.
La Relation R se définit donc comme '''la connectivité psychologique et/ou la continuité psychologique, pourvues de la bonne sorte de cause'''. La « bonne sorte de cause » ne pose aucune exigence restrictive particulière ; n'importe quelle cause causale suffit<ref>Parfit, ''Reasons and Persons'', p. 216</ref>.
===== Le cas de division du cerveau : le cœur du problème =====
La force de la position de Parfit réside largement dans son analyse du '''cas de division du cerveau''', qui crée une aporie majeure pour toutes les théories traditionnelles de l'identité.
'''Le scénario :''' Imaginez que vous souffriez d'une maladie incurable et que la seule issue thérapeutique consiste à séparer vos deux hémisphères cérébraux — qui fonctionnent normalement de manière tout à fait indépendante — et à les transplanter chacun dans un corps différent. Les deux êtres conscients résultant de cette opération seraient qualitativement identiques à vous : ils posséderaient les mêmes souvenirs, les mêmes intentions, les mêmes traits de personnalité. Vous vous réveilleriez apparemment comme deux personnes distinctes, chacune convaincue d'être vous.
'''Le problème logique qui en découle :''' Posez maintenant une question qui semble simple : dans ce scénario, serai-je numériquement identique à la personne qui se réveille dans le corps de gauche ?
- Si la réponse est « oui », serai-je aussi numériquement identique à la personne du corps de droite ?
- Si les réponses sont « oui » aux deux questions, alors par transitivité logique, les deux personnes seraient identiques l'une à l'autre — ce qui s'avère absurde.
- Si la réponse n'est « oui » que pour une seule des deux, il nous faut une raison objective pour distinguer les deux cas — mais aucune raison objective n'existe. Les deux corps sont identiques, et les deux personnes résultantes entretiennent une relation identiquement forte avec mon moi pré-division.
- Si la réponse est « non » pour les deux, il semblerait que je ne survive pas à l'opération, ce qui contredit notre intuition : la totalité de ma vie mentale s'est néanmoins perpétuée et poursuivie dans les deux corps<ref>Parfit, ''Reasons and Persons'', Partie III, sections 79-80, p. 254-255</ref>.
'''La conclusion paradoxale de Parfit :''' Parfit soutient que ce dilemme révèle que l'identité personnelle ne peut pas constituer ce qui compte vraiment pour la survie. Au lieu de cela, c'est '''la Relation R''' qui compte. Dans le cas de division, même si je ne suis numériquement identique à aucune des deux personnes résultantes (au sens strict de l'identité numérique), j'ai néanmoins survécu de la manière la plus excellente possible : la totalité de mes contenus mentaux, mes connexions psychologiques, se perpétuent dans les deux corps<ref>Parfit, ''Reasons and Persons'', p. 261</ref>.
Parfit énonce ainsi sa célèbre maxime : « l'identité n'est pas ce qui compte » (''identity is not what matters''). Ce qui doit vraiment nous préoccuper concernant notre avenir — ce qui mérite notre inquiétude rationnelle — ce n'est point l'identité numérique stricte, mais plutôt la présence et la persistance de la Relation R<ref>Parfit, ''Reasons and Persons'', p. 215-216</ref>.
===== Implications pour notre compréhension de la mort =====
Cette théorie novatrice engendre des implications importantes pour notre rapport à la mort :
'''Première implication :''' Si ce qui compte vraiment n'est pas l'identité personnelle mais la Relation R, alors notre peur ordinaire de la mort — entendue comme anéantissement simple et fin de l'identité — s'en trouve profondément reconsidérée. La mort met certes fin à la Relation R, mais ce phénomène ne s'avère pas plus mystérieux ou rationnellement troublant que d'autres ruptures naturelles de continuité<ref>Parfit, ''Reasons and Persons'', p. 216-223</ref>.
'''Deuxième implication :''' Si dans le cas de division du cerveau, ne pas être identique à mes continuateurs psychologiques ne constitue pas un problème du moment que la Relation R se perpétue, alors nous devons remettre en question notre conception ordinaire et quasi-intuitive de la mort. Une mort prématurée n'est mauvaise que parce qu'elle prive la Relation R d'une continuité ultérieure possible. Elle ne constitue point un mystère métaphysique terrifiant ou incompréhensible<ref>Parfit, ''Reasons and Persons'', p. 280-281</ref>.
'''Troisième implication :''' Parfit propose une conception graduée et nuancée de la mort. Au lieu de concevoir la mort comme une fin abrupte, totale et catégorique, nous pourrions la penser comme une diminution progressive de la Relation R. Même après le décès physique, certaines formes affaiblies de continuité demeurent — par exemple, dans la mémoire vivante d'autrui, dans l'influence durable de mon œuvre intellectuelle ou artistique. Ces continuités résiduelles, bien qu'atténuées, constituent toujours une forme résiduelle de persistance<ref>Parfit, ''Reasons and Persons'', p. 281</ref>.
===== Critiques majeures et difficultés =====
Cette théorie, bien qu'influente et féconde, affronte plusieurs objections philosophiques sérieuses :
'''L'objection de Bernard Williams :''' Williams soutient que la Relation R n'offre pas une base suffisante pour justifier nos craintes rationnelles face à la mort. Si la division cérébrale était physiquement possible, nous devrions rester indifférents entre une mort ordinaire et une division, puisque dans les deux cas ma Relation R continue. Or, il semble profondément irrationnel de rester indifférent entre ces deux scénarios différents. Cela suggère que quelque chose de plus profond que la Relation R importe réellement<ref>David Lewis, « Survival and Identity », dans ''The Identities of Persons'', University of California Press, 1976, p. 17-40</ref>.
'''L'objection de la non-trivialité :''' Si la Relation R est ce qui compte vraiment, alors selon Parfit, la question « serai-je identique à quelqu'un dans le futur ? » n'est pas tranchée par des faits profonds et non-triviaux. Or, cela semble peu convaincant : que je survive ou non me paraît dépendre de faits bien plus fondamentaux que la simple absence de division cérébrale ou la présence de continuité psychologique<ref>Parfit, ''Reasons and Persons'', p. 264-265</ref>.
'''L'objection de la perplexité conceptuelle :''' Si dans le cas de division je ne suis numériquement identique à aucune des deux personnes résultantes, mais que je survéco néanmoins en un sens significatif, cela rend le concept même de survie perplexe et énigmatique. Qu'est-ce que « survivre » pourrait signifier si ce n'est précisément être identique à une personne future<ref>Derek Parfit, « We Are Not Human Beings », ''Journal of Consciousness Studies'', vol. 19, n° 5-6, 2012, p. 79-105</ref> ?
===== La révision ultérieure de Parfit : une retraite stratégique =====
Fait remarquable et révélateur, Parfit lui-même a révisé sa théorie quelque trente années après sa publication. Dans un article tardif intitulé We Are Not Human Beings (2012), Parfit propose que les personnes ne sont point des organismes biologiques humains, mais plutôt des organismes cérébraux — plus précisément, des cerveaux ou leurs parties conscientes<ref>Parfit, « We Are Not Human Beings », p. 79-105</ref>. Cette position converge davantage vers une forme brainbound de l'animalisme : une réduction à la continuité cérébrale plutôt qu'à la continuité psychologique.
Cette révision tardive s'avère révélatrice. Elle suggère que même Parfit lui-même a trouvé la distinction entre la Relation R et l'identité numérique trop instable et insuffisante pour servir de fondement solide à une philosophie cohérente de la mort et de la persistance personnelle. Mais malgré cette retraite tardive et cette autocritique, sa contribution originale demeure profonde : avoir montré que les intuitions ordinaires concernant l'identité et la mort reposent sur des présuppositions métaphysiques fragiles et contestables qui méritent un examen critique rigoureux<ref>E. T. Olson, « On Parfit's View that We are not Human Beings », dans ''Journal of Philosophy'', vol. 112, n° 1, 2015, p. 5-22</ref>.
==== 2.1.3 L'animalisme : la mort comme mort de l'organisme ====
Face aux théories psychologiques, une position rivale s'est progressivement imposée au cours des dernières décennies : l'animalisme. Selon cette approche, nous ''sommes'' des organismes biologiques appartenant à l'espèce ''Homo sapiens''<ref>Eric Olson, ''The Human Animal: Personal Identity Without Psychology'', Oxford University Press, 1997, p. 1-18</ref>. Ce que nous désignons par le terme « personne » constitue simplement un animal particulier — nous ne sommes pas composés d'un corps animal auquel s'ajouterait une personne psychologique ; nous ''sommes'' l'animal lui-même.
Cette théorie possède des implications directes pour notre conception de la mort. Si je suis un organisme biologique, je meurs précisément quand cet organisme meurt, c'est-à-dire lorsque ses fonctions biologiques intégrées cessent de manière irréversible. L'animalisme semble offrir une réponse simple : la mort est littéralement la mort de l'organisme dont je suis, et c'est pourquoi elle constitue un mal pour moi<ref>Olson, ''The Human Animal'', p. 107-128</ref>. Il ne s'agit pas de chercher dans des abstractions psychologiques pour comprendre l'identité personnelle ; il suffit de reconnaître que je suis, ontologiquement, un animal.
Cependant, l'animalisme affronte une objection majeure. Si je suis le même animal que le fœtus dans l'utérus maternel ou que le nourrisson que j'ai été, comment affirmer que je suis identique à ce nourrisson, doté maintenant de souvenirs, de croyances et de désirs que le nourrisson ne possédait nullement ? Kevin Sharpe a proposé une position intermédiaire : le « psychological animalism » selon laquelle nous sommes essentiellement à la fois des animaux et des personnes<ref>Kevin W. Sharpe, « Animalism and Person Essentialism », dans ''Metaphilosophy'', vol. 46, n° 1, 2015, p. 48-68</ref>. Selon cette approche plus nuancée, nos conditions de persistance sont à la fois biologiques (puisque nous sommes des organismes) et psychologiques (puisque nous sommes essentiellement des personnes conscientes).
==== 2.1.4 Paul Ricoeur et l'identité narrative ====
Une approche différente a été développée par le philosophe français Paul Ricoeur, qui propose le concept d'« identité narrative ». Ricoeur reconnaît que toutes les théories traditionnelles de l'identité personnelle affrontent des difficultés logiques fondamentales : elles tentent toutes de fonder l'identité sur une base objective (la continuité de substance, de conscience, ou d'organisme), alors que la réalité de l'identité personnelle s'avère bien plus complexe et herméneutique<ref>Paul Ricoeur, ''Oneself as Another'', traduction de Kathleen Blamey, University of Chicago Press, 1992, p. 113-159</ref>.
Ricoeur propose que l'identité personnelle s'établit plutôt à travers le récit que j'élabore de moi-même. Je suis la personne que je me raconte être, et cette identité narrative se constitue par la manière dont j'intègre les événements de ma vie — y compris ma conscience de ma finitude et de ma mortalité — dans une totalité cohérente<ref>Ricoeur, ''Oneself as Another'', p. 140-168</ref>. L'importance de cette approche pour la compréhension de la mort réside en ceci : ma relation à ma propre mortalité ne se réduit pas à une simple question de fait biologique ou de continuité psychologique ; elle constitue une dimension constitutive de la manière dont je me comprends moi-même en tant que sujet.
La mortalité, selon Ricoeur, confère forme et sens à ma vie en lui donnant une trajectoire comprise entre naissance et mort. La conscience de ma finitude ne représente pas une simple information supplémentaire me concernant ; elle transforme fondamentalement le sens de mon identité<ref>Ricoeur, ''Oneself as Another'', p. 451-476</ref>. Cette approche narrative offre un avantage décisif : elle évite les apories des théories purement métaphysiques de l'identité en reconnaissant que l'identité personnelle constitue une construction herméneutique à la fois individuelle et collective, et qu'elle demeure essentiellement liée au temps vécu et à la conscience de la finitude.
=== 2.2 La mort de l'autre et l'altérité irréductible ===
Un aspect trop souvent négligé de la philosophie de la mort concerne ma relation à la mort d'autrui, plutôt que ma relation anticipée à ma propre mort. Tandis que la tradition existentialiste insiste sur la manière dont l'anticipation de ma propre mort structure mon existence, une autre tradition philosophique — issue notamment d'Emmanuel Levinas — soutient que c'est précisément la mort de l'autre, et non la mienne, qui pose la question éthique fondamentale.
==== 2.2.1 Levinas et l'impossibilité d'approprier la mort de l'autre ====
Emmanuel Levinas, philosophe d'origine lituanienne qui a passé la majeure partie de sa vie en France, a mis en avant le caractère absolument singulier et irréductible de la mort d'autrui. Dans son ouvrage maîtresse ''Totalité et Infini'', Levinas critique explicitement les philosophies existentialistes (particulièrement Heidegger) qui tendent à ramener la mort à une structure constitutive de l'existence du Dasein : il n'existe, selon Levinas, aucun moyen pour moi d'anticiper ou de posséder la mort de l'autre de la même façon que je pourrais (du moins en principe) anticiper ma propre mort<ref>Emmanuel Levinas, ''Totalité et Infini. Essai sur l'Extériorité'', Martinus Nijhoff, 1961, p. 235-250</ref>.
Levinas établit une distinction entre le mourir en tant que réalité que j'expérimente — acte que je pourrais en principe anticiper et intérioriser — et la mort d'autrui comme événement qui m'échappe absolutement. La mort du proche est ce qui me « résiste » de manière absolue, ce qui n'entre jamais dans le champ de ma compréhension ou de mon pouvoir<ref>Levinas, ''Totalité et Infini'', p. 238-240</ref>. Ce qui différencie profondément la position levinassienne de celle de Heidegger, c'est que pour Levinas, ce n'est pas ma mort qui me constitue éthiquement, mais ma responsabilité infinie face à la mort de l'autre.
La vulnérabilité d'autrui face à la mort m'appelle à la responsabilité éthique. La mort possible de celui que j'aime, la fragilité absolue du proche, le fait qu'à tout moment il peut disparaître — autant de réalités qui me posent une obligation morale incontournable<ref>Emmanuel Levinas, « De la conscience à la veille » (1990), dans ''Dieu, la mort et le temps'', traduction de Michel Haerne, Grasset, 2007, p. 97-128</ref>. Cette responsabilité ne constitue pas quelque chose que j'aurais ''choisi'' de prendre, mais quelque chose auquel je suis d'emblée assigné du simple fait même de ma relation à autrui.
==== 2.2.2 Derrida et le deuil comme appropriation impossible ====
Jacques Derrida, philosophe franco-algérien qui a poursuivi certaines intuitions de Levinas, a accordé une importance philosophique centrale à la mort d'autrui, notamment à travers le phénomène du deuil. Dans son ouvrage ''Aporias'', Derrida pose des questions vertigineuses : la mort de l'autre peut-elle être anticipée ou redoutée de la même manière que ma propre mort ? N'existe-t-il pas une asymétrie entre mon rapport à ma propre mortalité et mon rapport à celle d'autrui<ref>Jacques Derrida, ''Aporias'', Galilée, 1996, p. 47-52</ref> ?
Derrida établit une distinction conceptuelle fondamentale entre le « mourir » (''dying''), qui désigne mon expérience particulière de mon propre processus vers la mort, et le « périr » (''perishing''), qui désigne l'altérité absolue de l'événement de mort d'autrui. Le mourir peut posséder un caractère de ''mien'', en un sens ; le périr de l'autre ne peut jamais véritablement m'appartenir — c'est précisément ce qui m'échappe de manière irréductible<ref>Derrida, ''Aporias'', p. 78-84</ref>. Cette distinction s'avère décisive pour comprendre le deuil : celui-ci porte fondamentalement sur quelque chose d'irrémédiablement perdu et incommunicable.
Derrida introduit une autre distinction conceptuelle importante : celle entre le travail du deuil (''mourning work'') et la mélancolie. Selon la tradition psychanalytique héritée de Freud, le travail du deuil consiste à progressivement désInvestir l'objet perdu pour réinvestir les relations vivantes. Cependant, Derrida soutient que ce modèle du deuil « réussi » passe sous silence quelque chose d'essentiel : la mort de la personne est unique, irremplaçable, incomparable. Aucun deuil ne peut être véritablement « complètement élaboré », car cela supposerait que la personne décédée puisse être finalement « intégrée » à mon univers psychique et émotionnel, ce qui constituerait une forme de trahison envers son absolue singularité<ref>Jacques Derrida, ''The Work of Mourning'', traduction d'Edmund Jephcott, Pascale-Anne Brault et Michael Naas, University of Chicago Press, 2001, p. 1-30</ref>.
Plus précisément, Derrida soutient que le deuil doit rester « impossible » en un sens précis : il faut à la fois s'approprier d'une certaine manière la mort de celui que j'ai aimé (pour ne pas l'abandonner à l'indifférence totale) et en même temps ne pas l'approprier complètement (pour respecter son altérité absolue)<ref>Derrida, ''The Work of Mourning'', p. 169-175</ref>. Le deuil authentique oscille dans cette aporie, dans ce double lien impossible. C'est en cela que tout deuil porte en lui une dimension irréductiblement mélancolique — non pas comme pathologie psychologique, mais comme structure constitutive de la relation à celui qui meurt.
==== 2.2.3 La mort de l'autre et la transformation de mon identité ====
La mort d'autrui ne se limite pas à constituer un problème éthique abstrait ; elle modifie profondément mon identité personnelle. Levinas affirme avec une force particulière que « la mort de l'autre affecte ma responsabilité »<ref>Emmanuel Levinas, ''Autrement qu'être ou au-delà de l'essence'', Martinus Nijhoff, 1974, p. 247-253</ref>. La mort d'un proche, d'une personne envers laquelle j'assumais une responsabilité, cela m'affecte ''en moi-même'' — ce n'est pas une expérience que je pourrais simplement observer ou éprouver de manière neutre.
La perte de celui qui meurt transforme ma relation à moi-même. Je ne suis plus la même personne après la mort de quelqu'un qui m'était cher. Mais cette transformation ne se réduit pas à une simple modification psychologique, ni même essentiellement affective — elle est d'abord éthique. J'ai perdu un vis-à-vis, quelqu'un à qui je devais rendre des comptes. Cette perte redéfinit constitutionnellement ce que je suis<ref>Paul Ricoeur, ''La Mémoire, l'Histoire, l'Oubli'', Éditions du Seuil, 2000, p. 523-538</ref>. Le deuil, dans cette perspective, ne constitue pas simplement un travail psychologique de séparation et d'élaboration ; il s'agit d'une reconstitution fondamentale de mon identité en l'absence de celui qui en était une part essentielle.
==== 2.2.4 La singularité irremplaçable de chaque mort ====
Une dernière dimension de l'importance philosophique de la mort de l'autre concerne sa singularité absolue et irremplaçable. Derrida insiste avec persistance sur le fait que chaque mort demeure unique, incomparable, singulière. Face à la mort de celui qu'on aime, ce que nous éprouvons c'est que « avec sa mort, c'est un monde entier qui disparaît »<ref>Derrida, ''The Work of Mourning'', p. 322-325</ref>. Cette affirmation peut sembler exagérée, car bien entendu le monde continue physiquement après la mort d'une personne. Mais Derrida entend souligner que du point de vue éthique et existentiel, quelque chose d'irréversible s'effondre.
Cette singularité de chaque mort crée une tension philosophique majeure. D'un côté, nous cherchons à honorer la mémoire des morts en les intégrant à une histoire collective, à une narrative communautaire qui préserve leur mémoire. De l'autre côté, cette intégration comporte un risque significatif : celui de « normaliser » ce qui était absolument singulier, de réduire le mort à une place dans une série identique<ref>Derrida, ''The Work of Mourning'', p. 140-160</ref>. C'est pourquoi le deuil authentique doit naviguer dans cette tension irrésolue : reconnaître l'unicité incomparable du disparu tout en le gardant présent dans la communauté des vivants, sans jamais complètement l'assimiler.
Cette question de la singularité de chaque mort rejoint une dimension essentielle de ma relation à mon identité personnelle : je suis moi aussi unique et incomparable, et ce que je redoute dans la mort, c'est peut-être moins la perte abstraite de conscience que la cessation de cette singularité particulière qui est précisément la mienne. L'impossibilité pour autrui de me sauver de ma propre mort, tout comme mon impossibilité à sauver celui que j'aime de sa mort, constituent deux faces d'une même condition humaine : nous sommes chacun seul face à notre mort, dans une solitude radicale. Et c'est paradoxalement cette solitude partagée qui constitue le fondement même de notre communauté éthique.
== III. Existentialisme et finitude ==
=== 3.1 Martin Heidegger et l'être-vers-la-mort ===
La compréhension existentialiste de la mort s'enracine profondément dans l'œuvre majeure de Martin Heidegger, ''Être et Temps'' (1927). Heidegger pose une question apparemment simple mais philosophiquement décisive : qu'est-ce que vivre authentiquement ? Sa réponse s'articule autour du concept du ''Sein-zum-Tode'' — l'« être-vers-la-mort » — qui ne constitue point une morbide obsession pour la mort, mais une élucidation fondamentale de ce que signifie exister en tant qu'être humain<ref>Martin Heidegger, ''Sein und Zeit'' (1927), traduction française de François Vezin, Gallimard, 1986, divisions II et sections 46-53, p. 279-304</ref>.
Heidegger établit d'abord une distinction cruciale entre le ''Dasein'' (littéralement « être-là ») — terme qu'il utilise pour désigner spécifiquement l'existence humaine — et les autres formes d'être. Ce qui caractérise uniquement le Dasein, c'est que son être-même reste constamment une question pour lui. Tandis que les choses inanimées simplement sont, et tandis que les animaux existent selon des instincts programmés, le Dasein doit constamment se demander « pour quoi existe-je ? »<ref>Heidegger, ''Sein und Zeit'', Introduction, p. 9-20</ref>.
C'est précisément dans cette ouverture à la mort que réside l'authenticité de l'existence. Heidegger affirme que la mort se situe à l'horizon de toute existence humaine, mais que nous la fuyons généralement. Dans la vie quotidienne, nous nous perdons dans le « On » — ce que nous pourrions appeler le « on-dit », la doxa, la sagesse populaire partagée — et nous nous laissons absorber dans les préoccupations de la routine. Le Dasein se disperse dans ce qu'il fait, ce qu'il pense, ce que tout le monde pense<ref>Heidegger, ''Sein und Zeit'', section 27, p. 153-164</ref>.
Heidegger énonce quatre caractéristiques fondamentales de la mort envisagée existentialement. Elle est d'abord « non-relationnelle » (unbeziehbar) — nul ne peut mourir à ma place, car ma mort demeure absolument singulière et insubstituable. Ensuite, elle est « certaine » — nous savons avec une certitude indubitable que nous mourrons. Elle est aussi « indéfinie » — bien que la mort soit certaine, nous ignorons quand elle surviendra. Enfin, elle est « insurpassable » (unüberholbar) — aucune possibilité future ne peut dépasser ou surpasser la mort, car elle termine toutes les possibilités<ref>Heidegger, ''Sein und Zeit'', section 50, p. 293-299</ref>.
Mais pourquoi cette analyse minutieuse de la mort revêt-elle une importance ontologique pour Heidegger ? Parce que la conscience de la mort, loin de paralyser l'existence, la libère en réalité pour une authenticité. Confronté à la certitude absolue de sa mort, le Dasein peut se « ressaisir » face au flux inauthentique de la vie quotidienne. Cette prise de conscience s'accompagne généralement d'une émotion caractéristique que Heidegger appelle l'« angoisse » (Angst) — non la peur devant un objet spécifique, mais plutôt une tonalité affective radicale face au néant et à la liberté<ref>Heidegger, ''Sein und Zeit'', section 40, p. 227-231</ref>.
L'angoisse, pour Heidegger, ne constitue point une pathologie — elle demeure salutaire. Elle arrache le Dasein à son immersion commode dans les préoccupations triviales et le confronte à la nudité de son existence. En expérimentant cette angoisse face à sa propre finitude, le Dasein découvre ce que Heidegger appelle sa « possibilité la plus extrême ». Cette possibilité, c'est précisément de vivre en constante anticipation de la mort, non par morbidité, mais comme principe d'organisation de l'existence authentique<ref>Heidegger, ''Sein und Zeit'', sections 30-31, p. 176-189</ref>.
Cette « anticipation authentique de la mort » (eigentliches Vorlaufen-zu-Tode) constitue ce que Heidegger appelle la « résolution authentique » ou « authenticité » (Eigentlichkeit). Elle ne paralyse point l'existence ; au contraire, elle nous permet de vivre de manière résolue, d'accepter nos responsabilités avec une lucidité nouvelle, et de nous approprier notre propre existence plutôt que de simplement la subir passivement<ref>Heidegger, ''Sein und Zeit'', section 53, p. 304-314</ref>.
=== 3.2 Jean-Paul Sartre : la liberté face à la facticité et la mort ===
Jean-Paul Sartre, bien qu'influencé par Heidegger, apporte une perspective radicalement différente sur la mort et l'existence. Le principe fondamental du système sartresque repose sur l'affirmation que « l'existence précède l'essence » — proposition qui inverse complètement la tradition métaphysique occidentale<ref>Jean-Paul Sartre, ''L'existentialisme est un humanisme'' (1945), Éditions Nagel, 1966, p. 15-49</ref>.
Selon Sartre, aucune essence humaine fixe ne préexiste à notre entrée dans l'existence. Nous venons à l'existence d'abord, et ce n'est qu'ensuite, par nos choix et nos actions, que nous créons notre essence et définissons ce que nous sommes. Il n'existe pas de « nature humaine » en attente de réalisation ; il n'existe que la tâche constante de nous créer nous-mêmes<ref>Sartre, ''L'existentialisme est un humanisme'', p. 45-49</ref>.
Cette liberté radicale qui caractérise l'existence humaine engendre ce que Sartre appelle une « responsabilité absolue ». Nous sommes « condamnés à être libres » — c'est-à-dire que nous ne pouvons nous échapper à la nécessité de choisir et de créer notre essence. Aucune instance divine, aucune nature fixe, aucune détermination externe ne peut nous exonérer de cette responsabilité<ref>Sartre, ''L'Être et le Néant'', Gallimard, 1943, p. 9-60</ref>.
Comment la mort s'inscrit-elle dans cette philosophie de la liberté absolue ? Ici, Sartre adopte une position critique envers l'approche heideggérienne. Pour Sartre, la mort ne constitue pas une structure fondamentale de l'existence — c'est plutôt un fait brut, une contingence qui met fin à la possibilité d'existence sans pour autant structurer l'existence elle-même. Sartre affirme que « la mort n'est jamais ce qui donne sens à la vie ; c'est au contraire ce qui enlève tout sens à la vie »<ref>Sartre, ''L'Être et le Néant'', p. 615-617</ref>.
Cette affirmation peut sembler étrange, mais elle exprime une intuition importante : contrairement à Heidegger qui voit dans la mort un principe d'organisation pour une vie authentique, Sartre soutient que la mort demeure simplement ce qui intervient du dehors, cessant abruptement toute possibilité de création de sens<ref>Sartre, ''L'Être et le Néant'', p. 619-623</ref>. Donc, plutôt que de nous interroger sur « comment vivre authentiquement en vue de la mort », nous devrions nous interroger sur « comment créer du sens et des valeurs dans une existence absurde qui sera inévitablement terminée »<ref>Sartre, ''L'existentialisme est un humanisme'', p. 37-44</ref>.
Pour Sartre, la vraie question n'est donc pas celle de la finitude face à la mort, mais celle de la liberté face à la facticité — c'est-à-dire face aux conditions concrètes données de notre existence (notre classe sociale, notre époque historique, notre corps, etc.). Notre liberté consiste à transcender cette facticité, à la dépasser vers des projets futurs, à nous créer dans et par rapport à ces conditions<ref>Sartre, ''L'Être et le Néant'', p. 568-580</ref>.
=== 3.3 Søren Kierkegaard : l'angoisse comme condition de la liberté ===
Bien que Kierkegaard soit antérieur au mouvement existentialiste proprement dit, ses analyses de l'angoisse et de la liberté constituent une source majeure pour les existentialistes modernes. Dans son ouvrage ''Le Concept d'angoisse'' (1844), Kierkegaard propose une analyse psychologique et métaphysique profonde de cette émotion radicale<ref>Søren Kierkegaard, ''Le Concept d'angoisse'', traduction de Knud Ferlov et Jean-J. Gateau, Éditions Gallimard, 1990, p. 45-120</ref>.
Kierkegaard distingue soigneusement entre la « peur » et l'« angoisse ». La peur toujours porte sur quelque chose de spécifique — on a peur d'un chien, d'une maladie, d'une blessure physique. L'angoisse, en contraste, ne porte sur rien de précis. Elle constitue plutôt l'émotion devant la pure possibilité, devant la liberté elle-même et ses implications infinies<ref>Kierkegaard, ''Le Concept d'angoisse'', p. 70-75</ref>.
Kierkegaard illustre cette distinction par une image frappante. Un homme se tient au bord d'un précipice. Il craint certes de tomber — c'est une peur naturelle et rationnelle. Mais il éprouve aussi l'angoisse en réalisant qu'il demeure libre de sauter. Rien dans le monde physique ne l'empêcherait de se jeter dans le vide si telle était sa volonté. C'est cette conscience de sa propre liberté, de cette possibilité absolue face à l'abîme, qui constitue l'angoisse — ce que Kierkegaard appelle « le vertige de la liberté »<ref>Kierkegaard, ''Le Concept d'angoisse'', p. 61-65</ref>.
Cruciale est l'observation de Kierkegaard selon laquelle cette angoisse ne constitue pas simplement un défaut à corriger, mais une dimension intrinsèque et inévitable de l'existence humaine. Chaque être conscient, chaque entité pourvue de liberté, doit expérimenter l'angoisse. Car la liberté elle-même consiste en la conscience d'une multiplicité infinie de possibilités, dont aucune n'est garantie, dont aucune ne s'impose avec une nécessité absolue<ref>Kierkegaard, ''Le Concept d'angoisse'', p. 45-50</ref>.
Or, cette angoisse demeure étroitement liée chez Kierkegaard à la conscience de la mort. Pourquoi ? Parce que la mort représente la limite absolue de ma liberté — le moment où l'ensemble des possibilités qui se déploient devant moi s'effondrera et prendra fin. La conscience de cette possibilité ultime de ne-plus-être confère à ma liberté une intensité particulière et une responsabilité accablante. Je dois choisir ma vie, créer mon essence, décider de mon être, sachant qu'une fin certaine viendra mettre un terme à ce processus<ref>Kierkegaard, ''Le Concept d'angoisse'', p. 113-120</ref>.
Pour Kierkegaard, l'angoisse peut mener à deux chemins opposés : ou bien elle inspire le despair — la démission face à la liberté, le refuge dans des certitudes factices — ou bien elle devient le catalyseur d'une existence authentique fondée sur la foi et la passion<ref>Kierkegaard, ''Étapes de la vie'', traduction de Jean-J. Gateau, Éditions Gallimard, 1943, p. 340-360</ref>.
=== 3.4 Albert Camus et la révolte face à l'absurde ===
Albert Camus, bien qu'il rejette l'étiquette d'« existentialiste » (notamment en raison de ses désaccords avec Sartre), offre une réflexion existentielle majeure sur la mort envisagée comme source de l'absurde. Dans son essai ''Le Mythe de Sisyphe'' (1942), Camus énonce une thèse frappante : « Il n'y a qu'un seul problème philosophique véritablement sérieux : c'est le suicide »<ref>Albert Camus, ''Le Mythe de Sisyphe'', Gallimard, 1942, p. 13-18</ref>.
Cette affirmation peut sembler extrême, mais elle exprime une vérité existentielle profonde. Car si l'existence demeure absurde — c'est-à-dire si elle se confronte à une contradiction insurmontable entre nos exigences rationnelles et un univers qui demeure fondamentalement irrationnel — alors à quoi bon vivre ? Si nos vies sont destinées au néant, si tous nos projets, nos aspirations, nos créations finiront dans l'oubli et le silence de la mort, quel sens peuvent-ils posséder<ref>Camus, ''Le Mythe de Sisyphe'', p. 25-45</ref> ?
Camus identifie trois réponses possibles à ce problème. Première réponse : le suicide physique, qui consiste à céder face au sentiment d'absurdité en mettant fin à sa propre vie. Seconde réponse : le « suicide philosophique », qui consiste à s'échapper dans la foi religieuse ou dans une philosophie qui promettent de l'absolu — que ce soit Dieu, l'Idée, la Raison. Ces deux options, selon Camus, échouent : le suicide physique annule simplement la possibilité même d'existence ; le suicide philosophique constitue une forme de déshonnêteté envers la réalité<ref>Camus, ''Le Mythe de Sisyphe'', p. 47-80</ref>.
Mais existe une troisième voie : la révolte. Camus propose d'« imaginer Sisyphe heureux ». Sisyphe, cet être mythologique condamné à repousser éternellement un rocher qui retombe toujours, devient pour Camus le symbole de l'absurde héros. Il ne se suicide point ; il ne s'échappe point non plus dans l'illusion d'une signification cosmique. Au lieu de cela, il accepte pleinement l'absurdité de sa condition — il reconnaît que ses efforts demeurent vains, que le rocher retombera toujours — mais il persiste néanmoins. Il accepte la lutte, non en espérant qu'elle produise un résultat final satisfaisant, mais pour la simple raison que vivre, persister, lutter constituent la réalité de l'existence<ref>Camus, ''Le Mythe de Sisyphe'', p. 168-180</ref>.
Cette position camussienne face à la mort diffère significativement de celle de Heidegger. Pour Camus, il ne s'agit pas de se projeter authentiquement vers la mort pour donner une structure à l'existence. Au contraire, il s'agit de reconnaître clairement le caractère brut et inintelligible de la mort, et pourtant de continuer à vivre avec passion, solidarité et lucidité. La mort demeure un scandale, une injustice même, mais cette injustice ne nous autorise point à cesser d'exister<ref>Camus, ''Le Mythe de Sisyphe'', p. 120-140</ref>.
=== 3.5 Karl Jaspers et les « situations limites » ===
Karl Jaspers, philosophe allemand qui a influencé à la fois Heidegger et Sartre, apporte une perspective distincte sur la mort en la situant dans le contexte plus large de ce qu'il appelle les « situations limites » (Grenzsituationen). Ces situations constituent des moments où les limites de la compréhension rationnelle se heurtent à l'inévitable : la mort, la souffrance, la culpabilité, le hasard<ref>Karl Jaspers, ''Introduction à la pensée philosophique'', traduction d'Yvon Belaval, Éditions 10/18, 1970, p. 40-65</ref>.
Jaspers établit une distinction importante entre deux significations de la mort. D'abord, la mort demeure un fait objectif — la cessation des fonctions vitales, une réalité biologique. Ensuite, la mort constitue une situation limite existentielle — une expérience que je dois affronter personnellement, qui me concerne d'une manière unique et irremplaçable<ref>Jaspers, ''Introduction à la pensée philosophique'', p. 45-50</ref>.
C'est précisément cette deuxième dimension qui revêt une importance existentielle décisive. Confronté à sa propre finitude, à la conscience de sa propre mort, l'être humain peut soit se perdre davantage dans l'inauthenticité, soit accéder à un niveau plus profond d'existence que Jaspers appelle ''Existenz'' — une forme d'auto-conscience critique et de transcendance<ref>Jaspers, ''Introduction à la pensée philosophique'', p. 55-65</ref>.
Pour Jaspers, c'est justement dans l'expérience authentique des situations limites que l'individu accède à ce qu'il appelle la « transcendance ». Cette transcendance ne signifie point un dépassement métaphysique vers un autre monde, mais plutôt une conscience aiguë de la « Totalité » englobante au sein de laquelle mon existence particulière trouve son sens relatif<ref>Jaspers, ''Introduction à la pensée philosophique'', p. 65-80</ref>.
=== 3.6 Simone de Beauvoir : mort, liberté et éthique de l'ambiguïté ===
Simone de Beauvoir offre une perspective existentialiste qui intègre les questions de genre, de liberté concrète et de responsabilité éthique. Dans son ouvrage ''Éthique de l'ambiguïté'' (1947) et son monumental ''Le Deuxième Sexe'' (1949), Beauvoir affirme que la liberté demeure la valeur normative fondamentale de toute éthique existentielle<ref>Simone de Beauvoir, ''Éthique de l'ambiguïté'', Éditions Gallimard, 1947, p. 9-30</ref>.
Comment la mort s'inscrit-elle dans cette éthique de la liberté ? Beauvoir soutient que la mort, loin de constituer une structure ontologique donnant sens à l'existence comme chez Heidegger, représente plutôt une limite arbitraire imposée du dehors. Mais cette limite n'anéantit pas la liberté ; elle la redéfinit. La liberté consiste à créer du sens, à poursuivre des projets, à s'engager dans la construction d'un monde plus juste — tout en sachant que cette liberté demeure finie, que cette création de sens sera finalement interrompue<ref>Beauvoir, ''Éthique de l'ambiguïté'', p. 40-60</ref>.
Ce qui distingue Beauvoir des autres existentialistes, c'est son insistance sur le fait que cette liberté ne demeure pas abstraite. Elle demeure concrète, incarnée, et inégalement distribuée parmi les êtres humains. Les femmes, les opprimés, les marginalisés se voient souvent refuser l'accès aux formes d'existence que les existentialistes masculins envisagent comme naturelles ou universelles<ref>Simone de Beauvoir, ''Le Deuxième Sexe'', tome I, Éditions Gallimard, 1949, p. 13-32</ref>.
Beauvoir affirme que « l'on ne naît pas femme, on le devient » — formule qui exprime en microcosme sa pensée : aucune essence féminine fixe n'existe, aucune nature donnée une fois pour toutes. Au lieu de cela, la féminité demeure une condition historiquement construite et socialement imposée. Mais puisque cette condition ne s'inscrit pas dans la nature, elle peut être transformée par une action libre et consciente<ref>Beauvoir, ''Le Deuxième Sexe'', tome I, p. 13-18</ref>.
Quant à la mort, Beauvoir en reconnaît le caractère terrible et absurde. Mais elle refuse de permettre que cette absurdité justifie l'oppression, la domination ou le refus de reconnaître la liberté d'autrui. Au contraire, c'est précisément en raison de l'inévitabilité de la mort que nous devons créer de la solidarité, de la justice, de la fraternité pendant que nous vivons. La mort impose une urgence éthique à nos actions<ref>Beauvoir, ''Éthique de l'ambiguïté'', p. 115-145</ref>.
=== 3.7 Les thèmes transversaux de l'existentialisme face à la mort ===
Malgré les différences substantielles entre ces penseurs, certains thèmes existentialistes récurrents concernant la mort et la finitude émergent avec netteté :
'''L'authenticité contre l'inauthenticité :''' Tous les existentialistes s'accordent sur l'idée qu'existe une distinction fondamentale entre une existence authentique et une existence inauthentique. L'inauthenticité consiste généralement à fuir la responsabilité, à se réfugier dans les rôles sociaux prédéfinis, à nier la nécessité de créer sa propre essence. La mort, par sa certitude et son caractère inévitable, pose à chaque individu la question de savoir s'il vivra authentiquement ou s'il continuera de fuir<ref>Heidegger, ''Sein und Zeit'', sections 38-41, p. 214-225</ref>.
'''L'angoisse existentielle :''' L'angoisse ne constitue point une pathologie à traiter mais une révélation existentielle salutaire. Elle exprime la conscience de la liberté radicale, la réalité de la contingence humaine, et la responsabilité absolue face à l'absence de fondement stable. C'est précisément dans l'expérience de l'angoisse que l'existant accède à la vérité de sa condition<ref>Kierkegaard, ''Le Concept d'angoisse'', p. 61-70</ref>.
'''La singularité et l'irremplaçabilité :''' Ma mort demeure véritablement mienne. Elle ne peut être confiée à personne d'autre, elle ne peut être vécue qu'en première personne. Cette singularité absolue me constitue en tant qu'individu unique et irremplaçable. Elle fonde une forme d'égalité originelle : devant la mort, tous les êtres humains se trouvent dans une situation métaphysiquement identique<ref>Camus, ''Le Mythe de Sisyphe'', p. 23-25</ref>.
'''L'absence de fondement justificatif :''' L'univers ne fournit aucune justification à l'existence. Il n'existe pas de « pourquoi » cosmique qui expliquerait notre présence dans le monde. Cette absence de fondement définit précisément la condition absurde, et c'est cette absence qui confère une responsabilité absolue : si rien ne justifie notre existence de l'extérieur, nous devons créer cette justification de l'intérieur, par nos actions et nos choix<ref>Sartre, ''L'Être et le Néant'', p. 76-90</ref>.
En conclusion, l'approche existentialiste de la mort refuse de la traiter comme un simple problème technique ou biologique. Elle la conçoit plutôt comme le moment de révélation par excellence où la vérité de l'existence humaine — sa liberté, sa responsabilité, son absurdité, sa dignité — se manifeste avec la plus grande clarté. Loin de constituer une réflexion morbide, la philosophie existentialiste de la mort invite à vivre plus authentiquement, plus lucidement, plus librement.
== IV. Dimensions éthiques et axiologiques ==
=== 4.1 L'autonomie et le « droit de mourir » ===
La question du rapport entre autonomie et mort soulève des enjeux éthiques majeurs. La notion d'autonomie — entendue comme la capacité et le droit de chaque individu à orienter sa propre existence en accord avec ses valeurs — s'est imposée au cœur de la bioéthique contemporaine, notamment dans les débats relatifs à l'euthanasie et à l'assistance médicale à mourir<ref>Tom L. Beauchamp et James F. Childress, ''Principles of Biomedical Ethics'', Oxford University Press, 7e éd., 2013, p. 101-150</ref>.
Les partisans d'un « droit de mourir » soutiennent que le respect de l'autonomie implique de reconnaître à chacun la faculté de décider des conditions de sa fin de vie — notamment le refus de traitements prolongeant artificiellement l'existence ou, là où la loi l'autorise, la demande d'une assistance médicale pour hâter la mort<ref>Ronald Dworkin, ''Life's Dominion'', Knopf, 1993, p. 190-237</ref>. Cette position repose sur un principe fondamental : il revient à chaque personne de déterminer le sens et la valeur de sa propre vie. Il serait dès lors contraire au respect d'imposer une manière de mourir qui contredit les convictions et les préférences de cette personne<ref>Beauchamp et Childress, ''Principles of Biomedical Ethics'', p. 135-140</ref>.
Cette invocation de l'autonomie soulève néanmoins plusieurs difficultés. D'abord, le caractère réellement autonome de la demande d'aide à mourir demeure problématique : lorsqu'une personne souffre intensément, déprimée, se vit comme un fardeau pour ses proches, peut-on raisonnablement tenir sa demande pour entièrement libre de pressions, explicites ou implicites<ref>Agata Mullock, « Assisted Dying, Vulnerability, and the Potential Value of Prospective Legal Authorization », ''Medical Law Review'', 2025, p. 1-45</ref> ? Ensuite, même supposant la demande véritablement autonome, reste à déterminer si l'autonomie individuelle suffit à justifier l'intervention d'un tiers — le médecin — pour provoquer la mort, ou si d'autres considérations (protection des plus vulnérables, rôle de la médecine, répercussions sociales) doivent aussi entrer en compte<ref>Helga Kuhse et Peter Singer (dir.), ''A Companion to Bioethics'', Blackwell, 2009, p. 314-325</ref>.
Plusieurs auteurs dénoncent un « autonomisme » excessif dans la bioéthique contemporaine, c'est-à-dire une surestimation du rôle que devrait jouer l'autonomie dans les décisions de fin de vie. Ils observent que l'autonomie n'existe jamais en isolation : elle s'exerce toujours prise dans des réseaux d'inégalités, de vulnérabilités et de rapports de pouvoir<ref>Mullock, « Assisted Dying, Vulnerability… », p. 12-18</ref>. Lorsque des personnes pauvres, sans domicile fixe, gravement handicapées ou incarcérées demandent à mourir, il devient nécessaire de se demander si cette demande reflète un choix authentiquement libre, ou si elle résulte de conditions sociales si désastreuses que la société aurait pu et dû améliorer<ref>Rebecca Dresser et Peter Singer, « Euthanasia and the Question of Justice », dans ''Choices at the End of Life'', 2002, p. 82-104</ref>.
=== 4.2 Dignité, qualité de vie et valeur de la vie ===
Le concept de « mort digne » revient constamment dans les débats bioéthiques sur l'euthanasie et l'assistance médicale à mourir. Ceux qui défendent ces pratiques soutiennent qu'il importe de permettre à chacun de mourir selon sa propre conception de la dignité — que refuser cette possibilité constituerait une atteinte à la valeur fondamentale de la personne<ref>Michael Tooley, Celia Wolf-Devine et James W. Diamond (dir.), ''Physician-Assisted Suicide: Expanding the Debate'', Routledge, 1997, p. 201-232</ref>.
Mais que signifie exactement « mourir avec dignité » ? S'agit-il de conserver la maîtrise de ses décisions et de son corps jusque dans la mort ? De ne pas endurer une souffrance extrême ? Ou bien d'une valeur inconditionnelle, inhérente au seul fait d'être une personne, indépendamment de l'état physique ou mental<ref>Stephen R. Richards, « The Morality of Assisted Dying », ''Journal of Moral Philosophy'', 2025, p. 1-28</ref> ?
Une longue tradition philosophique et juridique défend cette dernière conception : la dignité humaine serait intrinsèque, inaliénable, et ne pourrait donc ni se perdre ni se gagner. Une existence marquée par la douleur, la dépendance physique ou la démence ne serait pas, en elle-même, « indigne », car la dignité ne se mesure ni à la performance physique, ni au fonctionnement autonome, ni à la lucidité mentale<ref>Lydia Dugdale, ''The Lost Art of Dying'', Harper Wave, 2020, p. 145-180</ref>.
Les partisans du droit à l'assistance au mourir rétorquent que cette approche, bien qu'elle protège contre les jugements méprisants sur la valeur des vies fragiles, risque de devenir insensible à la souffrance concrètement vécue. Ils observent aussi qu'une distinction entre dignité ontologique (égale pour tous) et dignité existentielle (comment se déploient les derniers moments d'une vie) s'impose : la manière dont s'écoulent les derniers instants influe sur le sens que l'existence entière revêt, rétrospectivement, pour la personne mourante et pour les proches<ref>Ronald Dworkin, ''Life's Dominion'', p. 187-240</ref>.
=== 4.3 Justice, équité et accès aux soins de fin de vie ===
Une dimension moins médiatisée mais politiquement décisive concerne la justice et l'équité dans l'accès aux soins de fin de vie. Tandis que les débats théoriques portent volontiers sur l'euthanasie en contextes où une offre médicale existe déjà, la réalité révèle un scandale plus fondamental : l'inégalité d'accès aux soins palliatifs, au soulagement de la douleur et à l'accompagnement des mourants<ref>Marissa T. French et alii, « Exploring Socioeconomic Inequities in Access to Palliative and End-of-Life Care in the UK », ''Palliative Medicine'', vol. 35, n° 10, 2021, p. 1843-1856</ref>.
Les personnes sans domicile, les détenus, les migrants sans statut légal et même certains patients souffrant de troubles psychiatriques graves demeurent largement écartés de dispositifs permettant un mourir apaisé, entouré, médicalement et humainement accompagné<ref>Kelli Stajduhar et alii, « Equity-Focused Palliative and End-of-Life Care in a System Lacking Equity », dans ''Palliative Care Practice'', 2024, p. 112-138</ref>. L'accès à un « bon mourir » reste un privilège socioéconomique.
Il en résulte une dissonance morale frappante : promouvoir un « droit à mourir » là où n'est pas garanti un droit effectif à bien vivre jusqu'au bout. Tant que l'accès universel à des soins palliatifs de qualité, à la prise en charge de la douleur et au soutien psychosocial n'est pas assuré, existe le risque que l'assistance médicale à mourir ne devienne une réponse aux carences sociales plutôt que l'expression d'un choix existentiel mûrement réfléchi<ref>Leon R. Kass, « 'I Will Give No Deadly Drug': Why Doctors Must Not Kill », dans ''The Case Against Physician-Assisted Suicide'', 2002, p. 17-39</ref>.
=== 4.4 Vulnérabilité, dépendance et éthique du care ===
Une éthique contemporaine de la vulnérabilité et du care rappelle que la dépendance ne constitue pas une anomalie réservée à certains groupes fragiles, mais une condition humaine universelle. Tout être humain se trouve exposé à la maladie, à l'accident, à la précarité du corps et, finalement, à la mort<ref>Martha Albertson Fineman, ''The Autonomy Myth: A Theory of Dependency'', The New Press, 2004, p. 1-30</ref>.
L'éthique du care propose de déplacer le centre de gravité : au lieu de prendre l'individu abstraitement autonome comme modèle, elle met au premier plan les relations concrètes de soin, l'interdépendance et la responsabilité mutuelle. Personne ne meurt « seul » au sens existentiel : chaque mort affecte un tissu humain de proches, interpelle une communauté, suppose un ensemble de gestes, de présences, de paroles<ref>Nel Noddings, ''Caring: A Feminine Approach to Ethics and Moral Education'', University of California Press, 2e éd., 2003, p. 142-175</ref>.
Dans cette perspective, les questions de fin de vie ne sauraient se réduire à un conflit binaire entre le « droit individuel » et les « interdits collectifs ». Il convient de tenir compte des responsabilités relationnelles, des liens de soin qu'il faut entretenir ou créer, et de ce que signifie pour une communauté d'accueillir et de soutenir ses mourants<ref>Siv Maj Jämterud, « Acknowledging Vulnerability in Ethics of Palliative Care », ''Nursing Ethics'', vol. 29, n° 2, 2022, p. 335-347</ref>. La vulnérabilité n'est pas un simple motif de protection paternaliste : elle constitue le point de départ d'une éthique de l'attention mutuelle.
=== 4.5 Finitude, signification de la vie et axiologie de la mort ===
Du point de vue axiologique, la mort ne se réduit pas à un « problème » moral qu'il faudrait encadrer. Elle joue un rôle décisif dans la valeur que peut prendre une vie. Une part importante de la philosophie contemporaine soutient que la finitude n'appauvrit pas nécessairement la signification de l'existence ; elle en constitue plutôt une condition<ref>Liudmila Baeva, ''Existential Axiology: A New Philosophical Paradigm'', Routledge, 2013, p. 1-40</ref>.
Thaddeus Metz a défendu l'idée qu'une vie sans terme imaginable, infinie et interminable, perdrait une bonne part de ce qui rend l'existence précieuse : l'urgence des choix, la nécessité de hiérarchiser les projets, la conscience que tout ne pourra pas être accompli<ref>Thaddeus Metz, « The Meaning of Life », ''Stanford Encyclopedia of Philosophy'', 2021, sections 3-4</ref>. C'est précisément la finitude qui confère à l'amitié, l'amour, le travail créateur ou la quête de justice leur poids particulier.
La reconnaissance lucide de la mortalité invite à une réorientation des priorités morales. Au lieu de rêver d'abolir la mort — ou de s'absorber exclusivement dans la maîtrise technique de ses modalités — il s'agit de se demander ce qui mérite d'être accompli dans le temps qui nous échoit : quels liens tisser, quelles œuvres entreprendre, quelle empreinte laisser<ref>Jonathan Noonan, « Mortality, Finitude, and Meaningful Lives », ''Journal of Philosophy of Life'', vol. 3, n° 1, 2013, p. 5-41</ref>.
=== 4.6 Responsabilité collective et conditions sociales du mourir ===
Enfin, la mort n'est jamais seulement affaire de choix privés. Elle demeure inséparable de structures institutionnelles, de politiques publiques et d'allocations budgétaires. La manière dont on meurt dans une société dépend de systèmes de santé, de régimes d'assurance, de normes professionnelles, mais aussi de politiques de logement, de lutte contre la pauvreté, d'accompagnement du grand âge<ref>Courtney S. Campbell, « Mortal Responsibilities: Bioethics and Medical-Assisted Dying », ''Yale Journal of Biology and Medicine'', vol. 92, n° 4, 2019, p. 733-739</ref>.
Il est révélateur que les pays ayant légalisé les formes les plus étendues d'assistance médicale à mourir (Pays-Bas, Belgique, Canada, certaines provinces australiennes) disposent tous d'un accès universel à des soins de santé de base<ref>Campbell, « Mortal Responsibilities… », p. 735-736</ref>. À l'inverse, il apparaît moralement troublant qu'une société qui ne garantit pas à tous un accès minimal aux soins puisse se prévaloir d'avoir légalisé la mort médicalement provoquée comme une avancée bioéthique majeure.
Une éthique responsable de la mort exige que l'on dépasse la seule question : « Qu'ai-je le droit de décider pour moi-même ? ». Elle impose de se demander aussi : « Quelles conditions de vie créons-nous pour les vivants et pour les mourants ? » ; « Quelles vies estimons-nous suffisamment dignes pour être soutenues, accompagnées, soignées jusqu'au bout » ? Ces interrogations collectives ne peuvent rester sans réponse<ref>Stajduhar et alii, « Equity-Focused Palliative and End-of-Life Care… », p. 125-138</ref>.
Dans cette perspective, l'enjeu ultime n'est pas d'opposer abstraitement un « droit de mourir » à un « devoir de vivre », mais de faire en sorte que chacun puisse vivre et mourir dans un cadre où l'assistance à la mort ne remplace jamais l'obligation collective de soulager la misère, la solitude et l'abandon.
== V. La mort et le sens de la vie ==
=== 5.1 L'immortalité comme malédiction : la théorie de l'ennui infini ===
L'une des questions les plus intrigantes que pose la philosophie de la mort concerne l'hypothèse contrefactuelle suivante : serait-il bon de vivre éternellement ? Au premier abord, cette question semble relever du domaine des jeux de l'esprit, mais elle révèle en réalité des enjeux profonds concernant la relation entre finitude et sens.
Bernard Williams, philosophe analytique contemporain, a défendu une position controversée : contrairement à ce que l'on pourrait attendre, vivre éternellement serait en réalité indésirable<ref>Bernard Williams, « The Makropulos Case: Reflections on the Tedium of Immortality » (1973), dans ''Problems of the Self'', Cambridge University Press, 1973, p. 82-100</ref>. Williams prend appui sur la pièce de théâtre de Karel Čapek ''La Vie de Madame Macropulos'', où l'héroïne, qui a vécu trois cents ans grâce à un élixir, découvre que la vie immortelle devient irrémediablement ennuyeuse.
L'argument de Williams procède ainsi : d'une part, ce qui donne sens à nos projets et à nos engagements, c'est notamment leur caractère temporellement limité et urgent ; d'autre part, une vie infinie serait nécessairement monotone, car il n'y aurait jamais vraiment de nouvelles expériences (après un temps suffisamment long, tout s'épuiserait et se répéterait), ou bien nous devrions constamment avoir oublié nos expériences passées pour préserver l'impression de nouveauté (ce qui reviendrait à une discontinuité de la conscience et de l'identité)<ref>Bernard Williams, « The Makropulos Case », p. 88-99</ref>.
Simone de Beauvoir avait déjà exploré cette possibilité dans ses analyses de l'ennui, affirmant que la mort, paradoxalement, peut être un bienfait en tant qu'elle introduit une limite nécessaire à l'existence<ref>Simone de Beauvoir, ''Tout compte fait'', Gallimard, 1972, p. 308-315</ref>. Cette position remet en question l'idée commune selon laquelle la mort serait un mal absolu qu'il faudrait à tout prix surmonter.
Cependant, cette théorie a été vigoureusement contestée. Certains philosophes soutiennent que Williams confond l'immortalité corporelle avec une immortalité qui permettrait l'oubli régulier, ou que sa conception de l'ennui est trop restrictive<ref>Donald Bruckner, « Against the Tedium of Immortality », dans ''Journal of Philosophy'', vol. 109, n° 11, 2012, p. 628-645</ref>. D'autres objectent que nous ne pouvons tout simplement pas savoir ce que serait une vie infinie et que par conséquent cet argument demeure hautement spéculatif.
=== 5.2 La mort comme condition de signification ===
Une approche alternative insiste sur le lien constitutif entre finitude et signification. Ce qui nous motive, ce qui nous donne direction, c'est précisément le fait que le temps nous manque, que nos jours sont comptés. Heidegger lui-même en fait un élément central de sa pensée : c'est la conscience de ma mortalité qui me permet de ''vraiment'' décider, de ''vraiment'' m'engager dans un projet, plutôt que de vivre passivement au jour le jour<ref>Heidegger, ''Sein und Zeit'', § 67-72</ref>.
De plus, la mort de l'autre donne sens à notre solidarité, à notre compassion. Si personne ne mourait, la distinction entre vraie bienveillance et simple échange utilitaire s'estomperait. Nos engagements les plus profonds envers les autres -- ceux qui nous font accepter du sacrifice, du deuil -- tirent leur sens de cette conscience partagée de la mortalité.
Cette perspective a une résonance particulière dans les traditions existentialistes et notamment dans la pensée de Paul Ricœur, qui voit dans la finitude le fondement même de ce qui rend la vie méritante de vivre : c'est une vie finie qui me demande de bien la vivre<ref>Paul Ricœur, ''Soi-même comme un autre'', Seuil, 1990, p. 453-476</ref>. Sans cette limitation, l'existence serait peut-être éternelle, mais elle risquerait de perdre cette tension, cette urgence qui la rend signifiante.
=== 5.3 Thomas Nagel et l'absurdité de la mort ===
Thomas Nagel a proposé une analyse novatrice en situant la mort parmi les sources majeures de ce qu'il appelle l'absurdité de l'existence humaine. L'absurdité naît, selon Nagel, non de la mortalité elle-même, mais du contraste entre l'importance subjective que nous conférons à nos vies et la relative insignifiance de ces vies au sein de l'univers cosmique<ref>Thomas Nagel, « The Absurd » (1971), dans ''Mortal Questions'', p. 11-32</ref>.
La mort amplifie cette absurdité en ceci que la mort apparaît à la fois comme certaine et comme inimaginable pour le sujet vivant. Nous savons que nous mourrons, mais cette certitude reste abstraite ; nous ne pouvons vraiment imaginer notre non-existence. De ce contraste surgit une forme d'absurdité existentielle qui n'est pas directement accessible à la résolution rationnelle<ref>Nagel, « The Absurd », p. 27-30</ref>.
Contrairement à Camus, qui voyait dans cette absurdité une occasion pour la révolte créative, Nagel y voit plutôt une condition inéluctable de la conscience réflexive : du moment que nous pouvons nous voir ''de l'extérieur'', que nous pouvons prendre du recul sur nos propres préoccupations, nous sommes confrontés à cette absurdité<ref>Nagel, « The Absurd », p. 31-32</ref>. La mort, dans ce contexte, est l'un des symptômes majeurs de notre condition d'êtres à la fois immergés dans la vie et capables de nous en distancier.
== VI. Phénoménologie du mourir ==
=== 6.1 Le mourir comme phénomène vécu ===
Bien que la mort elle-même, stricto sensu, échappe à toute expérience possible (puisque l'expérience présuppose la conscience et la mort sa cessation), le mourir -- le processus qui mène à la mort -- constitue une réalité vécue qui peut être objet de réflexion phénoménologique.
La phénoménologie du mourir, dans la tradition heideggérienne et post-heideggérienne, cherche à décrire comment la proximité de la mort se manifeste dans l'expérience vécue. La peur de la mort, l'angoisse devant la finitude, l'acceptation progressive ou la dénégation -- voilà autant de phénomènes psychologiques et ontologiques que la mort entraîne<ref>Heidegger, ''Sein und Zeit'', § 40</ref>.
Jan Patočka, philosophe tchèque influencé par Husserl et Heidegger, a développé une approche phénoménologique particulière où le mourir apparaît comme ce qui révèle la structure fondamentale de la vie humaine. Pour Patočka, nous vivons constamment ''dans la proximité de la mort'', et c'est cette proximité qui confère à la vie son caractère de ''drame'' inéludable<ref>Jan Patočka, ''Platon et l'Europe'', Verdier, 1983, p. 157-198</ref>.
Michel Henry a proposé une approche critique des phénoménologies heideggériennes de la mort, affirmant que l'accent mis sur l'être-vers-la-mort risque de passer sous silence l'expérience la plus profonde de la vie : la présence affective originaire (l'affectivité comme ''pathos'' primordial)<ref>Michel Henry, ''C'est moi la vérité. Pour une philosophie du christianisme'', Seuil, 1996, p. 129-145</ref>. De ce point de vue, toute tentative d'intelliger la mort par la seule réflexion existentiale demeure insuffisante.
=== 6.2 L'angoisse face à la mort ===
L'angoisse (''Angst'' en allemand) constitue un thème central de la pensée existentialiste, notamment chez Kierkegaard, Heidegger et Sartre. L'angoisse face à la mort ne doit pas être confondue avec la peur de la mort, bien que les deux soient liées. La peur se rapporte à quelque chose de particulier et déterminé ; l'angoisse, en revanche, demeure indéterminée et radicale<ref>Søren Kierkegaard, ''Le Concept d'Angoisse'' (''Begrebet Angest'', 1844), traduction de Gilles Hanot, PUF, 2007, p. 53-87</ref>.
Chez Heidegger, l'angoisse révèle l'essence de la mort comme possibilité absolue et singulière du Dasein. Dans l'angoisse, nous sommes face à face avec l'absurdité fondamentale de l'existence : que je sois plutôt que rien, c'est quelque chose qui ne se justifie pas et qui, un jour, s'arrêtera<ref>Heidegger, ''Sein und Zeit'', § 40-42</ref>.
Cette approche a des implications profondes pour la psychologie et la psychiatrie. Si l'angoisse face à la mort est constitutive de la condition humaine et non une pathologie, comment faut-il la traiter ? La psychanalyse a proposé que refouler cette angoisse (l'inconscient humain dénie systématiquement la réalité de la mort) peut mener à des symptômes névrotiques. Certains thérapeutes, influencés par les existentialistes, affirment que l'acceptation progressive de la mortalité représente un élément clé de la maturation psychologique<ref>Irvin Yalom, ''Staring at the Sun: Overcoming the Terror of Death'', Jossey-Bass, 2008, p. 47-89</ref>.
== VII. La mort dans les traditions religieuses et spirituelles ==
=== 7.1 L'au-delà et l'immortalité de l'âme ===
Historiquement, les traditions religieuses ont majoritairement affirmé que la mort n'est pas la fin absolue de la personne, mais une transition vers une autre forme d'existence (l'au-delà, le paradis, la réincarnation, le nirvana, etc.). Ces conceptions ne relèvent pas directement de la philosophie, mais elles constituent un contexte culturel et historique inévitable pour comprendre la réflexion philosophique occidentale sur la mort<ref>Mircea Eliade, ''Le Chamanisme et les Techniques archaïques de l'extase'', Payot, 1951, p. 401-437</ref>.
La question philosophique spécifique que ces traditions soulèvent concerne la possibilité conceptuelle d'une immortalité personnelle. Si l'immortalité consiste en une simple survie de l'âme (entendue comme substance immatérielle), comment l'âme peut-elle persister sans rapport au corps ? Quel serait le contenu de cette expérience immortelle ? Ces questions ont occupé les plus grands penseurs, de Platon (notamment dans le ''Phédon'') à Descartes en passant par Leibniz et Kant<ref>Platon, ''Phédon'', traduction de Monique Dixsaut, GF Flammarion, 1991, p. 88-145</ref>.
Kant, dans la critique de la raison pure, a affirmé qu'il est impossible de démontrer théoriquement l'immortalité de l'âme, mais que cette croyance peut être rationnellement justifiée au sein d'une économie pratique de la raison : nous ne pouvons progresser moralement infiniment dans une vie finie, donc il faut postuler une survie indéfinie de l'âme pour rendre compte de la possibilité du progrès moral<ref>Immanuel Kant, ''Critique de la Raison Pratique'' (1788), traduction de François Picavet, PUF, 1943, p. 154-162</ref>.
=== 7.2 La mort et le sacré ===
Bien au-delà des doctrines spécifiques d'immortalité, la mort a toujours occupé une place privilégiée dans l'expérience religieuse et spirituelle. Des rites funéraires à travers le monde aux pratiques contemplatives des traditions mystiques, la mort demeure au cœur du rapport humain au sacré.
Depuis les travaux du sociologue et anthropologue Émile Durkheim, la mort a été reconnue comme un moment privilégié de manifestation du sacré. Les rituels funéraires, bien qu'ils varient considérablement selon les cultures, partagent souvent une fonction commune : marqueur de transition, affirmation de la continuité du groupe social face à la disruption qu'introduit le décès d'un de ses membres<ref>Émile Durkheim, ''Les Formes élémentaires de la vie religieuse'' (1912), traduction de Corinne Enaudeau, Seuil, 2003, p. 325-381</ref>.
Certaines traditions spirituelles, notamment dans le bouddhisme tibétain (notamment le ''Bardo Thödol'' ou ''Livre des Morts Tibétain''), conçoivent le mourir et le moment qui suit comme une opportunité transformatrice majeure. La mort n'est pas considérée comme une fin, mais comme un passage initiatique potentiel offrant la possibilité d'une libération ou d'une meilleure réincarnation<ref>Jetsun Kalu Rinpoche, ''Le Chemin de la Lumière'', Claire Lumière, 1987</ref>.
La philosophie contemporaine, dans sa tentative de comprendre ces phénomènes sans adopter les croyances spécifiques des traditions religieuses, peut néanmoins reconnaître que le rapport de l'humanité à la mort demeure indissociable de la dimension spirituelle et rituellique de l'existence. Ignorer cet aspect serait réduire la mort à une simple question biologique, ce qui passerait sous silence une partie essentielle du phénomène<ref>Paul Tillich, ''Le Courage d'exister'', Centurion, 1967, p. 156-198</ref>.
== VIII. Questions métaphysiques et logiques ==
=== 8.1 La mort et la causalité inversée : le problème du manque ===
Un problème logique subtil s'impose à la théorie déprivationniste : comment quelque chose qui n'existe pas (une vie non-vécue) peut-il être causal dans le monde ? Si je suis privé de certains biens parce que je suis mort, comment la mort (un événement qui produit mon inexistence) peut-elle être la cause d'une privation qui m'affecte ? Cette question de la ''causalité inversée'' a occupé les métaphysiciens de la mort contemporains<ref>George Pitcher, « The Misfortune of the Dead », dans ''The American Philosophical Quarterly'', vol. 21, n° 3, 1984, p. 183-188</ref>.
Plusieurs solutions ont été proposées. Fred Feldman, par exemple, soutient que nous pouvons concevoir une relation de ''privation causale'' où la mort cause une privation sans que la mort elle-même agisse comme cause efficiente au sens traditionnel<ref>Fred Feldman, ''Confrontations with the Self'', Oxford University Press, 1986, chap. 1</ref>. D'autres, comme James Stacey Taylor, explorent une conception contrefactuelle : la mort m'est nuisible en ceci qu'elle rend faux un contrefactuel (s'il y avait continuité de vie, j'aurais expérimenté certains biens)<ref>James Stacey Taylor, ''Death, Positivism and Meaning'', Routledge, 2007, p. 42-71</ref>.
=== 8.2 La mort et le réalisme métaphysique ===
La mort soulève aussi des questions épistémologiques fondamentales concernant le statut des entités qui cessent d'exister. Un objet matériel qui se désagrège au fil du temps semble persister en tant qu'objet jusqu'à un certain degré de désagrégation. Mais à quel point devient-il plus correct de dire que l'objet n'existe plus ? De la même façon, une personne meurt progressivement : ses fonctions cognitives s'altèrent, puis ses fonctions biologiques s'arrêtent. Y a-t-il un moment précis où la personne cesse d'être, ou s'agit-il plutôt d'un processus continu<ref>Dean Zimmerman, « The A-Theory of Time and the B-Theory », dans ''Stanford Encyclopedia of Philosophy'', 2011</ref> ?
Ces questions se sont intensifiées avec les progrès biomédicaux contemporains. Les notions de mort cérébrale, de mort clinique et de possibilités de réanimation ont troublé les frontières autrefois claires entre vie et mort. Philosophiquement, ces développements nous force à reconnaître que la mort n'est pas une simple dichotomie (on vit ou on meurt), mais un spectre complexe<ref>Robert Troug, « Defining Death -- Again '', ''Hastings Center Report'', vol. 35, n° 6, 2005, p. 6</ref>.
== IX. Perspectives bioéthiques contemporaines ==
=== 9.1 Mort clinique, mort cérébrale et statut du mourant ===
Les développements des technologies de maintien artificiel de la vie ont profondément modifié le paysage éthique et philosophique de la mort. Avant l'invention des respirateurs artificiels et autres dispositifs de soutien vital, la mort était un processus relativement rapide et, dans sa plupart des cas, reconnaissable. Aujourd'hui, il est possible de maintenir certaines fonctions biologiques longtemps après que les fonctions cognitives ou toutes les fonctions biologiques aient cessé<ref>Robert Troug et James L. Bernat, « Ethical Guidelines for the Diagnosis and Management of Brain Death in Adults and Children'', ''Critical Care Medicine'', vol. 38, n° 9, 2010, p. 1962-1978</ref>.
La question de savoir qui est ''vraiment'' mort a acquis une nouvelle urgence pratique. La notion de mort cérébrale, adoptée depuis les années 1960, redéfinit la mort non plus comme arrêt du cœur et de la respiration, mais comme arrêt irréversible de toutes les fonctions du cerveau (ou du tronc cérébral, selon les juridictions)<ref>Académie américaine de Neurologie, ''Determining Brain Death in Adults'', 1995</ref>. Cette redéfinition, bien que médicalement et scientifiquement justifiée, a des implications philosophiques majeures : elle privilégie le cerveau comme siège de l'identité personnelle, ce qui représente un choix métaphysique au-delà de la simple constatation empirique.
=== 9.2 L'allongement de la vie et l'anti-vieillissement ===
La bioéthique contemporaine est occupée par un dilemme : d'un côté, la mort demeure inévitable et, en un sens, souhaitable (sans elle, l'existence deviendrait finie, monotone) ; de l'autre, les capacités technologiques contemporaines permettent d'envisager des vies considérablement plus longues, voire, dans certains scénarios futuristes, potentiellement infinies.
Aubrey de Grey et d'autres chercheurs en biogérontologie soutiennent que le vieillissement est une maladie et qu'il est en principe possible de développer des thérapies susceptibles d'arrêter ou d'inverser les processus biologiques du vieillissement<ref>Aubrey de Grey et Michael Rae, ''Ending Aging'', St. Martin's Press, 2007</ref>. Cette perspective soulève immédiatement la question : si l'on pouvait techniquement éliminer le vieillissement, devrait-on le faire ? La réponse dépend largement de la manière dont on évalue les enjeux philosophiques et éthiques que nous avons discutés plus haut (la théorie de l'ennui infini, le rôle de la mort dans la signification de la vie, etc.)<ref>Nick Bostrom, « The Immortality Question '', ''Journal of Medical Ethics'', vol. 31, n° 12, 2005, p. 682-686</ref>.
=== 9.3 L'équité d'accès et la justice distributive ===
Si l'allongement de la vie devient techniquement possible, une question majeure de justice se pose : qui aura accès à ces technologies ? L'histoire de la médecine et des technologies montre que les innovations émergent généralement dans les contextes de richesse et de privilège avant, éventuellement, de devenir plus largement accessibles. Il serait naïf de croire qu'une technologie radicale comme l'extension massive de la durée de vie serait immédiatement disponible pour tous<ref>Eric Parens (éd.), ''Shaping Our Selves: On Technology, Flourishing, and a Habit of Thinking'', Oxford University Press, 2010, p. 78-104</ref>.
Cela soulève la question de savoir si créer une situation dans laquelle certains seraient immortels ou très longévifs tandis que d'autres mourraient à un âge biologique normal serait éthiquement acceptable. Cela reposerait-il sur une conception fortement inégalitaire de la justice ? Ou, au contraire, faudrait-il considérer que chacun est libre de décider si oui ou non il souhaite bénéficier des technologies de prolongement de la vie, indépendamment du fait que d'autres n'y auraient pas accès<ref>Ronald Sandel, ''Le Tyran intérieur : La biotechnologie et nos limites'', Seuil, 2013, p. 132-156</ref> ?
== X. Mort et écriture : le mourir philosophique ==
=== 10.1 L'ineffable de la mort et les limites du langage ===
Un dernier ensemble de questions, épistémologiquement et esthétiquement pertinent, concerne la possibilité de dire la mort, d'en écrire et d'en penser. La mort semble occuper une place singulière dans le langage : elle est à la fois ce dont on parle constamment (mort naturelle, meurtre, suicide, etc.) et ce qui échappe apparemment à la pensée conceptuelle quand nous nous confrontons à notre propre finitude.
Blanchot, écrivain et philosophe français, a insisté sur ce point : la mort ne peut être pensée qu'à la limite du pensable, là où le langage frôle son ineffabilité. Pour Blanchot, il ne s'agit pas simplement de dire que la mort est au-delà des mots (ce qui serait un platonisme romantique), mais que la mort révèle les limites du langage de manière productive : c'est dans l'expérience de cette limite que survient une certaine clarté<ref>Maurice Blanchot, ''L'Entretien infini'', Gallimard, 1969, p. 123-152</ref>.
=== 10.2 Littérature et philosophie face à la mort ===
La littérature, particulièrement dans les traditions lyrique et épique, a souvent offert ce que la philosophie ne peut que pointer du doigt. Les poètes, de Gilgamesh à Baudelaire en passant par Rilke, ont exploré les dimensions existentielles, affectives et imaginaires de la mort d'une manière que les arguments philosophiques stricts ne peuvent qu'approximativement capturer.
Heidegger lui-même, malgré son effort pour ''libérer'' la mort d'une réduction technoscientifique, reconnaît que la poésie peut révéler des dimensions de la mort que la seule analyse conceptuelle ne peut atteindre. Le vers de Rilke ''Les morts reviennent'' ou le poème de Hölderlin ''Pain du jour'', ces œuvres ne ''démontrent'' rien philosophiquement, mais elles ''montrent'', elles ouvrent une expérience différente du rapport à la mort<ref>Heidegger, ''Introduction à la Métaphysique'', traduction de Gilbert Kahn, PUF, 1967, p. 147-165</ref>.
== XI. Synthèse et enjeux ouverts ==
=== 11.1 La pluralité des approches et l'impossibilité d'une réduction ===
Ce qui émerge de cet examen détaillé de la philosophie de la mort, c'est d'abord l'impossibilité d'une réduction de la mort à une seule dimension (biologique, psychologique, existentielle, éthique, métaphysique). La mort se présente plutôt comme un phénomène polymorphe qui appelle des réponses philosophiques multiples et souvent tensions entre elles.
La tension entre la théorie déprivationniste (qui insiste sur la mort comme privation de biens futurs) et l'argument épicurien (qui affirme que nous ne pouvons pas vraiment expérimenter la mort comme un mal) demeure en grande partie non résolue. Cette tension n'est pas une imperfection de la philosophie, mais révèle quelque chose de crucial : que la mort échappe à nos catégories conceptuelles habituelles.
De la même façon, la tension entre l'affirmation existentialiste selon laquelle la mort donne sens à la vie et la théorie que la mort rend la vie absurde (Camus) ne peut être ''résolue'' au sens d'une démonstration logique univoque. Ces deux positions reflètent plutôt deux aspects complémentaires et irréductibles de la condition humaine mortelle.
=== 11.2 La mort comme horizon de la pensée ===
Pour conclure, il importe de souligner que la question de la mort n'est pas une question parmi d'autres en philosophie. Elle constitue l'horizon ultime sur lequel se projette toute réflexion sur le sens, la valeur, l'authenticité et la responsabilité éthique. Aussi bien Heidegger qu'Epicure, Camus que Levinas, bien qu'ils arrivent à des conclusions différentes, s'accordent sur un point : la mort nous force à nous interroger sur ce qui compte vraiment, sur ce qui rend la vie vivable.
Dans un contexte contemporain d'accélération technologique et de transformation radicale de la condition humaine, cette réflexion éternelle sur la mort demeure aussi vitale qu'elle l'a jamais été. Comment devons-nous concevoir nos rapports à la finitude face aux promesses d'allongement de la vie ? Comment construire une éthique du deuil et du respect des morts dans un monde de plus en plus fragmenté et délocalisé ? Comment penser la solidarité humaine sans la conscience partagée de notre mortalité commune<ref>Jonathan Glover, ''Humanity: A Moral History of the Twentieth Century'', Yale University Press, 1999, p. 234-267</ref> ?
Ce sont les questions que les générations futures devront continuer à se poser, et c'est peut-être en cela que la mort, ce phénomène ultime qui nous échappe toujours, demeure la question constitutive de toute réflexion sérieuse sur ce que c'est que d'être humain.
== Notes et références ==
{{references}}
== Notions liées ==
* [[../Finitude/]]
* [[../Existence et temps/]]
{{AutoCat}}
o547nr7hv21ptt1nmqhf3bfnoxtonsz
Wikilivres:CDU/6/69/699.8
4
19311
768647
184366
2026-06-25T17:44:19Z
DavidL
1746
768647
wikitext
text/x-wiki
{{Titre simple|699.8 - Protection des bâtiments contre le feu, l'eau, l'humidité, les termites, la foudre ...}}
* [[La sécurité du citoyen dans son logement]]
[[Catégorie:Recherche CDU 69 – Industrie de la construction, matériaux de construction, méthodes et pratiques de la construction]]
[[Catégorie:Recherche CDU niveau 3]]
9uzi324fux1bpju7avlkx9di2nq0j4c
Wikilivres:CDU/6/64/643
4
19333
768648
184333
2026-06-25T17:44:43Z
DavidL
1746
768648
wikitext
text/x-wiki
{{Titre simple|643 - La maison, le domicile}}
* [[La sécurité du citoyen dans son logement]]
[[Catégorie:Recherche CDU 64 - Économie domestique, science ménagère]]
[[Catégorie:Recherche CDU niveau 3]]
h2o86b78jdbjwwm682s0vwlcu8dnaz5
Hydrostatique
0
20546
768713
279971
2026-06-26T09:30:36Z
Xhungab
23827
768713
wikitext
text/x-wiki
{{Suppression Immédiate|Elle est laissée à l'abandon depuis : 22 novembre 2007 à 20:42 Yves~frwikibooks. Sans contenu pertinent}}
Le mot [[statique]] s'utilise en sciences chaque fois que dans un domaine des sciences, ce que l'on observe et que l'on veut décrire et formuler ne varie pas dans le temps. Ainsi l'hydrostatique correspond à l'observation de liquides tels que aucune molécule n'est en mouvement.
[[Catégorie:Mécanique]]
msqdn43q7p0yrxj9euw3cihcbs55mu3
Dictionnaire de philosophie/Monothéisme
0
37432
768687
767666
2026-06-26T04:31:51Z
PandaMystique
119061
Bot : ajout du paramètre lecture=oui au modèle {{DicoPhilo}}
768687
wikitext
text/x-wiki
{{DicoPhilo|Monothéisme|lecture=oui}}
Le '''monothéisme''' est une doctrine religieuse ou [[Philosophie/Une brève introduction|philosophique]] qui affirme l'existence d'un dieu unique et transcendant (distinct du monde). Le monothéisme tend à faire de dieu un nom propre, et le terme s'écrit alors ''Dieu'', indépendamment du culte particulier ou de la conception déterminée qu'on en a. Le monothéisme s'oppose à la fois à l'[[Dictionnaire de philosophie/Athéisme|athéisme]] (il y a un dieu), au [[Philosophie/Polythéisme|polythéisme]] (ce dieu est le seul), et au [[Philosophie/Panthéisme|panthéisme]] (ce dieu est transcendant). La postériorité du monothéisme sur le polythéisme a été défendue en occident, mais n'est pas attestée historiquement. Un polythéisme peut dériver d'un monothéisme antérieur (les fonctions différentes du dieu gagnent leur indépendance et s'incarnent séparément), et un polythéisme s'orienter vers un hénothéisme qui le rapproche du monothéisme (l'écart étant toutefois maintenu). Le culte d'Aton (XIVe av. JC) mis en place par Akhenaton est considéré par certains comme le premier monothéisme, celui-ci succédant à une phase de monolâtrie (la réalité de la monolâtrie ne fait elle pas débat). Judaïsme, christianisme et Islam sont trois figures importantes du monothéisme, mais celui-ci peut correspondre à des conceptions moins religieuses (quoique dérivées des premières, voir [[Philosophie/Déisme|déisme]]).
==Bibliographie==
*{{RLivre|Dictionnaire de philosophie |auteurs= Christian Godin|annee=2004 |editeur=fayard |langue=fr }}
*{{RLivre|Dictionnaire de philosophie | soustitre= 3e édition |auteurs= Noëlla Baraquin, Anne Baudart, Jean Dugué, Jacqueline Lafitte, François Ribes, Joël Wilfert|annee=2007 |editeur=Armand Colin |langue=fr }}
*{{RLivre|La pratique de la philosophie de A à Z|auteurs= Elisabeth Clément|annee=20OO |editeur=Hatier |langue=fr }}
*{{RLivre|Nouveau vocabulaire de la philosophie et des sciences humaines|soustitre= 3e édition retirage avec corrections |auteurs= Louis-Marie Morfaux, Jean Lefranc|annee=207 |editeur=Armand Colin |langue=fr }}
{{AutoCat}}
3ewo1dghavv4g587zxao9mnqmos8dji
Dictionnaire de philosophie/Théisme
0
37434
768700
760057
2026-06-26T04:34:01Z
PandaMystique
119061
Bot : ajout du paramètre lecture=oui au modèle {{DicoPhilo}}
768700
wikitext
text/x-wiki
{{DicoPhilo|Théisme|lecture=oui}}
Le mot « théisme » vient du grec ''theos'' (« dieu »). Dans son sens le plus large, il désigne la croyance selon laquelle il existe au moins une réalité divine. En philosophie de la religion, on réserve souvent le terme à une conception plus précise : il existe un Dieu personnel, transcendant et créateur, doté d’attributs éminents (puissance, connaissance, bonté), dont dépend l’être de tout ce qui existe et qui est digne de culte.<ref>Hywel D. Lewis, ''Philosophy of Religion'', Londres, Allen & Unwin, 1976, chap. 4.</ref>
Par contraste, on appelle généralement « athéisme » la thèse selon laquelle il n’existe aucun dieu, et « non‑théisme » toute position qui ne se place pas dans un cadre théiste (certaines formes de bouddhisme, par exemple). D’autres notions proches – monothéisme, polythéisme, panthéisme, déisme – se distinguent par la manière dont elles conçoivent la ou les divinités et leur rapport au monde. Le théisme, au sens strict, se caractérise par deux idées centrales : la dépendance de l’ensemble du réel à l’égard d’une réalité divine ultime, et le caractère personnel de cette réalité (Dieu comme sujet capable de connaître, vouloir, aimer).
== 1. Définition du théisme ==
=== 1.1. Définition minimale et théisme « classique » ===
Une définition minimale du théisme consiste à dire : « Il existe au moins un être divin. » Cette définition reste très large : elle engloberait, par exemple, des formes de polythéisme, où plusieurs dieux coexistent, souvent limités et parfois en rivalité. Dans la tradition philosophique occidentale, le terme a rapidement reçu un sens plus restreint, lié au monothéisme abrahamique.
Ralph Cudworth, représentant des Platoniciens de Cambridge, caractérise Dieu comme « un être parfaitement conscient et intelligent, existant de lui‑même de toute éternité, cause de toutes les autres choses ».<ref>Ralph Cudworth, ''The True Intellectual System of the Universe'', Londres, Richard Royston, 1678, livre I, chap. IV (éd. moderne : Bristol, Thoemmes Press, 1995).</ref> Un tel Dieu n’est pas seulement supérieur aux autres êtres : il est le principe même de leur existence, leur cause et leur mesure.
Ce type de définition est emblématique de ce qu’on appelle aujourd’hui le « théisme classique » (''classical theism'') : Dieu y est conçu comme l’ultime principe de réalité, créateur ''ex nihilo'', omnipotent, omniscient et parfaitement bon, simple (sans composition interne), immuable et éternel.<ref>Brian Leftow, ''God and Necessity'', Oxford, Oxford University Press, 2012, chap. 1.</ref> Cela signifie, par exemple, que Dieu n’a pas de parties, qu’il ne change pas, qu’il n’est pas soumis au temps comme nous le sommes, qu’il connaît tout ce qui est possible et réel, et qu’il veut toujours le bien. Cette image, élaborée par la tradition philosophique et théologique, structure encore aujourd’hui une grande partie du débat.
Pour décrire le contenu variable de la notion, on distingue parfois un théisme « mince » (''thin theism'') – simple affirmation d’une intelligence supérieure à l’origine du monde, sans autre précision – et un théisme « épais » (''thick theism'') qui attribue à Dieu un ensemble riche de propriétés métaphysiques et morales (infinité, omniscience, sainteté, providence, etc.).<ref>Paul Russell, « Hume on Religion », ''Stanford Encyclopedia of Philosophy'', 1ʳᵉ éd. 2005, section 1.</ref> Le premier se contente, par exemple, de dire qu’« un esprit très puissant a organisé l’univers », alors que le second soutient qu’il existe un Dieu parfait, source de l’être, de la vérité et du bien. Cette distinction permet de mesurer l’écart entre des conceptions très abstraites de Dieu (premier principe ou première cause) et les représentations religieuses plus concrètes (Dieu qui parle, qui commande, qui pardonne).
=== 1.2. Théisme, monothéisme et croyance religieuse ===
Le théisme philosophique, tel qu’il s’est développé dans la tradition occidentale, est étroitement lié au monothéisme, c’est‑à‑dire à l’idée qu’il n’existe qu’un seul Dieu. Le monothéisme, au sens strict, ajoute au théisme une affirmation d’unicité : s’il existe un être tel que Dieu – créateur, souverain, parfait –, alors il ne peut y en avoir qu’un seul, car deux réalités tout à fait suprêmes entreraient nécessairement en concurrence.<ref>Graham Oppy, « Monotheism », ''Stanford Encyclopedia of Philosophy'', 1ʳᵉ éd. 2005, section 1.</ref> L’unicité de Dieu n’est donc pas un simple détail supplémentaire, mais une conséquence logique de sa perfection supposée.
Dans le langage courant, on identifie volontiers le théisme avec les grandes religions monothéistes (judaïsme, christianisme, islam). Pourtant, du point de vue philosophique, il est utile de distinguer le théisme comme thèse métaphysique générale et les formes concrètes de croyance religieuse. D’un côté, le théisme peut être compris comme une affirmation purement philosophique : il existe un Dieu créateur, distinct du monde, moralement parfait, dont dépend tout ce qui existe. Une telle thèse peut être défendue indépendamment de toute tradition religieuse particulière, par exemple à partir d’arguments cosmologiques ou moraux. De l’autre côté, chaque religion monothéiste ajoute à ce noyau des contenus spécifiques : pour les chrétiens, Jésus est le Fils de Dieu incarné ; pour les musulmans, Mahomet est le dernier prophète ; chaque tradition propose un corpus de récits, de rites, de lois et de doctrines propres.<ref>J.‑P. W. Doody, « Croyance religieuse (A) », ''L’Encyclopédie philosophique'', en ligne, 2016.</ref>
Ainsi, un philosophe peut être théiste sans pour autant adhérer à un credo déterminé : il peut juger qu’il est raisonnable de tenir pour vraie la thèse « il existe un Dieu créateur et bon », tout en restant agnostique sur les prétentions de telle ou telle Église à parler en son nom. Inversement, il existe des formes de religiosité qui ne sont pas théistes au sens strict (certains bouddhismes, certains cultes ancestraux), même si elles accordent une grande place au sacré.
== 2. Brève histoire de la notion ==
=== 2.1. Antécédents antiques : du polythéisme au « Dieu des philosophes » ===
Le mot « théisme » est moderne, mais l’idée qu’un principe divin ultime fonde le réel est bien plus ancienne. Dans les religions de la Grèce antique, on trouve surtout des formes de polythéisme : plusieurs dieux, dotés de pouvoirs plus ou moins étendus, exercent une influence sur le monde et les affaires humaines. Cependant, la réflexion philosophique tend progressivement à dépasser ce cadre.
Chez Platon, la ''Forme du Bien'' joue le rôle de principe suprême qui donne l’être et l’intelligibilité à toutes choses, même si ce Bien n’est pas encore conçu comme un Dieu personnel au sens plein.<ref>Platon, ''La République'', VI, 509b–511e, trad. R. Baccou, Paris, GF Flammarion, 1966.</ref> Quand Platon affirme que le Bien est « au‑delà de l’être en dignité et en puissance », il ouvre la voie à une pensée de l’Absolu qui inspirera de nombreuses conceptions théistes. Dans le ''Timée'', un ''démiurge'' (artisan divin) ordonne le cosmos selon un modèle intelligible, sans pour autant le créer ''ex nihilo'' : il façonne une matière préexistante à l’image des formes éternelles.<ref>Platon, ''Timée'', 28a‑30c, trad. L. Brisson, Paris, GF Flammarion, 1995.</ref>
Aristote décrit, dans le livre Λ de la ''Métaphysique'', un « Premier moteur immobile », acte pur de pensée se pensant elle‑même, cause finale du mouvement de l’univers.<ref>Aristote, ''Métaphysique'', Λ, 6‑10, trad. J. Tricot, Paris, Vrin, 1981.</ref> Le monde, éternel, est mis en mouvement parce qu’il désire, en quelque sorte, ressembler à ce Premier moteur parfaitement actuel et immobile. Ce Dieu aristotélicien est parfait et éternel, mais il n’est pas, à proprement parler, créateur du monde ; il en est plutôt la fin attractive et le modèle intelligible.
Les stoïciens, de leur côté, identifient le principe divin au ''logos'' immanent au monde : une raison ordonnatrice et providentielle pénètre toute réalité et la rend cohérente.<ref>A. A. Long & D. N. Sedley, ''The Hellenistic Philosophers'', vol. 1, Cambridge, Cambridge University Press, 1987, p. 52‑68.</ref> Dieu n’est pas un être séparé, mais la rationalité du cosmos lui‑même ; il est aussi feu créateur, souffle vital, loi naturelle. Cette théologie cosmique annonce certains motifs théistes (ordre, rationalité, providence), mais conçoit Dieu moins comme une personne distincte que comme la raison du monde.
Ces élaborations préparent ce qu’on appellera plus tard le « Dieu des philosophes » (expression rendue célèbre par Pascal) : un principe unique, éternel, source de l’être et du bien, que l’on cherche à atteindre par un raisonnement plutôt que par la tradition religieuse. À l’époque moderne, ce « Dieu des philosophes » sera souvent distingué du « Dieu d’Abraham, d’Isaac et de Jacob », c’est‑à‑dire du Dieu vivant de la foi.
=== 2.2. Le théisme médiéval : Dieu comme être nécessaire et créateur ===
Avec le judaïsme, le christianisme et l’islam, le monothéisme biblique rencontre la philosophie grecque. Cette rencontre produit les grands systèmes de théisme classique. Les philosophes médiévaux cherchent à penser le Dieu de la Révélation biblique avec les outils conceptuels d’Aristote, de Platon et des néoplatoniciens.
Chez Augustin, Dieu est à la fois le créateur du monde et la source de la vérité intérieure. Dans un passage célèbre des ''Confessions'', il écrit : « Tu étais plus intérieur à moi que le plus intime, et plus élevé que les sommets de moi‑même. »<ref>Augustin, ''Confessions'', X, 27, 38, trad. P. de Labriolle, Paris, Garnier‑Flammarion, 1964.</ref> L’existence de Dieu se manifeste à ses yeux aussi bien dans l’ordre du monde que dans la présence de vérités nécessaires (logiques, mathématiques, morales) qu’aucun individu ne semble pouvoir produire. Le théisme augustinien souligne ainsi que Dieu n’est pas un objet extérieur parmi d’autres, mais la condition même de la vérité et de la connaissance.
Anselme de Cantorbéry propose, dans le ''Proslogion'', la célèbre « preuve ontologique » : Dieu est « l’être tel qu’on ne peut en concevoir de plus grand » ; or, un être qui existe seulement dans l’intellect serait moins grand qu’un être qui existe à la fois dans l’intellect et dans la réalité ; il faut donc que Dieu existe en réalité.<ref>Anselme, ''Proslogion'', chap. 2‑3, trad. D. Moreau, Paris, GF Flammarion, 2010.</ref> Que cette preuve convainque ou non, elle montre bien ce qui caractérise le théisme médiéval : Dieu est pensé comme être absolument parfait, dont l’essence implique l’existence, et non comme simple cause extérieure parmi d’autres.
Thomas d’Aquin développe une théologie naturelle articulée autour de « cinq voies » qui visent à montrer, à partir de l’expérience du mouvement, de la causalité, de la contingence, des degrés de perfection ou de l’ordre du monde, qu’il existe un premier moteur, une cause première, un être nécessaire, un être souverainement parfait et une intelligence ordonnatrice.<ref>Thomas d’Aquin, ''Somme de théologie'', I, q. 2, a. 3, trad. J.‑P. Torrell, Paris, Cerf, 1984.</ref> Chez lui, Dieu est ''Acte pur'', ''ipsum esse subsistens'' (l’être même subsistant) dont toute créature reçoit l’existence. Le théisme thomiste insiste à la fois sur la transcendance de Dieu et sur sa proximité, en tant qu’il « est plus intimement présent à chaque chose qu’elle ne l’est à elle‑même ».
Dans la philosophie islamique, Avicenne (Ibn Sīnā) distingue les êtres possibles, qui reçoivent l’existence d’un autre, et l’« Être nécessaire par soi » (''wājib al‑wujūd bi‑dhātihi'') qui existe par soi et dont dépend la totalité des êtres possibles.<ref>Avicenne, ''Livre des Directives et Remarques (Al‑Ishârât wa‑t‑tanbîhât)'', trad. A.‑M. Goichon, Paris, Vrin, 1951, troisième partie.</ref> Maïmonide, dans le ''Guide des égarés'', insiste sur l’unité et la simplicité divines, au point de privilégier une théologie dite « négative » : il vaut mieux dire ce que Dieu n’est pas (il n’est pas corps, il n’est pas limité, il n’est pas sujet au changement) que prétendre décrire positivement son essence.<ref>Maïmonide, ''Le Guide des égarés'', I, 50‑60, trad. S. Munk, Paris, Vrin, 1958.</ref>
Jean Duns Scot propose une démonstration élaborée de l’existence d’un premier être infini, doté de toutes les perfections, qui « précède par nature » tout ce qui est causé, et développe une conception originale de l’univocité de l’être : le mot « être » s’applique à Dieu et aux créatures dans un sens fondamentalement commun, ce qui permet selon lui de construire une métaphysique plus rigoureuse de l’essence divine.<ref>Jean Duns Scot, ''Questions sur la Métaphysique'', trad. O. Boulnois, Paris, Vrin, 1997, introd.</ref>
Dans ces systèmes, le théisme n’est pas seulement un article de foi : il structure toute l’architecture philosophique. Il organise la métaphysique (hiérarchie des êtres, distinction puissance/acte, contingent/nécessaire), la théorie de la connaissance (dépendance de la vérité à l’intellect divin) et l’éthique (Dieu comme fin ultime de l’agir humain et bien suprême auquel tend la volonté).
=== 2.3. L’époque moderne : déisme, théisme philosophique et critiques sceptiques ===
À l’époque moderne, le développement des sciences, les guerres de religion et les débats sur la Révélation conduisent à reformuler le théisme. Le Dieu des philosophes tend à se séparer du Dieu des Églises, et de nouvelles positions apparaissent.
Descartes affirme l’existence d’un Dieu infini, parfait, créateur, connu par l’idée claire et distincte d’un être souverainement parfait dont l’existence est impliquée par sa nature.<ref>René Descartes, ''Méditations métaphysiques'', III et V, trad. J.‑M. Beyssade, Paris, GF Flammarion, 1992.</ref> Dans certains textes, il suggère que Dieu est en un sens la cause des vérités éternelles, thèse qui nourrit une conception particulièrement forte de la souveraineté divine et qui a suscité de vifs débats ultérieurs.<ref>René Descartes, ''Réponses aux sixièmes objections'', in ''Œuvres philosophiques'', t. II, éd. F. Alquié, Paris, Garnier, 1967, p. 837‑840.</ref> Leibniz défend un Dieu parfaitement bon et sage, choisissant le « meilleur des mondes possibles » parmi une infinité de mondes concevables, de sorte que le mal lui‑même trouve une place dans une harmonie d’ensemble que Dieu seul embrasse pleinement.<ref>G. W. Leibniz, ''Essais de Théodicée'', I, §8‑10, éd. et trad. J.‑Br. Schneewind, Paris, Garnier, 1969.</ref>
Parallèlement, un courant ''déiste'' émerge, notamment en Angleterre et en France. Le déisme admet l’existence d’un Dieu créateur, accessible à la raison naturelle, mais refuse l’idée de révélation surnaturelle, de miracles, de providence particulière. Dieu y apparaît comme l’architecte initial d’un monde qui fonctionne ensuite selon ses propres lois, sans interventions ultérieures. Voltaire, par exemple, illustre ce type d’attitude : il se moque des superstitions religieuses, critique l’intolérance ecclésiastique, mais estime qu’une raison droite conduit naturellement à l’idée d’un Être suprême.<ref>Voltaire, ''Traité sur la tolérance'', Paris, Flammarion, 1964, chap. 5.</ref> Le déisme reproche ainsi aux formes historiques de théisme religieux d’être entachées de superstition ou de fanatisme, tout en considérant que la négation pure et simple de Dieu (athéisme) serait déraisonnable ou dangereuse.
On parle parfois aujourd’hui de « théisme philosophique » pour désigner une croyance en Dieu élaborée par la seule raison (par exemple, à partir de l’ordre du monde ou de la moralité), indépendamment de toute appartenance confessionnelle. Il s’agit alors d’une thèse métaphysique générale sur la réalité ultime, et non d’une doctrine religieuse déterminée : le théisme philosophique affirme qu’il existe un être ultime d’ordre personnel, créateur et bon, mais ne prend pas position sur les dogmes spécifiques d’une tradition.<ref>Brian Davies, ''An Introduction to the Philosophy of Religion'', 3ᵉ éd., Oxford, Oxford University Press, 2004, chap. 1.</ref>
Les Lumières voient également se développer des critiques sceptiques du théisme. David Hume, dans les ''Dialogues sur la religion naturelle'', met en cause l’analogie qui fonde l’argument du dessein : le monde ne ressemble pas assez à une machine pour que l’on puisse conclure avec assurance à un architecte intelligent, et le mal observable dans le monde rend peu plausible l’hypothèse d’un dieu parfaitement bon.<ref>David Hume, ''Dialogues sur la religion naturelle'', trad. M. Malherbe, Paris, Vrin, 1994, dixième dialogue.</ref> Dans ''L’Histoire naturelle de la religion'', il propose une généalogie naturaliste des croyances, faisant dériver le théisme de passions humaines (peur, espoir) et d’habitudes d’explication anthropomorphiques.<ref>David Hume, ''The Natural History of Religion'', éd. J. C. A. Gaskin, Oxford, Oxford University Press, 1993.</ref>
Kant, enfin, soutient que les preuves traditionnelles de l’existence de Dieu (ontologique, cosmologique, physico‑théologique) dépassent les limites légitimes de la raison spéculative : l’idée de Dieu est une Idée de la raison, nécessaire pour unifier notre connaissance, mais elle ne suffit pas à démontrer l’existence d’un être réel.<ref>Immanuel Kant, ''Critique de la raison pure'', Dialectique transcendantale, trad. A. Tremesaygues et B. Pacaud, Paris, PUF, 1944.</ref> Dans la ''Critique de la raison pratique'', l’existence de Dieu est postulée comme condition de possibilité du « souverain bien » (accord entre vertu et bonheur) : le théisme devient ainsi une exigence de la raison pratique plutôt qu’une thèse démontrable théoriquement.<ref>Immanuel Kant, ''Critique de la raison pratique'', trad. F. Alquié, Paris, Vrin, 1946, 2ᵉ partie, chap. 2.</ref>
=== 2.4. Critiques modernes du théisme : soupçon et naturalisme ===
Au XIXᵉ et au XXᵉ siècle, plusieurs courants mettent en question non seulement la vérité, mais aussi la fonction du théisme. La réflexion ne se limite plus à la question « Dieu existe‑t‑il ? » ; elle s’interroge aussi sur ce que signifie psychologiquement et socialement le fait de croire en Dieu.
Feuerbach interprète Dieu comme projection de l’essence humaine idéalisée : selon lui, l’homme attribue à un être transcendant ses propres qualités les plus hautes (raison, amour, justice) et se dépossède ainsi de sa propre grandeur. « La conscience de Dieu est la conscience que l’homme a de sa propre essence », écrit‑il.<ref>Ludwig Feuerbach, ''L’Essence du christianisme'', trad. J.‑P. Osier, Paris, Maspero, 1968, p. 55.</ref> Marx reprend et transforme cette analyse en insistant sur les déterminations sociales : la religion (et, partant, le théisme) serait une réponse illusoire à une situation réelle de détresse, qui détourne l’homme de la transformation effective du monde et de la suppression des conditions sociales qui engendrent cette détresse.<ref>Karl Marx, « Contribution à la critique de la philosophie du droit de Hegel », in ''Œuvres philosophiques'', Paris, Gallimard, ''Bibliothèque de la Pléiade'', 1982.</ref>
Nietzsche interprète la croyance en Dieu comme expression d’un certain ''ressentiment'' à l’égard de la vie : la morale chrétienne, en particulier, valoriserait la faiblesse, l’humilité, la souffrance, au détriment de l’affirmation de la puissance vitale. L’annonce de la « mort de Dieu » n’est pas pour lui un événement « métaphysique », mais le constat que les valeurs fondées sur le théisme ont perdu leur force dans la culture européenne moderne, ce qui oblige à repenser les fondements de la valeur.<ref>Friedrich Nietzsche, ''Le Gai Savoir'', §125, trad. P. Wotling, Paris, GF Flammarion, 2007.</ref> Freud, de son côté, analyse la représentation d’un Dieu‑père comme expression d’un désir infantile de protection et de justice : la figure divine serait le prolongement imaginaire du père humain, transposé à l’échelle cosmique.<ref>Sigmund Freud, ''L’Avenir d’une illusion'', trad. M. Bonaparte, Paris, PUF, 1949.</ref>
Ces critiques ne se contentent pas de contester les arguments du théisme : elles en proposent des interprétations psychologiques, sociales ou historiques qui visent à en « expliquer » l’apparition indépendamment de sa vérité. Le débat philosophique contemporain sur le théisme doit donc se confronter à ces lectures de soupçon, qui invitent à se demander si, et dans quelle mesure, une croyance peut être à la fois « explicable » et vraie.
=== 2.5. Le théisme dans la philosophie analytique contemporaine ===
À partir du milieu du XXᵉ siècle, la philosophie analytique de la religion connaît un renouveau marqué du débat sur le théisme. Dans le monde anglophone surtout, un grand nombre d’ouvrages et d’articles discutent à nouveaux frais les arguments classiques et en proposent de nouveaux. Alvin Plantinga, Richard Swinburne, William Alston, John Hick, entre autres, élaborent des versions sophistiquées du théisme et des arguments en sa faveur.
Plantinga soutient qu’il peut être rationnel de croire en Dieu sans s’appuyer sur des « preuves » au sens classique : la croyance en Dieu peut être « correctement basique » (''properly basic''), produite par des facultés cognitives fonctionnant correctement dans un environnement approprié.<ref>Alvin Plantinga, ''Warranted Christian Belief'', Oxford, Oxford University Press, 2000, chap. 6‑10.</ref> Sa position, parfois appelée « épistémologie réformée », conteste l’idée selon laquelle toute croyance rationnelle devrait être dérivée d’autres croyances au moyen d’arguments. Swinburne, quant à lui, défend l’idée que le théisme classique forme une hypothèse simple et unifiante, capable d’expliquer aussi bien l’existence de l’univers que l’ordre des lois de la nature, la conscience et certaines expériences religieuses, et qu’il est globalement confirmé par l’ensemble des données disponibles si l’on raisonne en termes de probabilité bayésienne.<ref>Richard Swinburne, ''The Existence of God'', 2ᵉ éd., Oxford, Oxford University Press, 2004.</ref>
En face, des philosophes comme J. L. Mackie, Graham Oppy ou J. L. Schellenberg critiquent la cohérence des attributs divins, contestent la force explicative du théisme et soulignent le poids des arguments tirés du mal ou de la « dissimulation » de Dieu (''divine hiddenness''). Mackie, par exemple, soutient que les arguments traditionnels en faveur du théisme sont entachés de failles logiques, et que le problème du mal constitue une objection redoutable à l’idée d’un Dieu parfaitement bon et tout‑puissant.<ref>J. L. Mackie, ''The Miracle of Theism'', Oxford, Oxford University Press, 1982.</ref> Schellenberg, lui, met en forme systématique l’argument de la dissimulation de Dieu, que l’on examinera plus loin.<ref>J. L. Schellenberg, ''Divine Hiddenness and Human Reason'', Ithaca, Cornell University Press, 1993.</ref>
Parallèlement, des formes particulières de théisme sont discutées : le théisme « ouvert » (Dieu ne connaît pas exhaustivement l’avenir contingent), le théisme « processuel » (Dieu évolue avec le monde), le théisme sceptique (nous devons être très réservés sur notre capacité à connaître les raisons de Dieu, notamment face au problème du mal).<ref>William Hasker, ''God, Time, and Knowledge'', Ithaca, Cornell University Press, 1989.</ref><ref>Charles Hartshorne, ''Man’s Vision of God and the Logic of Theism'', Hamden, Archon Books, 1941.</ref><ref>Michael Bergmann, « Skeptical Theism and the Problem of Evil », in M. Peterson & R. VanArragon (dir.), ''Contemporary Debates in Philosophy of Religion'', Oxford, Blackwell, 2004.</ref> Ces débats montrent que le théisme n’est pas une position figée : il se reconfigure au contact des objections, des découvertes scientifiques et des développements conceptuels.
== 3. Variantes et notions voisines ==
=== 3.1. Monothéisme, polythéisme, panthéisme, panenthéisme ===
Le monothéisme désigne la conviction qu’il n’existe qu’un seul Dieu, créateur et souverain. C’est dans ce cadre que s’est principalement développé le théisme philosophique occidental. La plupart des définitions « classiques » de Dieu (être nécessaire, parfait, simple, etc.) présupposent cette unicité : il ne peut y avoir deux réalités absolument suprêmes, comme il ne peut y avoir deux cercles au même endroit qui seraient à la fois distincts et parfaitement identiques.
Le polythéisme, à l’inverse, affirme l’existence de plusieurs dieux, souvent limités dans leurs pouvoirs et leurs domaines d’action, parfois en conflit. On peut être « théiste » au sens très large (croire à des dieux) sans être monothéiste. Cependant, la réflexion philosophique sur le théisme vise en général des conceptions plus unifiées de la divinité : l’idée qu’il existe un seul principe ultime paraît plus apte à jouer le rôle de fondement de l’être et de la valeur que celle d’un panthéon de puissances partielles.
Le panthéisme identifie Dieu au tout de la nature : ''Deus sive Natura'', comme l’écrit Spinoza.<ref>Spinoza, ''Éthique'', I, prop. 14, trad. B. Pautrat, Paris, Seuil, 1999.</ref> Dieu n’est plus une personne distincte du monde, mais l’ensemble infini des modes d’être. Le théisme classique s’en démarque en affirmant fortement la transcendance : Dieu n’est pas le monde, même s’il est présent à tout ce qui existe. Enfin, le panenthéisme soutient que le monde est « en » Dieu, mais que Dieu dépasse le monde : le cosmos est comme le corps de Dieu, mais la réalité divine excède toujours ce qu’il contient. Cette position, qu’on trouve dans certains courants de la théologie processuelle, cherche à concilier l’immanence (Dieu présent au monde) et la transcendance (Dieu plus grand que le monde).
Ces notions voisines permettent de situer le théisme : il n’est ni l’affirmation brute de dieux multiples, ni la pure identification de Dieu au tout, ni la simple reconnaissance d’une « force » anonyme, mais l’affirmation d’un Dieu personnel, unique, distinct du monde, qui le crée et le gouverne.
=== 3.2. Déisme et théisme philosophique ===
Le déisme peut être vu comme une forme atténuée de théisme. Il affirme qu’il existe un Dieu qui a créé le monde et en est la cause première, mais il nie ou met entre parenthèses la possibilité d’une révélation surnaturelle, de miracles, d’interventions providentielles particulières. Dieu, pour le déiste, n’est pas absent au sens d’inexistant : il est comme un grand horloger qui a construit une machine parfaite et la laisse fonctionner d’elle‑même. La relation entre Dieu et le monde est alors conçue de façon très générale : Dieu garantit l’ordre des lois de la nature, mais ne modifie pas à la demande le cours des événements.
Le théisme, au sens plus strict, affirme au contraire l’implication continue de Dieu dans le monde. Le Dieu du théisme classique n’est pas seulement cause première : il soutient à tout instant l’existence des créatures, il connaît et veut chaque chose, il exerce une providence, il peut – selon les traditions – répondre aux prières, accomplir des miracles, se révéler. J. C. A. Gaskin, commentant Hume, propose de distinguer clairement ces deux positions : le théisme soutient qu’il existe un Dieu unique et tout‑puissant, créateur et fondement ultime de tout ce qui est, qui demeure présent et agissant dans sa création (providence, miracles, révélation). Le déisme, lui, maintient l’existence et le rôle créateur de Dieu, mais rejette l’idée d’une action particulière de Dieu dans l’histoire : Dieu ne fait rien d’autre que créer et maintenir l’ordre général.<ref>J. C. A. Gaskin, Introduction à David Hume, ''The Natural History of Religion'' and ''Dialogues Concerning Natural Religion'', Oxford, Oxford University Press, 1993.</ref>
Le « théisme philosophique » contemporain, enfin, désigne la manière dont certains philosophes défendent l’idée de Dieu en s’appuyant exclusivement sur des arguments rationnels (cosmologiques, moraux, expérientiels), sans présupposer la vérité d’une tradition révélée particulière. Un tel théisme peut par exemple conclure qu’il est raisonnable d’affirmer l’existence d’un Dieu créateur et bon, sans se prononcer sur la Trinité, sur l’Incarnation ou sur la validité de tel texte sacré.
=== 3.3. Théisme classique, théisme ouvert, théisme processuel, théisme sceptique ===
Le théisme classique insiste, on l’a vu, sur les attributs d’éternité, d’immutabilité, de simplicité, d’impassibilité. Dieu ne change pas, ne souffre pas, ne dépend de rien.<ref>Eleonore Stump & Norman Kretzmann, « Eternity », ''Journal of Philosophy'', 78(8), 1981, p. 429‑458.</ref> Il est hors du temps, ou bien il embrasse en un seul regard toute l’histoire du monde. Cette conception a longtemps dominé la théologie chrétienne, influencée par l’héritage aristotélicien et néoplatonicien ; elle pose toutefois des questions délicates : comment concilier, par exemple, l’immutabilité divine avec l’idée d’une relation vivante entre Dieu et les créatures ?
Le théisme ouvert (''open theism'') propose une réponse originale à certains de ces problèmes. Ses partisans soutiennent que Dieu a créé des agents libres dont les choix futurs ne sont pas encore des vérités déterminées. La connaissance de Dieu porte sur toutes les possibilités, sur tous les états de choses qui pourraient se produire, mais les libres décisions futures n’existent pas encore comme des faits fixés ; il n’y a donc rien, strictement parlant, que Dieu pourrait connaître à leur sujet.<ref>Clark Pinnock et al., ''The Openness of God. A Biblical Challenge to the Traditional Understanding of God'', Downers Grove, InterVarsity Press, 1994.</ref> Cette position tente de préserver à la fois la liberté humaine et la responsabilité de Dieu : si Dieu décide de créer un monde où de vraies libertés existent, il accepte de ne pas tout contrôler à l’avance.
Le théisme processuel, inspiré par Whitehead et Hartshorne, va plus loin en soutenant que Dieu lui‑même est en devenir, affecté par le monde, et ne possède pas une omnipotence coercitive mais une puissance de persuasion.<ref>Alfred N. Whitehead, ''Process and Reality'', corr. éd. D. R. Griffin & D. W. Sherburne, New York, Free Press, 1978.</ref> Dieu serait alors l’instance qui, à chaque instant, propose aux créatures des possibilités de bien plus élevées, sans jamais les écraser. Il n’imposerait pas le cours de l’histoire, mais l’orienterait patiemment vers plus de complexité et de valeur. Cette vision remet en cause certains traits du théisme classique (immutabilité, impassibilité), mais elle cherche à sauvegarder l’intuition d’un Dieu profondément impliqué dans le devenir du monde.
Enfin, le théisme sceptique (''skeptical theism'') n’est pas tant une variante métaphysique qu’une thèse épistémologique. Il affirme que notre ignorance des raisons de Dieu est si grande qu’il est illégitime d’inférer de l’existence de maux « incompréhensibles » à la non‑existence de Dieu.<ref>Michael Bergmann, « Skeptical Theism and Rowe’s New Evidential Argument from Evil », ''Nous'', 35(2), 2001, p. 278‑296.</ref> Le théisme sceptique accepte donc le portrait classique de Dieu, mais conteste que nous ayons le point de vue adéquat pour évaluer la compatibilité entre ce Dieu et la distribution concrète des biens et des maux dans le monde.
Ces variantes illustrent la souplesse du théisme : on peut maintenir une affirmation centrale (il existe un Dieu personnel créateur) tout en modifiant de façon substantielle la manière de comprendre ses attributs et son rapport au monde. La frontière entre « théisme » et « autres conceptions de Dieu » n’est pas toujours nette : certaines versions du panenthéisme ou du théisme processuel, par exemple, sont parfois classées à la marge du théisme classique.
== 4. Arguments en faveur du théisme ==
Les arguments pour le théisme sont nombreux et très débattus. Certains se présentent comme des « preuves » au sens fort, d’autres comme des considérations de probabilité ou de plausibilité. L’objectif, dans ce qui suit, n’est pas de trancher leur validité, mais de comprendre leurs structures principales.
=== 4.1. Arguments cosmologiques et du « premier principe » ===
Les arguments cosmologiques partent, de manière générale, du fait qu’il existe quelque chose plutôt que rien, ou du caractère contingent du monde, pour conclure à l’existence d’un premier principe nécessaire. Leur intuition commune est que le monde tel que nous le connaissons n’a pas en lui‑même sa raison suffisante : il pourrait ne pas exister, ou exister autrement ; il appelle donc une explication qui dépasse la chaîne des causes finies.
Dans les formes ''kalām'' de l’argument, influencées par des théologiens islamiques médiévaux, le raisonnement commence par la prémisse : « tout ce qui commence d’exister a une cause ». Si l’on ajoute que l’univers a commencé d’exister, que ce soit pour des raisons philosophiques (impossibilité d’une série infinie d’événements passés) ou scientifiques (Big Bang), on conclut que l’univers a une cause qui, elle, ne commence pas d’exister. Cette cause doit alors être, en dernière analyse, un être personnel puissant et libre, capable de choisir de créer.<ref>William Lane Craig, ''The Kalām Cosmological Argument'', Londres, Macmillan, 1979.</ref>
Dans les versions thomistes, inspirées de Thomas d’Aquin, l’accent n’est pas mis sur un commencement temporel, mais sur la dépendance actuelle de tout être contingent à l’égard de l’être. Même si le monde avait existé de toute éternité, soutient Thomas, il n’en serait pas moins contingent : rien en lui n’exige qu’il existe plutôt que rien. Il faut donc remonter à un être dont l’essence est d’exister, un être nécessaire qui soit la source continuée de l’existence de tout ce qui est.<ref>Thomas d’Aquin, ''Somme de théologie'', I, q. 44, a. 1‑2.</ref>
Les formes leibniziennes de l’argument mobilisent le principe de raison suffisante : pour tout fait contingent, il y a une explication. La totalité du monde est elle‑même contingente ; elle doit donc avoir une raison suffisante en dehors d’elle‑même, qui ne soit pas contingente mais nécessaire (Dieu).<ref>G. W. Leibniz, « Principes de la nature et de la grâce fondés en raison », §7‑8, in ''Œuvres philosophiques'', Paris, Garnier, 1969.</ref> L’argument ne repose pas ici sur une impossibilité d’une série infinie cause‑effet, mais sur l’idée qu’une série infinie de faits contingents, même si elle n’avait ni commencement ni fin, n’expliquerait jamais pourquoi cette série existe plutôt qu’aucune.
Ces arguments ont fait l’objet de nombreuses critiques : certains contestent la portée du principe de raison suffisante, d’autres trouvent problématique de parler d’« être nécessaire ». Les débats contemporains portent aussi sur la question de savoir si la physique moderne (quantique, cosmologie) confirme ou affaiblit l’idée qu’un « commencement du monde » a besoin d’une cause transcendante.
=== 4.2. Arguments du dessein et du « réglage fin » ===
Les arguments téléologiques (du dessein) mettent en avant l’ordre, la complexité et l’ajustement fin du monde et de la vie. Depuis l’Antiquité, beaucoup ont été frappés par la régularité des mouvements célestes, la délicate organisation des organismes vivants, la manière dont divers éléments de l’environnement semblent « faits » les uns pour les autres. Chez les stoïciens et chez Cicéron, la régularité des cieux et l’adaptation des organismes à leur environnement sont interprétées comme des indices d’une providence rationnelle qui a ordonné toutes choses en vue d’une fin.<ref>Cicéron, ''De natura deorum'', II, trad. A. Yon, Paris, Les Belles Lettres, 1963.</ref>
Dans la version contemporaine dite du « réglage fin » (''fine‑tuning''), on insiste sur la sensibilité de certaines constantes physiques : de petits changements dans les constantes fondamentales (masse des particules, intensité des forces, densité initiale de l’univers, etc.) rendraient impossible l’apparition d’une vie complexe. Le fait que notre univers ait précisément les valeurs qui permettent l’émergence de la vie consciente semble, à certains auteurs, plus probable si un concepteur intelligent a choisi les paramètres que s’il s’agit d’un pur hasard.<ref>Robin Collins, « The Teleological Argument », in W. L. Craig & J. P. Moreland (dir.), ''The Blackwell Companion to Natural Theology'', Oxford, Blackwell, 2009.</ref> Le théisme offrirait alors une explication unifiée de cet ajustement : Dieu aurait voulu un univers où des créatures libres et conscientes puissent exister et l’atteindre.
Les critiques recopient toutefois que l’hypothèse d’un « multivers » – une immense variété d’univers aux constantes différentes – pourrait rendre le fait que l’un d’eux soit compatible avec la vie beaucoup moins étonnant. D’autres invoquent le principe anthropique : nous ne pouvons de toute façon observer qu’un univers compatible avec notre existence, de sorte que l’impression de « réglage fin » serait au moins en partie un effet de sélection. Reste la question, proprement philosophique, de savoir si l’on doit préférer une explication « théiste » ou « multivers » de ce réglage fin, ou s’il est raisonnable de suspendre le jugement.
=== 4.3. Argument ontologique ===
L’argument ontologique, initié par Anselme, a connu de nombreuses reformulations (chez Descartes, Leibniz, et, au XXᵉ siècle, chez Plantinga). Sa structure est singulière : au lieu de partir du monde ou de l’expérience, il part de l’idée même de Dieu. Dans une variante modale contemporaine, Plantinga suggère qu’on peut raisonner ainsi : il est possible (logiquement) qu’il existe un être maximalement grand (omniscient, omnipotent, parfaitement bon). Or, si un tel être est possible, alors il existe dans au moins un monde possible. Mais, en vertu de sa grandeur maximale, s’il existe dans un monde possible, il existe dans tous les mondes possibles, donc aussi dans le monde réel. Il s’ensuit qu’un être maximalement grand, c’est‑à‑dire Dieu, existe.<ref>Alvin Plantinga, ''The Nature of Necessity'', Oxford, Oxford University Press, 1974, chap. 10.</ref>
L’argument est fascinant par son ambition : il prétend montrer, uniquement à partir de la notion même de Dieu, que Dieu existe nécessairement. Il a cependant suscité de nombreuses objections. Beaucoup contestent la prémisse modale – « il est possible qu’un tel être existe » – qu’ils jugent loin d’être évidente. D’autres proposent des « parodies » : si l’on accepte ce type de raisonnement, ne pourrait‑on pas prétendre démontrer l’existence de n’importe quel être « parfait » inventé pour l’occasion ? Les défenseurs de l’argument répondent en affinant la notion de « grandeur maximale » et en montrant qu’elle ne fonctionne pas comme les concepts utilisés dans les parodies. Quoi qu’il en soit, l’argument ontologique continue à jouer un rôle important dans la réflexion sur le théisme, ne serait‑ce que comme occasion de préciser ce que l’on entend par nécessité, possibilité et perfection.
=== 4.4. Arguments moraux et arguments de la valeur ===
Les arguments moraux partent de l’existence (ou de l’objectivité supposée) de certaines valeurs et obligations morales. L’idée directrice est la suivante : si des normes morales valent de manière objective et contraignante, il faut qu’elles soient, en quelque manière, enracinées dans une réalité ultime qui dépasse les préférences humaines. Un Dieu parfaitement bon, dont la volonté et la nature sont la source du bien, pourrait remplir ce rôle.<ref>Robert M. Adams, ''Finite and Infinite Goods. A Framework for Ethics'', Oxford, Oxford University Press, 1999.</ref> Pour Robert Adams, par exemple, les biens finis trouvent leur mesure dans le bien infini qui est Dieu ; les obligations morales fondamentales expriment ainsi une relation à un Dieu aimant et saint.
Une autre stratégie consiste à s’intéresser à la structure de l’expérience morale elle‑même : nous nous sentons parfois liés par des exigences qui semblent s’adresser à nous comme si elles venaient de « quelque chose » ou de « quelqu’un » de plus grand que nous. Pour certains théistes, cette phénoménologie de l’obligation s’explique mieux si l’on suppose qu’il existe effectivement un Législateur moral divin qu’à partir de simples faits naturels.
D’autres arguments, plus axiologiques qu’éthiques, se demandent si l’hypothèse théiste rend le monde globalement meilleur (plus sensé, plus juste) que l’hypothèse athée. Un courant récent, parfois appelé « pro‑théisme », soutient qu’il serait préférable, toutes choses égales par ailleurs, que Dieu existe plutôt qu’il n’existe pas, parce qu’alors la justice ultime, le pardon parfait, l’accomplissement de la connaissance et de l’amour seraient possibles.<ref>Guy Kahane, « Should We Want God to Exist ? », ''Philosophy and Phenomenological Research'', 82(3), 2011, p. 674‑696.</ref> Cette ligne de réflexion ne démontre pas directement l’existence de Dieu, mais elle invite à réfléchir sur la forme de vie que l’on désire et sur l’image du monde que l’on juge souhaitable.
Ces arguments sont vivement discutés. Certains naturalistes estiment que des valeurs morales objectives peuvent exister sans Dieu (réalisme moral non théiste), ou que l’objectivité morale est une illusion liée à notre psychologie évoluée. D’autres font remarquer que la présence d’atroces injustices non réparées, de vies brisées sans consolation visible, complique singulièrement l’idée d’un Dieu comme garant ultime de la valeur.
=== 4.5. Argument de l’expérience religieuse ===
Beaucoup de croyants disent « faire l’expérience » de Dieu : sentiment de présence, transformation intérieure, perception d’un appel moral ou d’un pardon, certitude soudainement éprouvée que la réalité a un sens ultime. Un argument en faveur du théisme consiste à dire que, si une large part de l’humanité, dans des contextes variés, rapporte de telles expériences, il est rationnel de leur accorder, au moins ''prima facie'', un certain poids épistémique – de même qu’on fait confiance en général à ses sens, sauf raison particulière de s’en défier.<ref>William Alston, ''Perceiving God. The Epistemology of Religious Experience'', Ithaca, Cornell University Press, 1991.</ref>
William Alston, par exemple, propose d’analyser certaines expériences mystiques ou dévotionnelles comme des formes de « perception de Dieu ». Si celles‑ci surviennent de manière régulière, chez des personnes globalement fiables, dans des conditions analogues, il serait arbitraire de les disqualifier en bloc sans bonne raison, alors même que nous acceptons sans preuve indépendante le témoignage de nos sens corporels. Le théisme reçoit alors un soutien indirect : il n’est pas démontré, mais il apparaît comme une interprétation raisonnable d’un type d’expérience largement attesté.
Les critiques rétorquent que la diversité et parfois la contradiction des expériences (êtres surnaturels multiples, réalités impersonnelles, expérience de « néant » mystique) plaident plutôt en faveur d’une explication psychologique ou culturelle. Les états de conscience en cause – exaltation, sentiment océanique, impression de dissolution du moi – pourraient être expliqués en termes de neurophysiologie, de pratiques méditatives, de contagion émotionnelle, sans qu’il soit nécessaire de postuler un objet divin. De plus, une expérience ne s’interprète pas d’elle‑même : qu’un sujet dise « j’ai rencontré Dieu » dépend aussi de son langage, de ses attentes, de sa tradition religieuse. L’argument de l’expérience religieuse soulève donc des questions épistémologiques complexes sur le statut des vécus intérieurs.
== 5. Objections majeures au théisme ==
=== 5.1. Le problème du mal ===
Le problème du mal est sans doute l’objection la plus ancienne et la plus persistante au théisme. Dans sa forme logique, il s’énonce de manière tranchée : il est contradictoire d’affirmer en même temps qu’il existe un Dieu tout‑puissant, omniscient et parfaitement bon, et qu’il existe du mal. Car un Dieu omniscient connaîtrait tous les maux, un Dieu tout‑puissant pourrait les empêcher, et un Dieu parfaitement bon voudrait les empêcher. Si un tel Dieu existait, conclut‑on, il n’y aurait donc pas de mal ; or il y en a.
La version contemporaine la plus discutée est la version « évidentielle », plus modeste. Elle ne prétend plus montrer une contradiction pure et simple, mais une forte improbabilité : l’ampleur, la distribution et surtout la gratuité apparente de certains maux (souffrances atroces et inutiles, injustices flagrantes, catastrophes aveugles touchant des innocents) rendent très improbable l’existence d’un Dieu tel que le théisme le décrit.<ref>William L. Rowe, « The Problem of Evil and Some Varieties of Atheism », ''American Philosophical Quarterly'', 16(4), 1979, p. 335‑341.</ref> L’exemple, devenu classique, d’un faon brûlé vif dans une forêt, sans que personne n’en tire aucun bien, illustre cette intuition.
Les théistes ont répondu de plusieurs manières. Les « théodicées » (dont la plus célèbre est celle de Leibniz) cherchent à montrer que le mal est le prix à payer pour un monde globalement meilleur : ainsi, la liberté humaine est considérée comme un bien si précieux qu’il peut justifier, au moins en partie, la permission de certains maux qui en découlent ; de même, la stabilité des lois de la nature permet un ordre intelligible, mais au prix de catastrophes naturelles qui n’épargnent pas les innocents.<ref>G. W. Leibniz, ''Essais de Théodicée'', II, §21‑25.</ref> Les « défenses » du libre arbitre, comme celle de Plantinga, adoptent une approche plus modeste : il s’agit de montrer qu’il est logiquement possible qu’un Dieu parfaitement bon ait de bonnes raisons de permettre le mal, par exemple parce qu’il ne peut créer des créatures véritablement libres et, en même temps, garantir qu’elles ne pécheront jamais.<ref>Alvin Plantinga, ''God, Freedom, and Evil'', Grand Rapids, Eerdmans, 1974.</ref>
Enfin, le théisme sceptique insiste sur les limites de notre connaissance : du fait de notre position limitée dans le temps et l’espace, nous ne sommes pas bien placés pour juger de l’absence de raisons suffisantes que Dieu pourrait avoir de permettre certains maux. Il serait donc téméraire de conclure, à partir du seul fait que nous ne voyons aucune bonne raison à tel mal, qu’il n’en existe effectivement aucune. Cette réponse, toutefois, soulève à son tour des questions : si nos capacités de jugement sont si faibles, peut‑on encore prétendre connaître positivement quoi que ce soit de la bonté ou de la sagesse de Dieu ?
Le problème du mal illustre jusqu’où la discussion sur le théisme engage à la fois des considérations logiques (compatibilité de propositions), morales (poids accordé aux souffrances concrètes) et existentielles (sens de la confiance ou de la révolte face au réel).
=== 5.2. L’argument du Dieu caché ===
L'argument de la dissimulation divine (''divine hiddenness'') part d’un fait énigmatique : si un Dieu personnel, bon et aimant existait et désirait une relation avec les créatures capables de le connaître, il est difficile de comprendre pourquoi tant de personnes « non résistantes » (qui ne s’opposent pas par principe à Dieu, qui seraient prêtes à croire si elles avaient de bonnes raisons) demeurent sans croyance théiste – parfois après une recherche sincère et douloureuse.<ref>J. L. Schellenberg, ''Divine Hiddenness and Human Reason'', Ithaca, Cornell University Press, 1993.</ref> L’athée Schellenberg soutient que cette « absence de croyance non fautive » constitue une donnée qui rend incompatible, ou du moins très improbable, l’existence d’un Dieu désireux d’une relation personnelle avec toutes ses créatures.
Les réponses théistes sont diverses. Certains insistent sur le rôle de la liberté et de la maturation morale : un Dieu qui se manifesterait de manière écrasante, évidente, annihilerait en partie la liberté de croire ou de ne pas croire, de s’engager ou de se détourner. La dissimulation de Dieu ferait alors partie d’une pédagogie divine : elle laisserait place à une recherche, à une purification des motivations, à un cheminement intérieur. D’autres soulignent que Dieu pourrait viser autre chose que la seule croyance explicite : ce qui importerait pour lui serait moins l’adhésion intellectuelle à une proposition (« Dieu existe ») que la forme de vie effectivement menée, la manière dont on pratique la justice, la compassion, la vérité. Dans cette perspective, un agnostique sincère et moralement droit pourrait être plus proche de Dieu qu’un croyant superficiel. Enfin, certains invoquent une dimension eschatologique : la « rencontre » décisive avec Dieu ne serait pas promise à cette vie‑ci, mais à une autre vie, où la dissimulation actuelle serait levée.
Le débat sur la dissimulation de Dieu touche à la fois la psychologie humaine (qu’est‑ce qu’une « non‑résistance » réelle ? quelles sont nos véritables motivations ?) et l’image que le théisme se fait de Dieu (souhaite‑t‑il avant tout être cru, être aimé, être obéi, ou autre chose encore ?). Il oblige aussi à préciser ce que l’on entend par « relation personnelle » entre Dieu et une créature.
=== 5.3. Problèmes de cohérence des attributs divins ===
Le théisme classique attribue à Dieu une série de propriétés qui semblent parfois difficilement conciliables. Dieu est dit omnipotent, omniscient, parfaitement bon, simple, immuable, éternel hors du temps, etc. Plusieurs objections sont nées de cette accumulation d’attributs.
On évoque souvent le « paradoxe de l’omnipotence » : un Dieu tout‑puissant peut‑il créer une pierre qu’il ne peut pas soulever ? Si oui, il est une fois la pierre créée quelque chose qu’il ne peut pas faire ; si non, il y a déjà quelque chose qu’il ne peut pas faire (créer une telle pierre). La formulation est naïve, mais elle signale une difficulté plus générale : comment définir l’omnipotence sans tomber dans des contradictions logiques ? La réponse classique consiste à dire que Dieu peut tout ce qui est logiquement possible, mais pas ce qui est intrinsèquement contradictoire (un « cercle carré », par exemple).
Autre source de tension : la compatibilité entre omniscience et liberté humaine. Si Dieu sait à l’avance, avec certitude, ce que je ferai demain, puis‑je faire autrement ? Ne suis‑je pas, d’une certaine manière, enfermé dans un avenir déjà connu et fixé ? Diverses solutions ont été proposées : certains affirment que la connaissance divine est « hors du temps » et ne fait donc pas partie des causes qui déterminent mon choix ; d’autres soutiennent que la connaissance divine dépend, d’une façon mystérieuse, de ce que feront librement les créatures (Dieu sait ce que je ferai parce que je le ferai librement, et non l’inverse).<ref>Karel Lambert & Bas C. van Fraassen, « Relativistic Theories of Divine Omniscience », ''Philosophical Studies'', 29(2), 1976, p. 181‑190.</ref>
On évoque aussi la tension entre omniscience et immutabilité : si Dieu connaît des vérités temporelles changeantes (par exemple, « nous sommes en 2026 »), son savoir ne change‑t‑il pas lui aussi ? Et, si son savoir change, Dieu n’est‑il pas soumis au devenir ? Les défenseurs du théisme classique répondent en distinguant différents modes de connaissance (Dieu connaîtrait les choses dans une éternelle présence, non par succession), ou en refusant que les « vérités temporelles » fassent partie de la connaissance proprement dite que Dieu a de lui‑même et de sa création.
Face à ces difficultés, certains théistes choisissent de modifier le portrait divin (c’est le cas du théisme ouvert ou processuel), tandis que d’autres maintiennent le cadre classique en le complétant par une série de distinctions subtiles. Les débats sur les attributs divins montrent que le théisme n’est pas une notion monolithique, mais un champ où différentes conceptions de Dieu rivalisent.
=== 5.4. Pluralité des monothéismes ===
Plus récemment, certains philosophes ont souligné que, même si l’on admet des arguments en faveur d’un monothéisme général (il existe un Dieu transcendant), il subsiste une multitude de modèles possibles de ce Dieu. Un dieu limité, un dieu indifférent, un dieu malveillant, un dieu impersonnel, un dieu qui commence d’exister avec le monde, sont, en principe, des possibilités logiques. Le théisme « classique » – Dieu omnipotent, omniscient, parfaitement bon – n’est alors qu’une option parmi d’autres, difficile à privilégier par la seule raison.<ref>Raphael Lataster, « The Problem of Alternative Monotheisms », ''European Journal for Philosophy of Religion'', 10(1), 2018, p. 1‑23.</ref>
Cette pluralité des « monothéismes possibles » affaiblirait, selon certains, la portée des arguments pro‑théistes. Même si ceux‑ci rendaient plausible l’existence d’un être divin quelconque, ils ne permettraient pas de conclure aisément qu’il s’agit du Dieu décrit par le théisme abrahamique classique. Il faudrait alors des arguments supplémentaires, tirés peut‑être de la Révélation ou de l’histoire religieuse, pour passer d’un monothéisme philosophique abstrait à une conception déterminée de Dieu.
Le théisme, en tant que thèse philosophique, se trouve ainsi placé entre deux exigences : d’un côté, proposer une conception suffisamment riche de Dieu pour être intéressante (et pas seulement « il existe quelque chose »), de l’autre, rester assez générale pour ne pas se confondre avec une théologie confessionnelle particulière.
== 6. Le statut philosophique du théisme ==
Le théisme n’est ni une simple donnée culturelle, que l’on enregistrerait comme un fait sociologique parmi d’autres, ni une hypothèse scientifique formulée et testée dans un cadre expérimental. Il se présente plutôt comme une thèse métaphysique globale sur l’ultime structure de la réalité. À ce titre, il interagit avec plusieurs domaines de la philosophie.
En métaphysique, le théisme soulève des questions sur l’être nécessaire, la causalité, le temps, la liberté. Affirmer qu’il existe un Dieu créateur, c’est prendre position sur la manière dont le monde dépend d’autre chose que de lui‑même, sur la distinction entre contingent et nécessaire, sur l’existence d’un sens ultime du réel. La notion de création ''ex nihilo'', par exemple, modifie profondément la compréhension de la relation entre Dieu et le monde par rapport aux conceptions antiques d’un cosmos éternel.
En épistémologie, le théisme interroge la rationalité de la croyance religieuse : peut‑on croire raisonnablement en Dieu sans preuves décisives ? La foi est‑elle un mode spécifique de connaissance ou une simple attitude pratique ? Quel rôle donner à l’expérience intérieure, au témoignage, aux traditions religieuses, dans la formation de la croyance théiste ? Ces questions sont au cœur de l’épistémologie réformée et de la philosophie analytique de la religion.
En éthique, le théisme intervient dans la réflexion sur le fondement des valeurs et des normes. Si Dieu existe et qu’il est bon, les commandements moraux peuvent être compris comme l’expression d’une sagesse divine, et la vie morale comme une réponse à un appel. S’il n’existe pas, il faut penser autrement l’objectivité éventuelle des valeurs (par un réalisme moral non théiste, par exemple), ou renoncer à cette objectivité. Le théisme pose ainsi la question : les notions de devoir, de dignité, de mérite conservent‑elles la même portée selon que l’on admet ou non une référence à Dieu ?
En philosophie du langage et en philosophie de la religion, enfin, le théisme suscite l’examen des conditions de sens du discours sur un être transcendant. Thomas d’Aquin, par exemple, propose une théorie du langage analogique : lorsque l’on dit que Dieu est « bon » ou « sage », on n’emploie pas ces termes au sens exactement identique à celui qu’ils ont pour les créatures, ni dans un sens totalement équivoque, mais de manière analogique, en s’appuyant sur une certaine ressemblance entre Dieu et ce qu’il a créé.<ref>Thomas d’Aquin, ''Somme de théologie'', I, q. 13 (sur le langage analogique appliqué à Dieu).</ref> D’autres, comme Maïmonide, insistent davantage sur la théologie négative : nous pouvons dire ce que Dieu n’est pas, plutôt que ce qu’il est.
Pour certains auteurs, le théisme fournit un cadre explicatif unifié et structurant, permettant de penser à la fois la contingence du monde, l’orientation morale, l’expérience du sacré et l’espoir d’une justice ultime. Pour d’autres, il multiplie inutilement les entités, répond à des désirs de consolation ou d’ordre, et se trouve supplanté par des approches naturalistes de la réalité, qui cherchent à expliquer le monde, la conscience, la morale, la religion elle‑même, sans recourir à une réalité divine.
== Références ==
{{references}}
== Bibliographie ==
=== Ouvrages généraux de philosophie de la religion ===
* Adams, Robert M., ''Finite and Infinite Goods. A Framework for Ethics'', Oxford, Oxford University Press, 1999.
* Davies, Brian, ''An Introduction to the Philosophy of Religion'', 3ᵉ éd., Oxford, Oxford University Press, 2004.
* Oppy, Graham, ''Arguing About Gods'', Cambridge, Cambridge University Press, 2006.
* Quinn, Philip L. & Taliaferro, Charles (dir.), ''A Companion to Philosophy of Religion'', 2ᵉ éd., Oxford, Blackwell, 2010.
* Taliaferro, Charles, ''Philosophy of Religion. A Beginner’s Guide'', Oxford, Oneworld, 2009.
=== Sources classiques théistes (Antiquité et Moyen Âge) ===
* Aristote, ''Métaphysique'', trad. J. Tricot, Paris, Vrin, 1981.
* Augustin, ''Confessions'', trad. P. de Labriolle, Paris, Garnier‑Flammarion, 1964.
* Avicenne, ''Livre des Directives et Remarques (Al‑Ishârât wa‑t‑tanbîhât)'', trad. A.‑M. Goichon, Paris, Vrin, 1951.
* Cicéron, ''De natura deorum'', trad. A. Yon, Paris, Les Belles Lettres, 1963.
* Duns Scot, Jean, ''Questions sur la Métaphysique'', trad. O. Boulnois, Paris, Vrin, 1997.
* Maïmonide, ''Le Guide des égarés'', trad. S. Munk, Paris, Vrin, 1958.
* Platon, ''La République'', trad. R. Baccou, Paris, GF Flammarion, 1966.
* Platon, ''Timée'', trad. L. Brisson, Paris, GF Flammarion, 1995.
* Spinoza, ''Éthique'', trad. B. Pautrat, Paris, Seuil, 1999.
* Thomas d’Aquin, ''Somme de théologie'', trad. J.‑P. Torrell et al., Paris, Cerf, à partir de 1984.
=== Théisme classique et époque moderne ===
* Cudworth, Ralph, ''The True Intellectual System of the Universe'', Londres, Richard Royston, 1678 (rééd. Bristol, Thoemmes Press, 1995).
* Descartes, René, ''Méditations métaphysiques'', trad. J.‑M. Beyssade, Paris, GF Flammarion, 1992.
* Descartes, René, ''Œuvres philosophiques'', éd. F. Alquié, 3 vol., Paris, Garnier, 1963‑1973.
* Leibniz, G. W., ''Essais de Théodicée sur la bonté de Dieu, la liberté de l’homme et l’origine du mal'', éd. et trad. J.‑Br. Schneewind, Paris, Garnier, 1969.
* Voltaire, ''Traité sur la tolérance'', Paris, Flammarion, 1964.
=== Critiques modernes du théisme (XIXᵉ–XXᵉ siècles) ===
* Feuerbach, Ludwig, ''L’Essence du christianisme'', trad. J.‑P. Osier, Paris, Maspero, 1968.
* Freud, Sigmund, ''L’Avenir d’une illusion'', trad. M. Bonaparte, Paris, PUF, 1949.
* Hume, David, ''Dialogues sur la religion naturelle'', trad. M. Malherbe, Paris, Vrin, 1994.
* Hume, David, ''The Natural History of Religion'', éd. J. C. A. Gaskin, Oxford, Oxford University Press, 1993.
* Kant, Immanuel, ''Critique de la raison pure'', trad. A. Tremesaygues et B. Pacaud, Paris, PUF, 1944.
* Kant, Immanuel, ''Critique de la raison pratique'', trad. F. Alquié, Paris, Vrin, 1946.
* Marx, Karl, ''Œuvres philosophiques'', Paris, Gallimard, coll. « Bibliothèque de la Pléiade », 1982.
* Nietzsche, Friedrich, ''Le Gai Savoir'', trad. P. Wotling, Paris, GF Flammarion, 2007.
=== Philosophie analytique contemporaine du théisme ===
* Alston, William, ''Perceiving God. The Epistemology of Religious Experience'', Ithaca, Cornell University Press, 1991.
* Bergmann, Michael, « Skeptical Theism and Rowe’s New Evidential Argument from Evil », ''Nous'', 35(2), 2001, p. 278‑296.
* Craig, William Lane, ''The Kalām Cosmological Argument'', Londres, Macmillan, 1979.
* Hasker, William, ''God, Time, and Knowledge'', Ithaca, Cornell University Press, 1989.
* Mackie, J. L., ''The Miracle of Theism. Arguments for and against the Existence of God'', Oxford, Oxford University Press, 1982.
* Plantinga, Alvin, ''The Nature of Necessity'', Oxford, Oxford University Press, 1974.
* Plantinga, Alvin, ''God, Freedom, and Evil'', Grand Rapids, Eerdmans, 1974.
* Plantinga, Alvin, ''Warranted Christian Belief'', Oxford, Oxford University Press, 2000.
* Schellenberg, J. L., ''Divine Hiddenness and Human Reason'', Ithaca, Cornell University Press, 1993.
* Stump, Eleonore & Kretzmann, Norman, « Eternity », ''Journal of Philosophy'', 78(8), 1981, p. 429‑458.
* Swinburne, Richard, ''The Existence of God'', 2ᵉ éd., Oxford, Oxford University Press, 2004.
=== Variantes du théisme et approches « processuelles » ===
* Hartshorne, Charles, ''Man’s Vision of God and the Logic of Theism'', Hamden, Archon Books, 1941.
* Pinnock, Clark et al., ''The Openness of God. A Biblical Challenge to the Traditional Understanding of God'', Downers Grove, InterVarsity Press, 1994.
* Whitehead, Alfred N., ''Process and Reality'', corr. éd. D. R. Griffin & D. W. Sherburne, New York, Free Press, 1978.
=== Problème du mal, dissimulation de Dieu et objections récentes ===
* Kahane, Guy, « Should We Want God to Exist ? », ''Philosophy and Phenomenological Research'', 82(3), 2011, p. 674‑696.
* Lataster, Raphael, « The Problem of Alternative Monotheisms », ''European Journal for Philosophy of Religion'', 10(1), 2018, p. 1‑23.
* Rowe, William L., « The Problem of Evil and Some Varieties of Atheism », ''American Philosophical Quarterly'', 16(4), 1979, p. 335‑341.
=== Articles et ressources de référence (encyclopédies) ===
* Oppy, Graham, « Monotheism », ''Stanford Encyclopedia of Philosophy'', 1ʳᵉ éd. 2005, révisée 2013.
* Peterson, Michael & VanArragon, Raymond (dir.), ''Contemporary Debates in Philosophy of Religion'', Oxford, Blackwell, 2004.
* Russell, Paul, « Hume on Religion », ''Stanford Encyclopedia of Philosophy'', 1ʳᵉ éd. 2005.
* ''L’Encyclopédie philosophique'', articles « Dieu », « Croyance religieuse », en ligne : <nowiki>https://encyclo-philo.fr</nowiki>.
== Voir aussi ==
*[[Athéisme]]
*[[Agnosticisme]]
*[[Déisme]]
*[[Panthéisme]]
*[[Monothéisme]]
*[[Philosophie de la religion]]
{{Autocat}}
58f7g1xjku23dzf08la1rz5yr6gjnnj
Dictionnaire de philosophie/Panthéisme
0
37438
768690
753992
2026-06-26T04:32:21Z
PandaMystique
119061
Bot : ajout du paramètre lecture=oui au modèle {{DicoPhilo}}
768690
wikitext
text/x-wiki
{{DicoPhilo|Panthéisme|lecture=oui}}
Doctrine philosophique selon laquelle tout ce qui existe est en Dieu ou Dieu est le tout, la divinité et la nature étant confondue. Contre le [[Philosophie/Monothéisme|monothéisme]], le panthéisme pense un dieu immanent, ni extérieur ni supérieur au monde. Ni créateur, ni personnel, le dieu panthéique tend à s'identifier avec la nature (Spinoza, ''Éthique'', IV, Préface). Si on peut reconnaître des formes ou des tendances panthéistes dans le stoïcisme, le néo-platonisme et dans certaines religions (hindouisme, mais aussi chrétienté), la théorie de Spinoza s'est imposée comme archétype pour penser le panthéisme. Faire de celui-ci un athéisme ou un matérialisme caché est un contre-sens polémique sur la pensée de Spinoza ; contre-sens plus ou moins volontairement commis au XVIIIe par les adversaires et les défenseurs du monothéisme traditionnel. Plus une philosophie qu'une religion, le panthéisme pointe une difficulté du monothéisme : individuer Dieu est le limiter (fût-ce pour lui donner une infinité d'attributs), et le séparer du monde supprime son infinité. On distingue un panthéisme naturaliste (ou matérialiste), qui identifie Dieu au monde (Diderot, d'Holbach) et un panthéisme qui identifie le monde à Dieu (lecture classique mais contestable de Spinoza). La première version étant accusée à juste titre de n'être qu'un athéisme déguisée.
==Bibliographie==
*{{RLivre|Dictionnaire de philosophie |auteurs= Christian Godin|annee=2004 |editeur=fayard |langue=fr }}
*{{RLivre|Dictionnaire de philosophie | soustitre= 3e édition |auteurs= Noëlla Baraquin, Anne Baudart, Jean Dugué, Jacqueline Lafitte, François Ribes, Joël Wilfert|annee=2007 |editeur=Armand Colin |langue=fr }}
*{{RLivre|La pratique de la philosophie de A à Z|auteurs= Elisabeth Clément|annee=20OO |editeur=Hatier |langue=fr }}
*{{RLivre|Nouveau vocabulaire de la philosophie et des sciences humaines|soustitre= 3e édition retirage avec corrections |auteurs= Louis-Marie Morfaux, Jean Lefranc|annee=207 |editeur=Armand Colin |langue=fr }}
{{AutoCat}}
orumbjmaznjt8rs9t554qvwv9t5bwnf
Dictionnaire de philosophie/Polythéisme
0
37442
768694
754252
2026-06-26T04:33:01Z
PandaMystique
119061
Bot : ajout du paramètre lecture=oui au modèle {{DicoPhilo}}
768694
wikitext
text/x-wiki
{{DicoPhilo|Polythéisme|lecture=oui}}
'''Polythéisme''' (du grec ancien {{lang|grc|πολύς}} / ''polús'', « nombreux », et {{lang|grc|θεός}} / ''theós'', « dieu ») désigne la croyance en une pluralité de dieux ou de puissances divines. Cette forme de religiosité, qui précède historiquement le monothéisme dans la plupart des civilisations, constitue un objet d'étude philosophique important pour comprendre les rapports entre la multiplicité et l'unité, l'immanence et la transcendance, ainsi que la nature du divin.
== Caractérisation philosophique ==
Le polythéisme ne se réduit pas à la simple multiplication quantitative des entités divines. Il implique une conception particulière du sacré, de l'ordre cosmique et des relations entre les dieux et le monde. Contrairement au monothéisme qui affirme l'existence d'un Dieu unique, transcendant et créateur, le polythéisme se caractérise par une pluralité de divinités aux pouvoirs et aux domaines d'action spécifiques<ref>George I. Mavrodes, « Monotheism », in ''Routledge Encyclopedia of Philosophy'', Londres et New York, Routledge, 1998, vol. 6, p. 590-592.</ref>.
Dans les traditions polythéistes, les dieux ne sont généralement pas conçus comme des créateurs ex nihilo, mais plutôt comme des puissances immanentes au cosmos, responsables de son organisation et de son maintien. Cette conception distingue le polythéisme du théisme monothéiste où Dieu est pensé comme radicalement transcendant à sa création<ref>Keith E. Yandell, « Pantheism », in ''Routledge Encyclopedia of Philosophy'', Londres et New York, Routledge, 1998, vol. 7, p. 305-306.</ref>.
=== Hénothéisme et monolatrie ===
Il convient de distinguer plusieurs formes de polythéisme. L'''hénothéisme''', terme forgé par le philologue Max Müller pour décrire la religiosité védique, désigne une situation où chaque divinité est tour à tour exaltée comme suprême dans les hymnes qui lui sont adressés, sans pour autant nier l'existence des autres dieux<ref>Sibajiban Bhattacharyya, « God, Indian Conceptions of », in ''Routledge Encyclopedia of Philosophy'', Londres et New York, Routledge, 1998, vol. 4, p. 278-279.</ref>. La '''monolatrie''' désigne quant à elle le culte exclusif d'une seule divinité tout en reconnaissant l'existence d'autres puissances divines, comme c'est le cas dans certaines périodes de l'histoire religieuse d'Israël<ref>George I. Mavrodes, ''op. cit.'', p. 590.</ref>.
== Le polythéisme grec et la pensée philosophique ==
=== Xénophane de Colophon et la critique de l'anthropomorphisme ===
Xénophane de Colophon (vers 570-475 av. J.-C.) inaugure une réflexion critique sur les représentations religieuses traditionnelles. Il soulève le caractère anthropomorphique et ethnocentrique des conceptions divines. « Les Éthiopiens disent que leurs dieux sont camus et noirs, les Thraces qu'ils ont les yeux bleus et les cheveux roux », écrit-il (fragment 16). Plus radicalement, il ajoute : « Si les bœufs, les chevaux et les lions avaient des mains et pouvaient dessiner avec leurs mains et réaliser des œuvres comme les hommes, les chevaux dessineraient des figures de dieux semblables à des chevaux, les bœufs semblables à des bœufs » (fragment 15)<ref>Richard McKirahan, « Xenophanes of Colophon », in ''Encyclopedia of Philosophy'', 3e éd., vol. 10, Detroit, Macmillan Reference USA, 2006, p. 853-854.</ref>.
Cette critique des anthropomorphismes religieux ne conduit pas Xénophane à l'athéisme mais à une conception nouvelle du divin. « Un seul dieu, le plus grand parmi les dieux et les hommes, qui ne ressemble aux mortels ni par le corps ni par la pensée », affirme-t-il (fragment 23). Il décrit une divinité immobile, omnisciente et omnipotente, qui « sans peine, par la seule pensée de son esprit, fait tout vibrer » (fragment 25)<ref>Anthony Kenny, ''Ancient Philosophy'', Oxford, Clarendon Press, 2004, p. 36-37.</ref>. La question de savoir si Xénophane professait un monothéisme strict ou un hénothéisme reste debattue parmi les historiens de la philosophie.
=== Platon et la théologie des Lois ===
Dans les ''Lois'', son dernier dialogue, Platon élabore une théologie qui s'éloigne du polythéisme traditionnel tout en préservant une pluralité d'êtres divins. Il distingue une divinité suprême des dieux astraux (soleil, lune, planètes) et des divinités traditionnelles de la cité. Cette hiérarchisation des entités divines annonce les développements ultérieurs de la théologie philosophique, notamment chez Plotin.
=== Le stoïcisme : panthéisme polymorphe ===
La théologie stoïcienne présente une conception particulière que l'on peut qualifier de « panthéisme polymorphe ». Pour Chrysippe, « la puissance divine réside dans la raison, dans l'âme et l'esprit de la nature universelle ». Il appelle dieu le monde lui-même, l'âme universelle qui le pénètre, « la raison universelle qui embrasse tout ». Les dieux traditionnels deviennent des manifestations diverses d'une seule substance divine : comme terre, le dieu est Déméter ; comme eau et air, Poséidon ; comme feu ou éther, Zeus<ref>Cicéron, ''De natura deorum'', I, 39-40, cité in Anthony Kenny, ''Ancient Philosophy'', Oxford, Clarendon Press, 2004, p. 307.</ref>.
Cette interprétation allégorique permet aux stoïciens de concilier la tradition polythéiste avec leur monisme ontologique. Elle sera reprise et développée dans le néoplatonisme tardif, influençant profondément la théologie médiévale.
=== Le néoplatonisme : l'hénologie de Plotin ===
Plotin (205-270) élabore une métaphysique qui dépasse l'opposition entre monothéisme et polythéisme. Au sommet de sa hiérarchie ontologique se trouve l'Un, principe absolument simple et ineffable, au-delà même de l'être. De l'Un procède l'Intellect (Noûs), qui contient la pluralité des Formes intelligibles, puis l'Âme, qui anime le cosmos sensible<ref>Anthony Kenny, ''Ancient Philosophy'', Oxford, Clarendon Press, 2004, p. 311-314.</ref>.
Cette structure trinitaire — l'Un, l'Intellect, l'Âme — exercera une influence considérable sur la théologie chrétienne, tout en préservant une place pour la pluralité divine : l'Intellect contient la multitude des Idées platoniciennes, qui sont elles-mêmes des entités divines. Plotin peut ainsi affirmer : « Chacun est tous les dieux, et tous les dieux ne font qu'un » (''Ennéades'', V, 8, 9).
== Polythéisme et monothéisme : enjeux philosophiques ==
=== Arguments contre le polythéisme ===
Les philosophes monothéistes, notamment dans la tradition chrétienne médiévale, ont développé plusieurs arguments pour démontrer l'impossibilité logique d'une pluralité de dieux. Thomas d'Aquin, dans la ''Somme théologique'' (Ia, q. 11, a. 3), soutient que l'infinité et la perfection absolue de Dieu impliquent son unicité. Si deux êtres étaient tous deux infinis et parfaits, ils ne pourraient se distinguer l'un de l'autre, car toute distinction impliquerait qu'au moins l'un d'eux possède quelque chose que l'autre ne possède pas, ce qui contredirait leur perfection<ref>George I. Mavrodes, ''op. cit.'', p. 591.</ref>.
L'argument de l'omnipotence soulève également des difficultés : il semble contradictoire qu'il existe deux êtres omnipotents, car si l'un voulait accomplir quelque chose que l'autre voudrait empêcher, l'un des deux devrait échouer, ce qui nierait son omnipotence. Cet argument présente toutefois des faiblesses logiques relevées par plusieurs penseurs contemporains<ref>''Ibid.'', p. 591-592.</ref>.
=== Le problème du mal dans les théologies polythéistes ===
Le polythéisme offre une réponse différente au problème du mal que le monothéisme. Dans un univers régi par plusieurs dieux aux intentions parfois conflictuelles, l'existence du mal ne pose pas la même difficulté théodicéenne que dans le monothéisme, où un Dieu unique, tout-puissant et bienveillant doit être réconcilié avec la présence du mal dans le monde.
Les stoïciens, malgré leur monisme, ont dû affronter cette question. Chrysippe soutenait que les contraires ne peuvent exister l'un sans l'autre : la justice ne peut exister sans l'injustice, le courage sans la lâcheté. Il ajoutait que certains maux apparents résultent d'inévitables conséquences providentielles : la fragilité du crâne humain résulte de la nécessité qu'il soit mince pour accueillir l'intelligence<ref>A. A. Long et D. N. Sedley, ''The Hellenistic Philosophers'', Cambridge, Cambridge University Press, 1987, vol. 1, sect. 54.</ref>.
== Interprétations modernes et contemporaines ==
=== Spinoza : « Deus sive Natura » ===
Baruch Spinoza (1632-1677) développe une ontologie moniste qui identifie Dieu et la Nature (''Deus sive Natura''). Cette position, souvent qualifiée de panthéisme, a été accusée par ses contemporains de mener à l'athéisme. Pour Spinoza, il n'existe qu'une seule substance — Dieu ou la Nature — dont tous les êtres particuliers sont des modes ou des modifications<ref>Yitzhak Melamed et Antonio Salgado Borge, « Spinoza's Deus sive Natura », in ''The Oxford Handbook of Spinoza'', Oxford, Oxford University Press, 2018.</ref>.
Cette conception élimine la distinction entre le divin et le naturel qui caractérise tant le monothéisme que le polythéisme traditionnel. Les dieux du polythéisme classique deviennent, dans cette perspective, des noms différents pour désigner une seule et même réalité substantielle, considérée sous différents attributs ou modes.
=== Hegel et la religion grecque ===
Georg Wilhelm Friedrich Hegel (1770-1831) accorde une place importante au polythéisme grec dans sa philosophie de la religion. Pour lui, la religion grecque représente la « religion de la beauté », une étape essentielle mais non ultime dans le développement de l'Esprit. Le polythéisme grec manifeste l'Esprit qui commence à prendre conscience de lui-même dans la multiplicité des figures divines, chacune incarnant un aspect particulier de la réalité spirituelle<ref>Jon Stewart, « An Introduction to Hegel's Lectures on the Philosophy of Religion », in ''Hegel's Lectures on the Philosophy of Religion'', Oxford, Oxford University Press, 2021, p. 138-143.</ref>.
Cette multiplicité doit cependant être dépassée dans le monothéisme chrétien, où l'Esprit atteint sa pleine conscience de soi dans la doctrine de la Trinité. Le polythéisme apparaît ainsi comme une forme nécessaire mais provisoire dans la dialectique historique du déploiement de l'Esprit.
=== Schelling : mythologie et révélation ===
Friedrich Wilhelm Joseph von Schelling (1775-1854), dans sa ''Philosophie de la mythologie'', propose une interprétation originale du polythéisme. Il distingue un « monothéisme relatif » originel, antérieur à la période polythéiste documentée par les mythologies. Le polythéisme représenterait une phase de développement nécessaire de la conscience humaine, un « polythéisme successif » dans lequel les différentes divinités apparaissent progressivement à travers un processus mythologique<ref>Wagner Félix, « On the Emergence of Peoples : Schelling's Philosophy of Mythology and Amazonian Cosmology », in ''Aurora'', vol. 33, 2021, p. 223-249.</ref>.
Cette évolution devait conduire à un « monothéisme absolu » historique, non mythologique, que Schelling identifie avec la révélation chrétienne. Le polythéisme n'est donc pas une simple erreur ou une régression, mais une étape nécessaire dans l'histoire de la conscience religieuse de l'humanité.
=== Nietzsche : éloge du polythéisme et critique du monothéisme ===
Friedrich Nietzsche (1844-1900) renverse la hiérarchie traditionnelle en valorisant le polythéisme grec contre le monothéisme chrétien. Dans ''La Naissance de la tragédie'' (1872), il analyse la religion grecque à travers l'opposition entre Apollon et Dionysos, deux principes divins complémentaires qui structurent la culture tragique. Le polythéisme grec représente pour Nietzsche une affirmation de la vie dans sa multiplicité et ses contradictions<ref>Albert Henrichs, « Full of Gods : Nietzsche on Greek Polytheism and Culture », in Paul Bishop (éd.), ''Nietzsche and Antiquity'', Rochester, Camden House, 2004, p. 114-131.</ref>.
Dans ses œuvres ultérieures, notamment dans ''Le Gai savoir'' et ''L'Antéchrist'', Nietzsche développe une critique du monothéisme comme négation de la pluralité et de la richesse de l'existence. Le dieu unique du christianisme impose une uniformité morale qui étouffe la créativité et la diversité humaines. À l'inverse, le polythéisme grec permettait une multiplicité de modèles d'excellence et de voies vers l'accomplissement humain. Nietzsche se proclame « le dernier disciple de Dionysos » et oppose ce dieu de l'affirmation vitale au Christ, symbole de négation de la vie<ref>Friedrich Nietzsche, ''Ecce Homo'', « Pourquoi je suis un destin », § 8.</ref>.
=== Durkheim et la sociologie du fait religieux ===
Émile Durkheim (1858-1917), dans ''Les Formes élémentaires de la vie religieuse'' (1912), propose une analyse sociologique du polythéisme à partir de l'étude du totémisme australien. Pour Durkheim, les dieux du polythéisme sont des représentations collectives qui symbolisent la société elle-même et ses diverses composantes. Le caractère sacré attribué aux divinités exprime en réalité la transcendance que la société exerce sur les individus<ref>Émile Durkheim, ''Les Formes élémentaires de la vie religieuse'', Paris, Presses universitaires de France, 1912, rééd. CNRS Éditions, « Biblis », 2014.</ref>.
Cette perspective sociologique permet de comprendre le polythéisme non comme une erreur intellectuelle primitive, mais comme une forme d'organisation symbolique de l'expérience collective. La multiplicité des dieux correspond à la différenciation des fonctions sociales et des domaines d'activité au sein d'une société complexe.
=== Bergson : religion statique et religion dynamique ===
Henri Bergson (1859-1941), dans ''Les Deux sources de la morale et de la religion'' (1932), distingue la « religion statique », liée à la « fonction fabulatrice » de l'intelligence et destinée à renforcer la cohésion sociale, de la « religion dynamique », inspirée par l'expérience mystique et tournée vers l'universel. Le polythéisme appartient principalement à la sphère de la religion statique : les dieux multiples servent à rassurer face aux dangers de l'existence et à légitimer l'ordre social établi<ref>Henri Bergson, ''Les Deux sources de la morale et de la religion'', Paris, Presses universitaires de France, 1932, rééd. « Quadrige », 2008, p. 103-220.</ref>.
Toutefois, Bergson reconnaît que même dans les religions polythéistes, on peut trouver des élans mystiques et une ouverture vers l'universel qui transcendent la simple fonction sociale. La distinction entre polythéisme et monothéisme n'est donc pas absolue du point de vue de la valeur spirituelle.
=== Max Weber : polythéisme des valeurs et désenchantement ===
Max Weber (1864-1920) utilise la notion de « polythéisme des valeurs » pour caractériser la situation de la modernité. Après le « désenchantement du monde » (''Entzauberung der Welt''), le déclin des croyances religieuses traditionnelles et des visions unifiées du monde, nous nous trouvons face à une pluralité de sphères de valeurs — économique, politique, esthétique, érotique, intellectuelle — qui obéissent chacune à sa propre logique et qui peuvent entrer en conflit les unes avec les autres<ref>Max Weber, « Le métier et la vocation de savant », in ''Le savant et le politique'', Paris, Plon, 1959, rééd. La Découverte, « Poche », 2003.</ref>.
Cette situation s'apparente, selon Weber, au polythéisme antique où différentes divinités présidaient à différents domaines de l'existence et pouvaient avoir des exigences contradictoires. La modernité rationnelle ne nous libère donc pas de la pluralité conflictuelle des valeurs, mais nous la confronte de manière plus consciente, sans la médiation rassurante d'un principe d'unification religieux ou métaphysique.
== Polythéisme et philosophie indienne ==
=== Védisme et hénothéisme ===
Les hymnes du ''Rig-Veda'' (vers 1500-1000 av. J.-C.) présentent une forme de religiosité que Max Müller a qualifiée d'hénothéisme : chaque divinité (Indra, Varuna, Agni, etc.) est tour à tour exaltée comme suprême dans les hymnes qui lui sont adressés. Un passage célèbre affirme : « Ce qui est Un, les sages l'appellent de noms multiples : Agni, Yama, Mātariśvan » (''Rig-Veda'', I, 164, 46)<ref>Sibajiban Bhattacharyya, ''op. cit.'', p. 278.</ref>.
Cette formule exprime une intuition de l'unité sous-jacente à la multiplicité des manifestations divines, sans pour autant réduire les dieux à de simples noms d'une réalité unique. La conscience divine pénètre l'univers entier : les champs, les récoltes, les animaux, la parole humaine sont autant de formes de la présence du sacré.
=== Mīmāṃsā : nominalisme théologique ===
L'école Mīmāṃsā, fondée par Jaimini (vers 50 apr. J.-C.), adopte une position nominaliste radicale concernant les dieux. Les noms divins comme Indra ou Varuna existent bien dans les Vedas, mais il n'y a pas de dieux au-delà de ces noms. Les mantras (formules sacrées) ont une efficacité magique intrinsèque qui ne dépend pas de l'existence réelle d'entités divines<ref>''Ibid.'', p. 279.</ref>.
Cette position, qui évacue complètement la question ontologique de l'existence des dieux, peut être considérée comme une forme sophistiquée d'athéisme ritualiste : on conserve les pratiques religieuses pour leur efficacité pragmatique tout en rejetant toute métaphysique théologique.
=== Advaita Vedānta : l'illusion de la multiplicité ===
Śaṅkara (vers 788-820), fondateur de l'école Advaita Vedānta, soutient que la multiplicité, y compris la multiplicité des dieux, n'est qu'une apparence (māyā). Seul Brahman, l'Absolu impersonnel et sans qualités, est réellement existant. Les dieux du polythéisme hindou, de même que le Dieu créateur personnel (Īśvara), appartiennent au niveau de la réalité empirique, qui est ultimement illusoire<ref>''Ibid.'', p. 279-280.</ref>.
Du point de vue de la connaissance suprême (jñāna), il n'y a ni polythéisme ni monothéisme, mais seulement la réalisation de l'identité entre l'ātman (le Soi individuel) et Brahman. Les religions, qu'elles soient polythéistes ou monothéistes, relèvent du domaine de l'ignorance métaphysique (avidyā), même si elles peuvent servir de moyens provisoires sur la voie de la libération.
== Conclusion ==
Le polythéisme ne constitue pas simplement une forme primitive de religiosité destinée à être dépassée par le monothéisme. Il représente une manière spécifique de penser le divin, le cosmos et la place de l'homme dans le monde. Les grandes traditions polythéistes, de la Grèce antique à l'Inde védique, ont produit des réflexions philosophiques d'une grande profondeur sur la nature de la réalité, la multiplicité et l'unité, l'immanence et la transcendance.
Les débats philosophiques autour du polythéisme touchent aux questions fondamentales de la métaphysique (unité ou pluralité de l'être), de la théologie naturelle (arguments pour ou contre l'existence d'un Dieu unique), de l'anthropologie religieuse (fonctions sociales et psychologiques des représentations divines) et de l'éthique (pluralisme ou universalisme des valeurs).
== Notes et références ==
{{references}}
== Bibliographie ==
=== Sources antiques ===
* Cicéron, ''De natura deorum'' [''De la nature des dieux''], texte établi et traduit par Maurice Soubiran, Paris, Les Belles Lettres, « Collection des Universités de France », 2002.
* Plotin, ''Ennéades'', texte établi et traduit par Émile Bréhier, Paris, Les Belles Lettres, « Collection des Universités de France », 1924-1938, 6 volumes.
* Xénophane, fragments in Hermann Diels et Walther Kranz (éd.), ''Die Fragmente der Vorsokratiker'', 6e éd., Berlin, Weidmann, 1951, vol. 1, p. 126-138.
=== Études philosophiques ===
* George I. Mavrodes, « Monotheism », in Edward Craig (éd.), ''Routledge Encyclopedia of Philosophy'', Londres et New York, Routledge, 1998, vol. 6, p. 590-592.
* Keith E. Yandell, « Pantheism », in Edward Craig (éd.), ''Routledge Encyclopedia of Philosophy'', Londres et New York, Routledge, 1998, vol. 7, p. 305-308.
* Anthony Kenny, ''Ancient Philosophy'', Oxford, Clarendon Press, 2004 (''A New History of Western Philosophy'', vol. 1).
* A. A. Long et D. N. Sedley, ''The Hellenistic Philosophers'', Cambridge, Cambridge University Press, 1987, 2 volumes.
=== Philosophie moderne et contemporaine ===
* Henri Bergson, ''Les Deux sources de la morale et de la religion'', Paris, Presses universitaires de France, 1932, rééd. « Quadrige », 2008.
* Émile Durkheim, ''Les Formes élémentaires de la vie religieuse. Le système totémique en Australie'', Paris, Presses universitaires de France, 1912, rééd. CNRS Éditions, « Biblis », 2014.
* Georg Wilhelm Friedrich Hegel, ''Leçons sur la philosophie de la religion'', trad. Jean Gibelin, Paris, Vrin, 1954-1959, 3 volumes.
* Friedrich Nietzsche, ''La Naissance de la tragédie'', trad. Cornélius Heim, Paris, Gallimard, « Folio essais », 1977.
* Friedrich Nietzsche, ''Le Gai savoir'', trad. Patrick Wotling, Paris, Flammarion, « GF », 2007.
* Max Weber, ''Le savant et le politique'', trad. Julien Freund, Paris, Plon, 1959, rééd. La Découverte, « Poche », 2003.
=== Liens externes ===
* [https://plato.stanford.edu/entries/monotheism/ Stanford Encyclopedia of Philosophy : Monotheism]
* [https://www.iep.utm.edu/polythei/ Internet Encyclopedia of Philosophy : Polytheism]
{{AutoCat}}
h305ogp973yi8h0b4kvzxgwfik11r7l
Fonctionnement d'un ordinateur/Les circuits pour l'addition et la soustraction
0
65787
768650
768451
2026-06-25T17:56:26Z
Mewtow
31375
/* La propagation et la génération des retenues */
768650
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir les circuits capables de faire une addition ou une soustraction, ainsi que quelques circuits spécialisés. Précisons cependant que les fabricants de processeurs travaillent d'arrache-pied pour trouver des moyens de rendre ces circuits de calcul plus rapides et plus économes en énergie. Autant vous dire que les circuits que vous allez voir sont vraiment des circuits qui font pâle figure comparé à ce que l'on peut trouver dans un vrai processeur commercial !
==Les circuits pour additionner 2 ou 3 bits==
L'addition se fait en binaire de la même manière qu'en décimal. On additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. La soustraction fonctionne sur le même principe, sur le même modèle qu'en décimal.
[[File:Binary Addition Demonstration.svg|centre|vignette|Exemple d'addition en binaire.]]
En clair, additionner deux nombres demande d'additionner 2 bits et une retenue sur chaque colonne, et de propager les retenues d'une colonne à l'autre. La propagation des retenues est quelque chose de simple en apparence, mais qui est sujet à des optimisations extraordinairement nombreuses. Aussi, pour simplifier l'exposition, nous allons voir comment gérer une colonne avant de voir comment sont propagées les retenues.
[[File:Full Adder Block.svg|vignette|upright=1|class=transparent|Additionneur complet.]]
Si on effectue une addition en colonne, on doit additionner les deux bits sur la colonne, mais aussi additionner une éventuelle retenue. Il faut donc créer un circuit qui additionne trois bits : deux bits de données, plus une retenue. Ce circuit qui additionne trois bits est appelé un '''additionneur complet'''. Il fournit en sortie deux bits : un bit de somme et une retenue sortante. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|-
||0||0||0|| ||0||0
|-
||0||0||1|| ||0||1
|-
||0||1||0|| ||0||1
|-
||0||1||1|| ||1||0
|-
||1||0||0|| ||0||1
|-
||1||0||1|| ||1||0
|-
||1||1||0|| ||1||0
|-
||1||1||1|| ||1||1
|}
Il est possible d'utiliser un tableau de Karnaugh pour traduire la table de vérité, mais elle donne un résultat assez compliqué. La retenue et le bit de somme sont calculés à part. La retenue est calculée avec trois portes ET et une porte OU. Le bit de somme est calculé lui avec 4 portes ET, une porte OU, et trois portes NON.
[[File:Full Adler with 3 entries.png|centre|vignette|upright=2|Additionneur complet, fabriqué à partir de la table de vérité.]]
D'autres méthodes donnent des résultats plus compréhensibles. Nous allons les voir dans la suite de cette section.
===L'additionneur complet basé sur des demi-additionneurs===
[[File:1-bit half-adder.svg|class=transparent|vignette|Demi-addtionneur.]]
Nous avons déjà vu comment additionner deux bits dans le chapitre sur les incrémenteurs, mais quelques rappels ne seront pas de trop. Le '''demi-additionneur''' est un circuit qui additionne deux bits. Il implémente la table d'addition, qui est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Mais un demi-additionneur n'additionne que deux bits, il manque de quoi additionner la retenue. Une solution pour cela est d'enchaîner deux demi-additionneurs : un qui additionne les deux bits de données, et un second qui additionne la retenue au résultat du précédent. La retenue finale se calcule en combinant les sorties de retenue des deux demi-additionneurs, avec une porte OU. Le résultat est un additionneur complet fabriqué avec deux demi-additionneurs. Pour vous en convaincre, établissez la table de vérité de ce circuit, vous verrez que ça marche.
[[File:Full Adder Blocks.svg|centre|vignette|upright=2|Additionneur complet fabriqué avec deux demi-additionneurs.]]
[[File:Half-adder.svg|vignette|upright=1|Circuit d'un demi-additionneur naïf.]]
L'implémentation naïve d'un demi-additionneur utilise une porte XOR et une porte ET, comme illustré ci-contre. Le circuit s'obtient en faisant uen traduction littérale de la table de vérité du circuit. Si je dis qu'elle est naïve, c'est car il est possible de l'optimiser, de manière à éliminer des redondances cachées. Mais nous avions déjà vu cela dans le chapitre sur les incrémenteurs, aussi laissons cela pour plus tard.
En combinant deux demi-additionneurs de ce type, on obtient l'additionneur complet suivant. Les deux sorties de retenue des demi-additionneurs sont combinées avec une porte OU, pour calculer la retenue finale.
{| class="flexible"
|[[File:Full Adder Modules.svg|class=transparent|Composition d'un additionneur complet. On voit bien que celui-ci est composé de deux demi-additionneurs, en rouge et en bleu, auxquels on a ajouté une porte OU pour calculer la retenue finale.]]
|[[File:Full-adder.svg|300px|class=transparent|Circuit d'un additionneur complet.]]
|}
Le fait de combiner les deux retenues avec une porte OU n'est pas ce qu'il y a de plus intuitif. La table de vérité nous dit que ça fonctionne, mais on comprend mal pourquoi. Mais tout devient plus clair quand on sait que les deux retenues ne peuvent pas être à 1 en même temps. Même dans l'addition 1 + 1 + 1, seule une retenue est à 1. Soit l'addition des deux bits d'opérande donne naissance à une retenue, soit c'est l'addition ''résultat + retenue entrante''. La retenue sortant est donc à 1 quand une des deux addition donne une retenue. D'où l'usage d'une porte OU. Remarquez qu'une porte XOR donne le même résultat, vu que le cas où les deux retenues sont à 1 n'est jamais rencontré.
===La propagation et la génération des retenues===
L'additionneur complet que nous allons voir dans cette section sert à introduire les concepts de propagation, génération et absorption de retenue. Il s'agit de concepts très importants quand on étudie les additionneurs. l'idée part d'un principe très simple : la retenue sortante dépend de la retenue d'entrée. La relation entre les deux se résume à trois cas, qui dépendent de la valeur des deux bits additionnés, nommés A et B.
* Dans le premier cas, la retenue entrante est égale à la retenue sortante. On dit que la retenue entrante est propagée par l'additionneur.
* Dans le second cas, la retenue est forcée à 0 : la retenue sortante vaut 0, peu importe la valeur de la retenue entrante.
* Dans le troisième cas, la retenue est forcée à 1 : la retenue entrante vaut 1, peu importe la valeur de la retenue entrante.
[[File:Signaux P et G fournis par un demi-additionneur.png|vignette|Signaux P et G fournis par un demi-additionneur]]
Maintenant, créons un circuit qui nous dise si une retenue est propagée ou générée. Il a deux bits de sortie, nommés P et G : P pour ''Propagate'', G pour ''Generate''. Le bit P est à 1 si la retenue entrante est propagée, il est à 0 sinon. Le bit G est à 1 si une retenue est générée, à 0 sinon. Une retenue est considérée comme absorbée si elle n'est pas ni propagée ni générée, pas besoin d'un troisième bit pour gérer ce cas.
Il se trouve que ce circuit n'est autre qu'un demi-additionneur ! Pour vous en rendre compte, regardez la table de vérité d'un additionneur complet, illustrée ci-dessous. Lorsque les deux bits d'opérande sont à 0, la retenue sortante vaut toujours 0. Si ils sont tous deux à 1, alors la retenue sortante vaut 1. S'ils sont différents, alors retenues sortante et entrante sont égales. Le bit P est donc généré par une simple porte XOR. Quant au bit G, il est à 1 si les deux bits d'opérandes sont à 1, ce qui correspond à une porte ET. Il se trouve que ces deux portes forment un demi-additionneur !
{|class="wikitable"
|-
! Opérande 1 !! Opérande 2 !! Retenue entrante !! !! Retenue sortante
|- class="f_rouge"
||0||0||0|| ||0
|- class="f_rouge"
||0||0||1|| ||0
|- class="f_bleu"
||0||1||0|| ||0
|- class="f_bleu"
||0||1||1|| ||1
|- class="f_bleu"
||1||0||0|| ||0
|- class="f_bleu"
||1||0||1|| ||1
|- class="f_vert"
||1||1||0|| ||1
|- class="f_vert"
||1||1||1|| ||1
|}
Ensuite, créons un circuit qui prend ces deux sorties P et G, et calcule la retenue sortante en fonction. Le circuit en question a trois entrées : la retenue entrante, les deux bits P et G. La retenue sortante vaut 1 soit si une retenue est générée, soit si la retenue entrante est propagée et qu'elle vaut 1. En notant <math>{C_{in}}</math> et <math>{C_{out}}</math> les retenues entrantes et sortante, on a :
: <math>{C_{out}} = G + \left( P . C_{in} \right)</math>
Le circuit est donc composé d'une porte OU et d'une porte ET. En combinant un demi-additionneur avec le circuit de calcul de retenue sortante vu plus haut, on a :
[[File:Additionneur complet avec propagation et génération de retenue.png|class=transparent|centre|vignette|upright=2|Additionneur complet avec propagation et génération de retenue.]]
Certes, on retombe sur le circuit vu plus haut. Mais il est possible de remplacer le circuit en orange par un autre. Par exemple, il est possible de le remplacer par multiplexeur, qui choisit entre la retenue générée et la retenue entrante (propagée). Le choix se fait selon la valeur du bit P, qui chosiit entre propager la retenue et la générer.
[[File:Additionneur crée avec un multiplexeur.png|centre|vignette|upright=2|Additionneur crée avec un multiplexeur]]
Le circuit exact est illustré ci-dessous. Il semble utiliser plus de portes logiques que l'additionneur complet précédent. Cependant, nous verrons dans quelques chapitres qu'il est possible d'implémenter un multiplexeur avec seulement 6 transistors, voire moins ! L'implémentation utilise des portes à transmission, mais nous en reparlerons dans le chapitre sur les transistors, quand nous verrons les additionneurs à ''Manchester Carry Chain''. Au passage, une variante de ce circuit a été utilisée dans le processeur processeur 8086 d'Intel, comme on le verra dans le chapitre suivant.
[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
[[File:FulladderPG.png|vignette|upright=1|Additionneur complet avec deux sorties P et G.]]
Pour finir, sachez qu'il existe des additionneurs qui fournissent : le bit de résultat, deux sorties P et G qui indiquent si l'addition propage ou génère une retenue. Il ne s'agit pas d'additionneurs complets, car il manque de quoi calculer la retenue sortante. De tels additionneurs seront appelés des '''additionneurs partiels'''. De tels additionneurs sont utilisés dans certains additionneurs pour gagner en performance.
Enfin, il existe aussi ce que j'ai décidé d'appeler des '''additionneurs P/G''', qui sont des additionneurs complets auxquels on a ajouté deux sorties P et G, en plus de la sortie de retenue. Leur circuit est le même que celui d'un additionneur complet, auquel on a ajouté deux fils. Vu que ces deux sorties sont fournies par le premier demi-additionneur, ajouter ces deux sorties demande d'ajouter des fils, pas de portes logiques. De tels additionneurs sont aussi utilisés dans certains additionneurs pour gagner en performance.
===L'additionneur complet basé sur une porte à majorité===
Maintenant, voyons une dernière implémentation possible de l'additionneur complet. Mais avant de voir comment l'additionneur est implémenté, nous allons devoir faire un petit focus sur la retenue sortante. Reprenons la table de vérité, et regardons ce qu'il en est pour la retenue sortante uniquement.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante
|-
||0||0||0|| ||0
|-
||0||0||1|| ||0
|-
||0||1||0|| ||0
|-
||0||1||1|| ||1
|-
||1||0||0|| ||0
|-
||1||0||1|| ||1
|-
||1||1||0|| ||1
|-
||1||1||1|| ||1
|}
Vous remarquerez que la retenue sortante est égale au bit majoritaire, parmi les trois bits d'opérande. Si deux ou trois bits sont à 1, la retenue sortante vaut 1. Et réciproquement avec 0. Le circuit de calcul de la retenue peut donc être remplacé par une '''porte à majorité'''.
[[File:Additionneur crée avec une porte à majorité.png|centre|vignette|upright=2|Additionneur crée avec une porte à majorité]]
Mais une porte à majorité est assez gourmande en circuit, ce qui fait qu'un additionneur pareil ne serait pas pratique, ni utile. Du moins, ce serait le cas s'il n'y avait pas une possibilité d'optimisation extrêmement intéressante. Il est possible de calculer le 'bit de somme' à partir de la retenue sortante ! En effet, le bit de somme est l'inverse de la retenue sortante, sauf dans deux cas : les trois bits d'entrée sont à 0, où ils sont tous à 1. Le bit de somme vaut 0 dans le premier cas, 1 dans le second cas.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|- class="f_bleu"
||0||0||0|| ||0||0
|- class="f_vert"
||0||0||1|| ||0||1
|- class="f_vert"
||0||1||0|| ||0||1
|- class="f_vert"
||0||1||1|| ||1||0
|- class="f_vert"
||1||0||0|| ||0||1
|- class="f_vert"
||1||0||1|| ||1||0
|- class="f_vert"
||1||1||0|| ||1||0
|- class="f_rouge"
||1||1||1|| ||1||1
|}
En clair, 6 lignes de la table de vérité sur 8 peuvent se calculer avec une porte à majorité, le reste demandant quelques portes logiques pour faire la correction. L'implémentation en circuit demande donc trois choses :
* de calculer la retenue sortante et de quoi l'inverser ;
* un circuit qui vérifie si tous les bits opérande valent 0 : une porte NOR ou OU fait l'affaire ;
* un circuit qui vérifie s'ils valent tous 1, à savoir une porte ET.
Ensuite, on combine le résultat des trois circuits précédents pour obtenir le résultat final. La combinaison est le fait du circuit en jaune dans le schéma ci-dessous.
[[File:Full adder basé sur une modification de la retenue.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue]]
Le circuit en jaune est assez simple. Quand la porte ET sort un 1, la sortie doit être forcée à 1. Par contre, quand la porte OU sort un zéro, la sortie doit être forcée à 0. Il suffit donc d'enchainer un circuit de mise à 1 et un circuit de mise à 0. Nous avons vu dans le chapitre sur les opérations de masquage qu'il s'agit respectivement d'une porte OU et d'une porte ET.
[[File:Full adder basé sur une modification de la retenue interne, au complet.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue interne, au complet]]
Divers processeurs utilisaient des additionneurs complets de ce type : le fameux Z80, l'Intel 4004 et de l'Intel 8008 sont les exemples les plus notables. Pourtant, on pourrait s'étonner qu'un tel circuit ait existé. Il utilise beaucoup de portes logiques, a une profondeur logique supérieure : il n'a rien d'avantageux. Sauf qu'il était utilisé sur d'anciens processeurs, qui utilisaient la technologie dite TTL, différente de la technologie CMOS des transistors modernes. Et avec la technologie TTL, il est possible de fusionner plusieurs portes logiques ET et NOR en une seule porte logique ET/OU/NON ! Un additionneur complet construit ainsi ne prenait que deux portes logiques : une pour le calcul de la retenue sortante, une autre pour le reste du circuit.
==L'addition non signée==
Voyons maintenant un circuit capable d'additionner deux nombres entiers: l''''additionneur'''. Dans la version qu'on va voir, ce circuit manipulera des nombres strictement positifs. L'addition des nombres codés en complètement à deux sera vu dans une section ultérieure.
===L'additionneur série===
Il est possible d'additionner deux nombres bit par bit, gr$ace à un additionneur complet associé à plusieurs registres à décalages. Les opérandes sont placées chacune dans un registre à décalage, et l'additionneur complet utilise les bits sortants de ces registres à décalage. Le bit du résultat est envoyé au registre à décalage pour le résultat. La retenue de l'addition est stockée dans une bascule de 1 bit, en attente du prochain cycle d'horloge. Un tel additionneur est appelé un '''additionneur série'''. Il a été utilisé sur d'anciens ordinateurs dans les années 50-60, aussi bien des prototypes que des ordinateurs commerciaux.
[[File:Additionneur série.jpg|centre|vignette|upright=2.5|Additionneur série.]]
===L'additionneur à propagation de retenue===
L''''additionneur à propagation de retenue''' est aussi appelé l'additionneur ''ripple carry'', c'est son nom anglais. J'utiliserais occasionnellement le nom anglais dans ce qui suit, car celui-ci est plus court. Ilutilise un additionneur complet pour chaque colonne de l'addition. Pour gérer les retenues, il suffit ainsi de câbler des additionneurs complets les uns à la suite des autres, chacun envoyant sa retenue à la colonne suivante.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Notons la présence de la retenue sortante, qui est utilisée pour détecter les débordements d'entier, ainsi que pour d'autres opérations. Le bit de retenue final est souvent stocké dans un registre spécial du processeur (généralement appelé ''carry flag'').
Notez aussi, à droite du schéma précédent, l'entrée de retenue <math>\text{R}_\text{Entrée}</math>. Elle est très utile pour l'implémentation de certaines opérations comme l'inversion de signe, la soustraction, l'incrémentation, etc. Certains processeurs sont capables de faire une opération appelée ADC, ADDC ou autre nom signifiant ''Addition with Carry'', qui permet de faire le calcul A + B + Retenue (la retenue en question est la retenue sortante de l'addition précédente, stockée dans le registre ''carry flag''). Son utilité principale est de permettre des additions d'entiers plus grands que ceux supportés par le processeur. Par exemple, cela permet de faire des additions d'entiers 32 bits sur un processeur 16 bits.
[[File:Adder Network 3 Bit Sum Delay 003.svg|vignette|upright=0.5|Propagation de retenue dans l'additionneur.]]
Cet additionneur utilise très peu de portes logiques, ce qui fait qu'il était utilisé sur certains processeurs 8 et 16 bits, dont le budget en portes logiques était limité. Bien que très simple, cet additionneur est cependant peu performant. Son temps de calcul est proportionnel à la taille des opérandes. Par exemple, additionner deux opérandes de 32 bits prendra deux fois plus de temps que pour des opérandes de 16 bits. La raison est que les retenues doivent se propager à travers le circuit, du premier additionneur jusqu'au dernier.
L'addition étant une opération fréquente, d'autres additionneurs ont été inventés. Ils utilisent des optimisations qui utilisent plus de circuits pour gagner en rapidité. Mais avant de voir ces optimisations, nous allons voir s'il est possible d'optimiser les additionneurs à propagation de retenue.
===L'additionneur à saut de retenue===
L'additionneur à propagation de retenue peut être optimisé de deux manières. La première accélère la propagation de la retenue, en agissant au niveau des additionneurs complets. La ''Manchester carry chain'' est une optimisation de ce type, mais on ne peut pas encore expliquer à ce stade du cours, car elle optimisait les additionneurs complets au niveau des transistors eux-mêmes. Elle a été utilisée sur de nombreux processeurs connus, comme le 8086 d'Intel ou le Z80.
La seconde manière rend l'addition plus rapide dans certains cas. Le résultat est un additionneur dont le temps de calcul est variable. Le calcul prendra quelques cycles d'horloges avec certains opérandes, mais d'autres opérandes ne verront aucune amélioration. Il n'améliore pas le pire des cas, dans lequel la retenue doit être propagée du début à la fin, du bit de poids faible au bit de poids fort. Mais dans d'autres cas, il permet d'avoir le résultat en avance. L'optimisation en question donne l''''additionneur à saut de retenue''' (''carry-skip adder'').
Il est composé de blocs qui additionnent 4/5 bits, rarement plus. La taille d'un bloc varie d'un additionneur à l'autre, elle peut même varier dans l'additionneur, mais nous allons prendre un exemple où les blocs font 4 bits. L'idée est de détecter précocement si la retenue entrante est propagée à travers tout le bloc. Si la retenue est propagée dans tous le bloc, on peut directement l'envoyer sur la sortie de retenue, la retenue saute le bloc entier. Dans le cas contraire, on doit calculer la retenue normalement. Le choix entre les deux est le fait d'un multiplexeur. Toute la difficulté est de savoir comment commander le multiplexeur.
[[File:Carry skip adder, principe de base.png|centre|vignette|upright=2.5|Carry skip adder : principe de base]]
Pour savoir si une retenue est propagée dans un bloc entier, il faut utiliser des additionneurs complets, qui ont en plus une sortie P indiquant que l'additionneur propage la retenue. Le bloc propage une retenue si chaque additionneur complet propage la retenue, donc s'ils ont tous leur sortie P à 1. Un simple ET logique suffit à déterminer si c'est le cas. Le signal de commande du multiplexeur est donc un vulgaire ET entre toutes les sorties P des additionneurs complets du bloc.
[[File:Additionneur à saut de retenue.png|centre|vignette|upright=2.5|Calcul de la commande du MUX.]]
Voici ce que ça donne pour un bloc de 4 bits :
[[File:CSAdder4Bit.svg|centre|vignette|upright=1.5|Additionneur ''carry skip'' : bloc de 4 bits.]]
L'additionneur à saut de retenue est construit en assemblant plusieurs blocs de ce type.
[[File:BCSAdder16Bit.svg|centre|vignette|upright=3|Additionneur à saut de retenue.]]
===L'additionneur à sélection de retenue===
L''''additionneur à sélection de retenue''' utilise aussi des blocs, comme les additionneurs précédents. L'addition se fait en deux versions : une avec la retenue du bloc précédent valant zéro, et une autre version avec la retenue du bloc précédent valant 1. Il suffira alors de choisir le bon résultat avec un multiplexeur, une fois cette retenue connue. On gagne ainsi du temps en calculant à l'avance les valeurs de certains bits du résultat, sans connaître la valeur de la retenue. Petit détail : sur certains additionneurs à sélection de retenue, les blocs de base n'ont pas la même taille. Cela permet de tenir compte des temps de propagation des retenues entre les blocs.
[[File:Additionneur à sélection de retenue.png|centre|vignette|upright=1.5|Additionneur à sélection de retenue avec seulement deux blocs.]]
Dans les exemples du dessus, chaque sous-additionneur étaient des additionneurs à propagation de retenue. Mais ce n'est pas une obligation, et tout autre type d’additionneur peut être utilisé. Par exemple, on peut faire en sorte que les sous-additionneurs soient eux-mêmes des additionneurs à sélection de retenue, et poursuivre ainsi de suite, récursivement. On obtient alors un '''additionneur à somme conditionnelle''', plus rapide que l'additionneur à sélection de retenue, mais qui utilise beaucoup plus de portes logiques.
===Les additionneurs à anticipation de retenue===
Les '''additionneurs à anticipation de retenue''' calculent chaque retenue sans avoir à propager les retenues précédentes. Au lieu de calculer les retenues une par une, ils calculent toutes les retenues en parallèle, à partir des bits des opérandes. Une fois les retenues pré-calculées, il suffit de les additionner avec les deux bits adéquats, pour obtenir le résultat. Ces additionneurs sont composés de deux parties :
* un circuit qui pré-calcule les retenues, sans les propager, directement à partir des opérandes ;
* d'une couche d'additionneurs complets, qui additionnent chacun deux bits d'opérande et la retenue pré-calculée.
[[File:Additionneur à anticipation de retenue.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue.]]
Le calcul des retenues se fait en deux étapes. La première détermine si les retenues sont propagées ou générées, pour chaque colonne. Elle détermine, pour chaque colonne, les bits P et G qui indiquent respectivement : qu'une retenue a été générée sur cette colonne, que la colonne propage la retenue précédente. La seconde étape utilise ces signaux P et G pour déterminer la retenue sur chaque colonne. Le circuit de calcul des retenues est donc composé de deux parties : une couche de demi-additionneurs pour générer les signaux P et G, un circuit de calcul des retenues proprement dit.
[[File:Circuit complet d'un additionneur à anticipation de retenue.png|centre|vignette|upright=2|Circuit complet d'un additionneur à anticipation de retenue.]]
Pour rappel, la retenue de la colonne i, notée <math>C_i</math> est égale à :
: <math>G_i + (P_i . C_{i-1})</math>, avec G le signal de génération de retenue, P le signal de propagation de retenue.
Si on utilisait cette formule sans trop réfléchir, on retomberait sur un additionneur à propagation de retenue inutilement compliqué. L'astuce des additionneurs à anticipation de retenue consiste à remplacer le terme Ci−1 par sa valeur calculée avant. Par exemple, je prends un additionneur 4 bits. Je dispose de deux nombres A et B, contenant chacun 4 bits : A3, A2, A1, et A0 pour le nombre A, et B3, B2, B1, et B0 pour le nombre B. Si j'effectue les remplacements, j'obtiens les formules suivantes :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Ces formules nous permettent de déduire la valeur d'une retenue directement : il reste alors à créer un circuit qui implémente ces formules, et le tour est joué.
[[File:Four bit adder with carry lookahead.svg|centre|vignette|upright=1.5|Additionneur à anticipation de retenue de 4 bits.]]
Plus haut, j'ai dit que le circuit était composé de trois couches : une couche de demi-additionneur pour les signaux P et G ; un circuit d'anticipation de retenue, puis l'addition finale. Mais les deux bits d'opérandes sont déjà additionnés dans la couche de demi-additionneur, autant réutiliser le résultat pour l'additionner à la retenue. Pour éliminer toute redondance de ce type, il est possible d'utiliser des additionneurs partiels (des additionneurs où la sortie de retenue a été remplacée par deux sorties P et G). L'additionneur final est alors composé de plusieurs additionneurs complets de ce type, couplé à un circuit d'anticipation de retenue.
[[File:Cla4bits.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue de 4 bits.]]
Les additionneurs à anticipation de retenue sont plus rapides que les additionneurs à propagation de retenue. La raison est que les retenues sont calculées rapidement, dans un temps plus petit qu'en propageant la retenue. Pour rentrer dans les détails mathématiques, le temps de calcul n'est pas proportionnel au nombre de bits, mais proportionnel au logarithme de ce nombre de bits. Par contre, le gain en temps de calcul s'accompagne d'un cout en prote logique conséquent. Le circuit qui calcule les retenues anticipées utilise beaucoup de portes logiques.
La conséquence est qu'utiliser un additionneur à anticipation de retenue est une solution pratique pour additionner 4 ou 8 bits. Mais pour des opérandes plus grandes, de 16 ou 32 bits, cela utiliserait trop de portes logiques. Une partie de ce cout en portes logiques est cependant lié à des redondances. Si on analyse deux retenues différentes, à des colonnes différentes. Les circuits qui calculent ces deux retenues seront différents, mais ils contiendront des sous-circuits identiques. Pour vous en rendre compte, reprenons les quatre équations obtenues précédemment et essayez de trouver des redondances :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Vous devriez trouver :
* le terme ''P0 · C0'' dans toutes les lignes ;
* les termes ''P1 · P0 · C0'' et ''P1 · P0'' dans les trois dernières lignes ;
* le terme ''P2 · P1 · P0 · C0'' ; ''P2 · P1 · G0'' et ''P2 · G1'' dans les deux dernières lignes.
===Les additionneurs à calcul parallèle de préfixe===
Il existe plusieurs manières d'exploiter de telles redondances, exploitées dans les '''additionneurs à calcul parallèle de préfixe'''. L'optimisation apportée est de générer des signaux ''propagate'' et ''generate'' pour des groupes de 2, 3, 4, ..., N colonnes. Par exemple, il est possible de générer un signal ''P 0 vers 3'', qui indique si la retenue de la première colonne est propagée jusqu'à la 4ème colonne. Un autre exemple est un signal qui indique qu'une retenue a été générée entre les colonnes 4 à 7 génèrent une retenue ou non. En clair, les signaux P et G ont maintenant un intervalle, qui précise de quelle colonne vers quelle colonne se fait la propagation, ou entre quelles colonnes se fait la génération.
Il est possible de combiner les signaux P et G de deux groupes de colonne, s'ils sont contiguës (ils peuvent aussi se recouvrir). Par exemple, il est possible de calculer les bits P et G pour les colonnes 0 à 10, à partir des deux signaux P/G des colonnes 0-4 et 5-10. Pour cela, les équations sont assez simples. Si je prends deux groupes nommés A et B, avec A pour les colonnes de poids fort et B celles de poids faible, on a :
: <math>P_{sortie} = P_A . P_B</math>
: <math>G_{sortie} = G_A . P_A . G_B</math>
Pour les bits des colonnes 2, 4, 8, 16, 32, 64, et autres, le calcul est simple. L'idée est de grouper les colonnes par groupes de 2. Puis, on calcule chaque groupe de 4 bits à partir de deux groupes de 2. Puis les groupes de 8 bits sont calculés à partir de deux groupes de 4 bits, et ainsi de suite pour toute puissance de deux.
[[File:Squelette d'un additionneur à calcul parallèle de préfixe.jpg|centre|vignette|upright=2|Squelette d'un additionneur à calcul parallèle de préfixe]]
Les seules difficultés surviennent pour les colonnes dont le nombre n'est pas une puissance de deux. Il y a plusieurs manières pour subdiviser ces colonnes en intervalles plus petits et combiner le tout. Et elles donnent chacune des additionneurs différents. Ils portent les noms d'''additionneur de Ladner-Fisher'', d'''additionneur de Brent-Kung'', d'''additionneur de Kogge-Stone'', etc. Dans le cas général, l'additionneur de Kogge-Stone est un des plus rapide, mais aussi un des plus gourmand en circuits, alors que c'est l'inverse pour l'additionneur de Brent-Kung. Les deux additionneurs sont illustrés ci-dessous, pour des opérandes de 8 bits. Dans ceux-ci, chaque losange calculent les bits P et G, à partir des signaux P et G de deux groupes précédents.
{|
|[[File:Brent-kung-8-bit.png|vignette|Brent-kung-8-bit]]
|[[File:Kogge-stone-8-bit.png|vignette|upright=1.2|Kogge-stone-8-bit]]
|}
===Les additionneurs avec anticipation de retenue entre plusieurs additionneurs===
Dans cette section, nous allons voir des additionneurs conçus en assemblant des additionneurs à plus simples, qui additionnent environ 4 à 5 bits, parfois plus, parfois moins. Ces additionneurs simples seront nommés ''blocs'' dans ce qui suit, et l'un d'entre eux est illustré ci-contre. Chaque bloc prend en entrée un morceau des deux opérandes à additionner, mais aussi une retenue d'entrée.
Les blocs sont tous identiques dans le cas le plus simple, mais il est possible d'utiliser des blocs de taille variable. Par exemple, le premier bloc peut avoir des opérandes de 6 bits, le second des opérandes de 7 bits, etc. Faire ainsi permet de gagner un petit peu en performances, si la taille de chaque bloc est bien choisie. La raison est une question de temps de propagation des retenues. La retenue met plus de temps à se propager à travers 8 blocs qu'à travers 4, ce qui prend plus de temps qu'à travers 2 blocs, etc. En tenir compte fait que la taille des blocs tend à augmenter ou diminuer quand on se rapproche des bits de poids fort.
====Le calcul parallèle de la retenue====
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Plus haut, nous avons vu l'anticipation de retenue. Il est possible de l'adapter pour fonctionner avec des blocs. Une première idée est d'utiliser plusieurs additionneurs à anticipation de retenue, chacun étant un bloc. Vu qu'il s'agit d'additionneurs, ils disposent d'une sortie de retenue pour la retenue sortante, et d'une entrée de retenue pour la retenue entrante. Par exemple, le schéma ci-contre illustre un additionneur 4 bits de ce type.
De ce fait, il est possible de propager les retenues d'un bloc à l'autre. par exemple, on peut utiliser 4 additionneurs à anticipation de retenue de 4 bits, et propager les retenues entre eux. Le premier bloc calcule une retenue, qui est propagée au second bloc. Le second bloc calcule lui aussi une retenue, qui est propagée au troisième bloc, etc.
[[File:4008 Functional Diagram.svg|vignette|upright=1|4008 Functional Diagram]]
Le gain en performance est significatif, mais il est possible de faire presque aussi rapide, tout en économisant beaucoup de circuits. L'idée est de simplifier les additionneurs 4 bits, afin de ne pas utiliser une anticipation de retenue complète. L'anticipation de retenue n'est utilisée que pour la retenue sortante, alors que les bits de somme sont calculées avec propagation de retenue. La retenue sortante est donc calculée en parallèle de l'addition. Chaque bloc contient, à côté d'un additionneur proprement dit, un circuit qui calcule la retenue sortante. Il existe de nombreuses manières de calculer la retenue sortante.
Le gain en portes logiques est assez significatif. En effet, on se passe de circuits d'anticipation de retenue pour les 4 bits de somme, on ne garde que celui pour la retenue sortante. Le fait d'utiliser la propagation de retenue pour calculer les 4 bits de somme est censé avoir un léger cout en performance, mais il se trouve que ce cout est très faible. En effet, le résultat n'est connu qu'une fois les retenues propagées jusqu'au dernier bloc. Et malgré l'anticipation des retenues, la propagation est assez lente. les additionneurs à propagation de retenue auront terminé leur travail bien avant.
Un exemple est celui de l'additionneur CMOS 4008, un additionneur de 4 bit. Il est composé en trois sections. Une première couche de demi-additionneurs calcule les signaux P et G utilisés à la fois pour l'anticipation de la retenue sortante, que pour calculer les bits de somme. Le circuit de calcul de la retenue sortante utilise l'anticipation de retenue. Le reste du circuit fait l'addition en propageant les retenues.
[[File:CMOS 4008, circuit découpé en sections.png|centre|vignette|upright=1.5|CMOS 4008, circuit découpé en sections]]
====Les additionneurs à anticipation de retenue multi-niveau====
Il est possible d'utiliser l'anticipation de retenue entre deux blocs. Par exemple, il est possible de créer un additionneur 16 bits en utilisant 4 additionneurs/blocs de 4 bits. L'additionneur utilisé peut être un additionneur à propagation de retenue, ou à anticipation de retenue, peu importe.
Pour cela, les additionneurs de 4 bits doivent fournir deux sorties P et G. Les deux sorties indiquent que le bloc soit a généré une retenue, soit a propagé la retenue entrante. Par propagé, on veut dire que la retenue entrante du bloc s'est retrouvée sur la sortie de retenue du bloc. C'est le cas si les 4 colonnes propagent la retenue, ce qui signifie que :
: <math>P_\text{4 bits} = P_0 \cdot P_1 \cdot P_2 \cdot P_3</math>
Pour la génération, cela veut dire qu'une colonne a générée une retenue, qui a été propagée jusqu'à la sortie. En clair, pour chaque colonne, il faut que le G = 1, et que les signaux P des colonnes soient à 1. Pour les 4 colonnes, cela donne :
: <math>G_\text{4 bits} = G_3 + G_2 \cdot P_3 + G_1 \cdot P_3 \cdot P_2 + G_0 \cdot P_3 \cdot P_2 \cdot P_1</math>
[[File:Cla4bitsPG.png|centre|vignette|upright=2|Additionneur à anticipation de retenue de 4 bits.]]
Pour combiner 4 blocs de 4 bits, on devra donc combiner 4 bits P et G, avec un circuit d'anticipation de retenue.
[[File:16-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 16 bits.]]
Il est même possible d'aller plus loin et de combiner 4 additionneurs précédents pour obtenir un additionneur 64 bits.
[[File:64-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 64 bits.]]
Le résultat est un additionneur à plusieurs niveaux d’anticipation de retenue. Une anticipation de retenue dans un blocs de 4 bits (facultative), entre blocs de 4 bits, entre blocs de 16 bits. La même logique peut être utilisée avec des blocs de taille différente de 4, 16 et 64 bits.
==L'addition signée et la soustraction==
Après avoir vu l'addition, il est logique de passer à la soustraction, les deux opérations étant très proches. Si on sait câbler une addition entre entiers positifs, câbler une soustraction n'est pas très compliqué. De plus, la soustraction permet de faire des additions de nombres signés.
===Le soustracteur pour opérandes entiers===
Pour soustraire deux nombres entiers, on peut adapter l'algorithme de soustraction utilisé en décimal, celui que vous avez appris à l'école. Celui-ci ressemble fortement à l'algorithme d'addition : on soustrait les bits de même poids, et on propage éventuellement une retenue sur la colonne suivante. À la différence de l'addition, la retenue est soustraite, et non ajoutée. La table de soustraction nous dit quel est le résultat de la soustraction de deux bits. La voici :
* 0 - 0 = 0 ;
* 0 - 1 = 1 et une retenue ;
* 1 - 0 = 1 ;
* 1 - 1 = 0.
[[File:Algebra1 05 fig014.svg|centre|vignette|Soustraction en binaire, avec les retenues en rouge.]]
La table de soustraction peut servir de table de vérité pour construire un circuit qui soustrait deux bits. Celui-ci est appelé un '''demi-soustracteur'''. Il ressemble beaucoup à un demi-additionneur, les différences se résumant à une porte NON ajoutée pour le calcul de la retenue.
[[File:Half Subtractor Vektor.svg|centre|vignette|Demi-soustracteur.]]
Comme pour l'additionneur, deux demi-soustracteurs peuvent être combinés pour donner un '''soustracteur complet'''. Le calcul de la retenue se fait en combinant les deux retenues des demi-soustracteurs avec une porte OU. Les soustracteurs complets sont utilisés pour créer des soustracteurs à propagation de retenue ou tout autre circuit soustracteur, sur le même modèle que les additionneurs.
[[File:FullSubtractor.svg|centre|vignette|upright=2|Soustracteur complet.]]
Vous l'aurez sans doute remarqué, mais le demi-soustracteur ressemble beaucoup au demi-additionneur. La seule différence est une porte NON ajoutée au bon endroit dans le demi-soustracteur.
{|
|[[File:Half-adder.svg|vignette|Demi-additionneur]]
|[[File:Half Subtractor Vektor.svg|vignette|upright=1.3|Demi-soustracteur]]
|}
La conséquence est qu'il est possible de créer un circuit capable de faire à la fois des additions et des soustractions. L'idée est de remplacer la porte NON du demi-soustracteur par un inverseur commandable. Si on envoie un zéro sur l'entrée de commande, l'inverseur commandable se comportera comme une porte OUI. Le circuit sera alors identique à un demi-additionneur. Si on envoie un 1 sur l'entrée de commande, l'inverseur commandable se comportera comme une porte NON. le circuit sera alors un demi-soustracteur. Il faut faire cela pour chaque demi-soustracteur, ce qui donne ceci pour un soustracteur complet. le résultat est alors appelé un '''additionneur-soustracteur complet'''.
[[File:Additionneur-soustracteur complet.png|centre|vignette|upright=2|Additionneur-soustracteur complet]]
===L'additionneur-soustracteur pour opérandes codées en complément à deux===
Étudions le cas de la soustraction en complément à deux, dans l'objectif de créer un circuit soustracteur. Vous savez sûrement que a−b et a+(−b) sont deux expressions équivalentes. Et en complément à deux, − b = not(b) + 1. Dit autrement, a − b = a + not(b) + 1. On pourrait se dire qu'il faut deux additionneurs pour faire le calcul, mais la majorité des additionneurs possède une entrée de retenue pour incrémenter le résultat de l'addition. Un soustracteur en complément à deux est donc simplement composé d'un additionneur et d'un inverseur.
[[File:Additionneur soustracteur.png|centre|vignette|upright=2|Soustracteur en complément à deux.]]
Il est possible de créer un circuit capable d'effectuer soit une addition, soit une soustraction : il suffit de remplacer l'inverseur par un inverseur commandable, qui peut être désactivé. On a vu comment créer un tel inverseur commandable dans le chapitre sur les circuits combinatoires. On peut remarquer que l'entrée de retenue et l'entrée de commande de l'inverseur sont activées en même temps : on peut fusionner les deux signaux en un seul.
[[File:Additionneur-soustracteur en complément à deux.png|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux.]]
Une implémentation alternative est la suivante. Elle remplace l'inverseur commandable par un multiplexeur.
[[File:4-bit ripple carry adder-subtracter.svg|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux, version alternative.]]
===L'additionneur-soustracteur pour opérandes codées en signe-magnitude===
Passons maintenant aux nombres codés en signe-valeur absolue, les deux opérandes étant notées A et B. Suivant les signes des deux opérandes, on a quatre cas possibles : A + B, A − B (B négatif), −A + B (A négatif) et −A − B (A et B négatifs). Une astuce est que le circuit n'a besoin que de calculer A + B et A − B : il peut les inverser pour obtenir − A − B ou B − A. A + B et A − B peuvent se calculer avec un additionneur-soustracteur, reste à corriger le résultat. Il suffit de lui ajouter un inverseur commandable pour obtenir le circuit d'addition finale.
[[File:Additionneur en signe-valeur absolue.png|centre|vignette|upright=2|Additionneur en signe-valeur absolue.]]
Toute la difficulté tient dans le calcul du bit de signe du résultat, quand interviennent des soustractions. Autant l'addition de deux nombres de même signe (A + B et −A − B) ne pose aucun problème, autant les soustractions posent problème (A − B et −A + B). Suivant que <math>A<B</math> ou que <math>A>B</math>, le signe du résultat ne sera pas le même. Déterminer le signe du résultat se fait en regardant les bits de débordement d'entier, comme on le verra plus bas.
===L'additionneur-soustracteur pour opérandes codées en représentation par excès===
Passons maintenant aux nombres codés en représentation par excès. On pourrait croire que ces nombres s'additionnent comme des nombres non-signés, mais ce serait oublier la présence du biais, qui pose problème. Dans les cas de nombres signés gérés avec un biais, voyons ce que donne l'addition de deux nombres :
: <math>( n_1 + biais ) + ( n_2 + biais ) = n_1 + n_2 + 2 \times biais</math>
Or, le résultat correct serait :
: <math>n_1 + n_2 + biais</math>
En effectuant l'addition telle quelle, le biais est compté deux fois. On doit donc le soustraire après l'addition pour obtenir le résultat correct.
Même chose pour la soustraction qui donne ceci :
: <math>(n_1 + biais) - (n_2 + biais) = n_1 - n_2</math>
Or, le résultat correct serait :
: <math> ( n_1 - n_2 ) + biais </math>
Il faut rajouter le biais pour obtenir l'exposant correct.
On a donc besoin de deux additionneurs/soustracteurs : un pour additionner/soustraire les représentations binaires des opérandes, et un autre pour ajouter/retirer le biais en trop/manquant.
==L'additionneur BCD==
Maintenant, voyons un additionneur qui additionne deux entiers au format BCD. Pour cela, nous allons devoir passer par deux étapes. La première est de créer un circuit capable d'additionneur deux chiffres BCD. Ensuite, nous allons voir comment enchaîner ces circuits pour créer un additionneur BCD complet.
===L'additionneur BCD qui fait l'opération chiffre par chiffre===
Nous allons commencer par voir un additionneur qui additionne deux chiffres en BCD, une sorte d'équivalent BCD de l'additionneur complet. Il fournit un résultat sur 4 bits et une retenue qui est mise à 1 si le résultat dépasse 10 (la limite d'un chiffre BCD). Les deux opérandes sont des chiffres BCD codés sur 4 bits et sont additionnés en binaire par un additionneur des plus normaux, similaire à ceux vus plus haut. Le résultat est alors un entier codé en binaire, sur 5 bits, qu'on corrige/convertit pour obtenir un chiffre BCD et une retenue sortante.
Pour corriger le résultat, une idée intuitive serait de prendre le résultat et de faire une division par 10. Le quotient donne la retenue, alors que le reste est le résultat, le chiffre BCD. Mais un circuit diviseur par 10 utilise beaucoup de portes logiques, ce qui ne vaut pas le coup. Une autre méthode détecte si le résultat est égal ou supérieur à 10, ce qui correspond à un "débordement" (on dépasse les limites d'un chiffre BCD). Si le résultat est plus petit que 10, il n'y a rien à faire : le résultat est bon et la retenue est de zéro. Par contre, si le résultat vaut 10 ou plus, il faut corriger le résultat et générer une retenue à 1.
Il faut donc ajouter un circuit qui détecte si le résultat est supérieur à 9, qui calcule directement la retenue. Ce circuit peut se fabriquer simplement à partir de sa table de vérité, ou en utilisant les techniques que nous verrons dans un chapitre ultérieur sur les comparateurs. La solution la plus simple est clairement d'utiliser la table de vérité, ce qui est très simple, assez pour être laissé en exercice au lecteur. Pour comprendre comment corriger le résultat, établissons une table de vérité qui associe le résultat et le résultat corrigé. L'entrée vaut au minimum 10 et au maximum 9 + 9 = 18. On considère la sortie comme un tout, la retenue étant un 5ème bit, le bit de poids fort.
{|class="wikitable"
|-
! colspan="5" | Entrée
!
! rowspan="10" |
! Retenue
! Résultat corrigé (sans retenue)
! interprétation de la sortie en binaire (retenue inclue)
|-
| 0 || 1 || 0 || 1 || 0
| (10)
| 1 || 0000
| (16)
|-
| 0 || 1 || 0 || 1 || 1
| (11)
| 1 || 0001
| (17)
|-
| 0 || 1 || 1 || 0 || 0
| (12)
| 1 || 0010
| (18)
|-
| 0 || 1 || 1 || 0 || 1
| (13)
| 1 || 0011
| (19)
|-
| 0 || 1 || 1 || 1 || 0
| (14)
| 1 || 0100
| (20)
|-
| 0 || 1 || 1 || 1 || 1
| (15)
| 1 || 0101
| (21)
|-
| 1 || 0 || 0 || 0 || 0
| (16)
| 1 || 0110
| (22)
|-
| 1 || 0 || 0 || 0 || 1
| (17)
| 1 || 0111
| (23)
|-
| 1 || 0 || 0 || 1 || 0
| (18)
| 1 || 1000
| (24)
|}
En analysant le tableau, on voit que pour corriger le résultat, il suffit d'ajouter 6. La raison est que le résultat déborde d'un nibble à 16 en binaire, mais à 10 en décimal : il suffit d'ajouter la différence entre les deux, à savoir 6, et le débordement binaire fait son travail. Donc, la correction après une addition est très simple : si le résultat dépasse 9, on ajoute 6.
On peut maintenant implémenter l'additionneur BCD, en combinant le comparateur avec 10, le circuit de correction, et l'additionneur. La première solution calcule deux versions du résultat : la version corrigée, la version normale. Le choix entre les deux est réalisée par un multiplexeur, commandé par le comparateur.
[[File:Additionneur BCD.png|centre|vignette|upright=1.5|Additionneur BCD]]
L'autre solution utilise un circuit commandable qui soit additionne 6, soit ne fait rien. Le choix entre les deux est commandé par le bit calculé par le comparateur.
[[File:Additionneur BCD, seconde version.png|centre|vignette|upright=1.5|Additionneur BCD, seconde version.]]
Une version alternative du circuit précédent est la suivante. Il contient deux additionneurs : un pour additionner les deux chiffres BCD, un autre pour additionner 6 si besoin. Le résultat du comparateur est directement utilisé pour générer l'opérande du second additionneur : 0 ou 6. Le circuit est simple à concevoir, mais gaspille beaucoup de circuit. Idéalement, il vaudrait mieux utiliser un circuit combinatoire d'addition avec une constante.
[[File:Adder 4 Bit BCD.svg|centre|vignette|upright=2|Additionneur BCD, circuit complet.]]
Pour obtenir un additionneur BCD complet, il suffit d’enchaîner les additionneurs précédents, comme on le ferait avec les additionneurs complets dans un additionneur à propagation de retenue. Au final, l'additionneur BCD est beaucoup plus compliqué qu'un additionneur normal, car il rajoute un comparateur ">9", un petit additionneur pour ajouter 6 et éventuellement d'autres circuits. De plus, il est difficile d'appliquer les optimisations disponibles sur les additionneurs non-BCD. Notamment, les circuits d'anticipation de retenue sont totalement à refaire et le résultat est relativement compliqué. C'est ce qui explique pourquoi le BCD a progressivement été abandonné au profit du binaire simple.
La soustraction en BCD se fait comme en binaire : le nombre à soustraire est remplacé par son complément, le circuit additionne le complément et l'autre opérande, le débordement d'entier fait que le résultat marche. Sauf qu'ici, le complément est un complément à 9. Il se calcule chiffre par chiffre : chaque chiffre est remplacé par (9 - le chiffre en question).
===L'additionneur BCD par ajustement décimal===
L'additionneur BCD précédent effectuait son travail chiffre BCD par chiffre BCD, mais il existe des additionneurs BCD qui font autrement. Sur les premiers processeurs x86, il n'y avait pas d'opération d'addition BCD proprement dit, seulement une addition binaire normale de 8, 16 ou 32 bits. Par contre, elle était secondée par une opération dite d''''ajustement décimal''' qui transformait un nombre binaire en nombre codé en BCD. L'opération d'ajustement décimal prenait un opérande de 8 bits codé en binaire et fournissait un résultat de la même taille, c'est à dire deux chiffres BCD. Effectuer une addition BCD demandait donc de faire deux opérations à la suite : une addition binaire simple, suivie par l'opération d'ajustement décimal. Cela permettait de gérer des nombres entiers en binaire usuel et des entiers BCD sans avoir deux instructions d'addition séparées pour les deux, sans compter que cela simplifiait aussi les circuits d'addition.
L'ajustement décimal s'effectue en ajoutant une constante bien précise à l'opérande à convertir en BCD. L'idée est que la constante est découpée en morceaux de 4 bits, correspondant chacun à un chiffre BCD de l'opérande, chaque morceau contenant soit un 0, soit 6. Cela permet d'ajouter soit 0, soit 6, à chaque chiffre BCD, et donc de le corriger. La propagation des retenues d'un chiffre à l'autre est effectuée automatiquement par l'addition binaire de la constante. L'opération d'ajustement décimal calcule automatiquement la constante. Elle découpe l'opérande en ''nibbles'', vérifie si chaque ''nibble'' est supérieur ou égal à 10, puis détermine la valeur de chaque ''nibble'' de la constante finale. Par exemple, si je prends l'opérande 1001 1110, le ''nibble'' de poids faible déborde, alors que celui de poids fort non. La constante sera donc 0000 0110 : 0x06. Inversement, si le ''nibble'' de poids fort déborde et pas celui de poids faible, la constante sera alors 0x60. Et la constante est de 0x66 si les deux ''nibbles'' débordent, de 0x00 si aucun ne déborde.
Le circuit d’ajustement décimal est donc composé de trois étapes : deux étapes pour calculer la constante, et un circuit d'addition pour additionner cette constante au nombre de départ. La première étape découpe l'opérande en morceaux de 4 bits, en chiffres BCD, et vérifie si chacun d'entre eux vaut 10 ou plus. La seconde étape prend les résultats de la première étape, et les combine pour calculer la constante. Enfin, on trouve l'addition finale, qui était réalisée par un circuit d'addition utilisé à la fois pour l'ajustement décimal et l'addition binaire. La différence entre une addition normale et une opération d'ajustement décimal tient dans le fait que les deux premières étapes sont désactivées dans une addition normale.
[[File:Additionneur BCD parallèle.png|centre|vignette|upright=1.5|Additionneur BCD parallèle]]
===L'additionneur biquinaire===
Les entiers BCD ne sont qu'un des encodages hybrides entre décimal et binaire. L'encodage biquinaire est l'un d'entre eux et nous allons faire un rappel rapide à ce sujet. Pour simplifier, un chiffre encodé en biquinaire est composé de deux parties : un bit, couplé à une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits (0, 1, 2, 3 et 4). Le bit indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9.
Additionner deux nombres de biquinaire demande donc d'additionner deux parties quinaires encodées en ''one-hot'' et d'additionner deux bits. Mais attention : il faut tenir compte de la retenue de l'addition des parties quinaires. Et idéalement, il faut aussi tenir compte d'une retenue entrante, provenant de l'addition de la colonne de chiffres précédente. Toute la difficulté vient de la création de l'additionneur ''one-hot''. Heureusement, vu qu'il n'y a que 4-5 bits à additionner, il est souvent fabriqué à partir de sa table de vérité.
[[File:Additionneur bi-quinaire.png|centre|vignette|upright=2|Additionneur bi-quinaire]]
Un avantage du biquinaire est que le calcul du complément à 9 est très simple. Il faut pour cela : inverser la partie binaire avec une porte NON, puis inverser l'ordre des bits de la partie quinaire. Concrètement, le bit de poids faible devient le bit de poids fort, et ainsi de suite. Par exemple, une partie quinaire 01000 devient 00010, 10000 devient 00001, 00100 ne change pas, etc. Le tout peut se calculer avec une porte NON et 5 multiplexeurs.
===L'additionneur BCD avec calculs intermédiaires en biquinaire===
L'ordinateur IBM 1401, un ancien ''mainframe'' des années 60, utilisait un additionneur BCD un peu particulier. Les nombres étaient encodés en BCD dans la mémoire de l'ordinateur, mais les circuits de calcul utilisaient la représentation biquinaire. Lors d'un calcul, le processeur de l'ordinateur traduisait les chiffres BCD en représentation biquinaire, faisait une addition en biquinaire, avant de traduire le résultat en BCD normal.
Pour être précis, l'IBM 1401 utilisait une variante du biquinaire. L'encodage biquinaire de l'IBM 1401 est le suivant : la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre pair encodé par la partie quinaire. Si l'IBM 1401 utilisait cette variante du biquinaire, c'est car elle donnait des circuits de conversion BCD-biquinaire plus économes en portes logiques et plus rapides.
La partie binaire est le bit de poids faible du chiffre BCD, la partie biquinaire est calculée par un simple décodeur qui prend en entrée le chiffre BCD, amputé de son bit de poids faible. La traduction inverse demande d'utiliser un encodeur, à la place du décodeur. Par contre, le circuit d'addition biquinaire était plus compliqué du fait de la gestion des retenues. L'addition des parties binaires et quinaires se faisait en parallèle, dans deux additionneurs séparés. Cependant, l'addition des parties binaire fournit une retenue, qu'il faut prendre en compte. Pour cela, l'IBM 1401 disposait d'un troisième additionneur qui fournissait le résultat final, encodé en biquinaire.
[[File:Additionneur bi-quinaire de l'IBM 1401.png|centre|vignette|upright=2|Additionneur biquinaire de l'IBM 1401]]
Une implémentation moderne demanderait d'utiliser des portes ET combinées à des portes OU, le circuit pouvant être construit simplement à partir de sa table de vérité. Sur l'IBM 1401, le circuit était cependant différent, en raison de l'utilisation de OU câblés, des croisements de fils qui fonctionnent comme des portes OU, que nous n'avons pas encore vu pour le moment, mais qui seront détaillés dans quelques chapitres. Les OU câblés étaient utilisés pour simplifier le design du circuit, mais demandaient des portes logiques spécifiques, ce qui collait avec le fait que ce ''mainframe'' utilisait des transistors en Germanium. L'implémentation exacte est décrite dans cet article de blog, mais je ne recommande sa lecture qu'à ceux qui savent ce qu'est un OU câblé :
* [https://www.righto.com/2015/10/qui-binary-arithmetic-how-1960s-ibm.html Qui-binary arithmetic: how a 1960s IBM mainframe does math].
==Les débordements d'entier lors d'une addition/soustraction==
Les instructions arithmétiques manipulent des entiers codés sur un nombre fixe de bits, qui ne peuvent prendre leurs valeurs que dans un intervalle. Pour les nombres positifs, un ordinateur qui code ses entiers sur n bits pourra coder tous les entiers allant de 0 à <math>2^n -1</math>. Pour les nombres négatifs, l'intervalle est différent et dépend de la représentation utilisée. Dans le cas général, l'ordinateur peut coder les valeurs comprises de <math>N_\text{min}</math> à <math>N_\text{max}</math>. Si le résultat d'un calcul sort de cet intervalle, il ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math> , alors que la valeur basse vaut <math>N_\text{min} - 1</math> (avec <math>N_\text{max}</math> et <math>N_\text{min}</math> respectivement la plus grande et la plus petite valeur codable par l'ordinateur).
===La correction des débordements d'entier : l'arithmétique saturée===
Quand un débordement d'entier survient, tous les circuits de calcul ne procèdent pas de la même manière. Dans les grandes lignes, il y a deux réactions possibles : soit on corrige automatiquement le résultat du débordement, soit on ne fait rien et on se contente de détecter le débordement.
Si le débordement n'est pas corrigé automatiquement, le circuit ne conserve que les bits de poids faibles du résultat. Les bits en trop sont simplement ignorés. On dit qu'on utilise l''''arithmétique modulaire'''. Le problème avec ce genre d'arithmétique, c'est qu'une opération entre deux grands nombres peut donner un résultat très petit. Par exemple, si je dispose de registres 4 bits et que je souhaite faire l'addition 1111 + 0010 (ce qui donne 15 + 2), le résultat est censé être 10001 (17), ce qui est un résultat plus grand que la taille d'un registre. En conservant les 4 bits de poids faible, j’obtiens 0001 (1). En clair, un résultat très grand est transformé en un résultat très petit. Cela peut poser problèmes si on travaille uniquement avec des nombres positifs, mais c'est aussi utilisé pour coder des nombres en complément à deux.
D'autres circuits utilisent ce qu'on appelle l''''arithmétique saturée''' : si un calcul génère un débordement, on arrondi le résultat au plus grand entier supporté par le circuit. Les circuits capables de calculer en arithmétique saturée sont un peu plus complexes, vu qu'il faut rajouter des circuits pour corriger le résultat en cas de débordement. Il suffit généralement de rajouter un ''circuit de saturation'', qui prend en entrée le résultat et le corrige en cas de débordement. Ce circuit de saturation met la valeur maximale en sortie si un débordement survient, mais se contente de recopier le résultat du calcul sur sa sortie s'il n'y a pas de débordement. Typiquement, il est composé d'une couche de multiplexeurs, qui sélectionnent quelle valeur mettre sur la sortie : soit le résultat du calcul, soit le plus grand nombre entier géré par le processeur, soit le plus petit (pour les nombres négatifs/soustractions).
L'arithmétique saturée est utilisée pour les additions et soustractions, mais c'est plus rare pour les multiplications/divisions. Une des raisons est que le résultat d'une addition/soustraction prend un bit de plus que le résultat, là où les multiplications doublent le nombre de bits. Quand une addition déborde, le résultat réel est proche de la valeur maximale codable. mais quand une multiplication déborde, le résultat peut parfois valoir 200 à 60000 fois plus que la valeur maximale codable. Les calculs avec une valeur saturée/corrigée sont donc crédibles pour une suite d'additions, mais pas pour une suite de multiplications.
===La détection des débordements entiers===
Quand un débordement d'entier a eu lieu, il vaut mieux que l'additionneur prévienne ! Pour cela, l'additionneur a une '''sortie de débordement''', parfois nommée ''Overflow'', dont la valeur indique si l'addition a généré un débordement d'entier ou non. Reste que détecter un débordement ne se fait pas de la même manière selon que l'on parle d'un additionneur non-signé ou signé.
Pour les additionneur non-signés, l'additionneur calcule un bit de plus que ce qui est supporté par l'ordinateur. Par exemple, un additionneur 32 bits fournit un résultat sur 33 bits, un débordement d'entier a lieu quand le 33ème bit est à 1. Précisément, la sortie de débordement n'est autre que la retenue finale, celle fournie par le dernier additionneur complet. Le seul type de débordement possible est un débordement par le haut, où le résultat dépasse la valeur maximale. Avec l'arithmétique saturée, le circuit de saturation consiste en une seule couche de multiplexeurs, voire en un circuit de mise à la valeur maximale tel que vu dans le chapitre sur les opérations bits à bits.
[[File:Gestion des débordements d'entiers.png|centre|vignette|upright=2.5|Gestion des débordements d'entiers lors d'une addition non-signée.]]
Pour les additionneurs non-signés, la gestion des débordements d'entiers dépend fortement de la représentation signée. Nous allons étudier le cas du complément à deux. Si vous vous rappelez le chapitre 1, les calculs sur des nombres en complètement à deux utilisent les règles de l'arithmétique modulaire, c'est une condition nécessaire. À priori, on peut penser que dans ces conditions, les débordements d'entiers sont une chose parfaitement normale, qui nous permet d'avoir des résultats corrects. Néanmoins, certains débordements d'entiers peuvent survenir malgré tout et produire des bugs assez ennuyeux.
Si l'on tient en compte les règles du complément à deux, on sait que le bit de poids fort (le plus à gauche) permet de déterminer si le nombre est positif ou négatif : il indique le signe du nombre. Tout se passe comme si les entiers en complément à deux étaient codés sur un bit de moins, et avaient leur longueur amputé du bit de poids fort. Si le résultat d'un calcul écrase le bit de poids fort, il y a un débordement d'entiers. Il existe une règle simple qui permet de détecter ces débordements d'entiers. L'addition de deux nombres positifs ne peut pas être un nombre négatif. Si on additionne deux nombres dont le bit de signe est à 0 et que le bit de signe du résultat est à 1, on est en face d'un débordement d'entiers. Même chose pour deux nombres négatifs : le résultat de l'addition ne peut pas être positif. On peut résumer cela en une phrase : si deux nombres de même signe sont ajoutés, un débordement a lieu quand le bit du signe du résultat a le signe opposé.
Modifier les circuits d'au-dessus pour qu'ils détectent les débordements en complément à deux est simple comme bonjour : il suffit créer un petit circuit combinatoire qui prenne en entrée les bits de signe des opérandes et du résultat, et qui fasse le calcul de l'indicateur de débordements. Si l'on rédige sa table de vérité, on doit se retrouver avec la table suivante :
{|class="wikitable"
|-
!Entrées
!Sortie
|-
|000||0
|-
|001||1
|-
|010||0
|-
|011||0
|-
|100||0
|-
|101||0
|-
|110||1
|-
|111||0
|}
L'équation de ce circuit est la suivante, avec <math>S_a</math> et <math>S_b</math> les signes des deux opérandes, et <math>C_i</math> la retenue de la colonne précédente :
: <math> ( S_a . S_b . \overline{C_i} ) + ( \overline{S_a} . \overline{S_b} . C_i )</math>
En simplifiant, on obtient alors :
: <math>( S_a . S_b ) \oplus C_i</math>
Or, il se trouve que <math>S_a . S_b</math> est tout simplement la retenue en sortie du dernier additionneur, que nous noterons <math>C_o</math>. On trouve donc :
: <math>C_o \oplus C_i</math>
Il suffit donc de faire un XOR entre la dernière retenue et la précédente pour obtenir le bit de débordement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de décalage et de rotation
| prevText=Les circuits de décalage et de rotation
| next=Les circuits de comparaison
| nextText=Les circuits de comparaison
}}
</noinclude>
8hcsihbei3d25zkd2suecvhv75bmgoq
768651
768650
2026-06-25T17:56:45Z
Mewtow
31375
/* La propagation et la génération des retenues */
768651
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir les circuits capables de faire une addition ou une soustraction, ainsi que quelques circuits spécialisés. Précisons cependant que les fabricants de processeurs travaillent d'arrache-pied pour trouver des moyens de rendre ces circuits de calcul plus rapides et plus économes en énergie. Autant vous dire que les circuits que vous allez voir sont vraiment des circuits qui font pâle figure comparé à ce que l'on peut trouver dans un vrai processeur commercial !
==Les circuits pour additionner 2 ou 3 bits==
L'addition se fait en binaire de la même manière qu'en décimal. On additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. La soustraction fonctionne sur le même principe, sur le même modèle qu'en décimal.
[[File:Binary Addition Demonstration.svg|centre|vignette|Exemple d'addition en binaire.]]
En clair, additionner deux nombres demande d'additionner 2 bits et une retenue sur chaque colonne, et de propager les retenues d'une colonne à l'autre. La propagation des retenues est quelque chose de simple en apparence, mais qui est sujet à des optimisations extraordinairement nombreuses. Aussi, pour simplifier l'exposition, nous allons voir comment gérer une colonne avant de voir comment sont propagées les retenues.
[[File:Full Adder Block.svg|vignette|upright=1|class=transparent|Additionneur complet.]]
Si on effectue une addition en colonne, on doit additionner les deux bits sur la colonne, mais aussi additionner une éventuelle retenue. Il faut donc créer un circuit qui additionne trois bits : deux bits de données, plus une retenue. Ce circuit qui additionne trois bits est appelé un '''additionneur complet'''. Il fournit en sortie deux bits : un bit de somme et une retenue sortante. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|-
||0||0||0|| ||0||0
|-
||0||0||1|| ||0||1
|-
||0||1||0|| ||0||1
|-
||0||1||1|| ||1||0
|-
||1||0||0|| ||0||1
|-
||1||0||1|| ||1||0
|-
||1||1||0|| ||1||0
|-
||1||1||1|| ||1||1
|}
Il est possible d'utiliser un tableau de Karnaugh pour traduire la table de vérité, mais elle donne un résultat assez compliqué. La retenue et le bit de somme sont calculés à part. La retenue est calculée avec trois portes ET et une porte OU. Le bit de somme est calculé lui avec 4 portes ET, une porte OU, et trois portes NON.
[[File:Full Adler with 3 entries.png|centre|vignette|upright=2|Additionneur complet, fabriqué à partir de la table de vérité.]]
D'autres méthodes donnent des résultats plus compréhensibles. Nous allons les voir dans la suite de cette section.
===L'additionneur complet basé sur des demi-additionneurs===
[[File:1-bit half-adder.svg|class=transparent|vignette|Demi-addtionneur.]]
Nous avons déjà vu comment additionner deux bits dans le chapitre sur les incrémenteurs, mais quelques rappels ne seront pas de trop. Le '''demi-additionneur''' est un circuit qui additionne deux bits. Il implémente la table d'addition, qui est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Mais un demi-additionneur n'additionne que deux bits, il manque de quoi additionner la retenue. Une solution pour cela est d'enchaîner deux demi-additionneurs : un qui additionne les deux bits de données, et un second qui additionne la retenue au résultat du précédent. La retenue finale se calcule en combinant les sorties de retenue des deux demi-additionneurs, avec une porte OU. Le résultat est un additionneur complet fabriqué avec deux demi-additionneurs. Pour vous en convaincre, établissez la table de vérité de ce circuit, vous verrez que ça marche.
[[File:Full Adder Blocks.svg|centre|vignette|upright=2|Additionneur complet fabriqué avec deux demi-additionneurs.]]
[[File:Half-adder.svg|vignette|upright=1|Circuit d'un demi-additionneur naïf.]]
L'implémentation naïve d'un demi-additionneur utilise une porte XOR et une porte ET, comme illustré ci-contre. Le circuit s'obtient en faisant uen traduction littérale de la table de vérité du circuit. Si je dis qu'elle est naïve, c'est car il est possible de l'optimiser, de manière à éliminer des redondances cachées. Mais nous avions déjà vu cela dans le chapitre sur les incrémenteurs, aussi laissons cela pour plus tard.
En combinant deux demi-additionneurs de ce type, on obtient l'additionneur complet suivant. Les deux sorties de retenue des demi-additionneurs sont combinées avec une porte OU, pour calculer la retenue finale.
{| class="flexible"
|[[File:Full Adder Modules.svg|class=transparent|Composition d'un additionneur complet. On voit bien que celui-ci est composé de deux demi-additionneurs, en rouge et en bleu, auxquels on a ajouté une porte OU pour calculer la retenue finale.]]
|[[File:Full-adder.svg|300px|class=transparent|Circuit d'un additionneur complet.]]
|}
Le fait de combiner les deux retenues avec une porte OU n'est pas ce qu'il y a de plus intuitif. La table de vérité nous dit que ça fonctionne, mais on comprend mal pourquoi. Mais tout devient plus clair quand on sait que les deux retenues ne peuvent pas être à 1 en même temps. Même dans l'addition 1 + 1 + 1, seule une retenue est à 1. Soit l'addition des deux bits d'opérande donne naissance à une retenue, soit c'est l'addition ''résultat + retenue entrante''. La retenue sortant est donc à 1 quand une des deux addition donne une retenue. D'où l'usage d'une porte OU. Remarquez qu'une porte XOR donne le même résultat, vu que le cas où les deux retenues sont à 1 n'est jamais rencontré.
===La propagation et la génération des retenues===
L'additionneur complet que nous allons voir dans cette section sert à introduire les concepts de propagation, génération et absorption de retenue. Il s'agit de concepts très importants quand on étudie les additionneurs. l'idée part d'un principe très simple : la retenue sortante dépend de la retenue d'entrée. La relation entre les deux se résume à trois cas, qui dépendent de la valeur des deux bits additionnés, nommés A et B.
* Dans le premier cas, la retenue entrante est égale à la retenue sortante. On dit que la retenue entrante est propagée par l'additionneur.
* Dans le second cas, la retenue est forcée à 0 : la retenue sortante vaut 0, peu importe la valeur de la retenue entrante.
* Dans le troisième cas, la retenue est forcée à 1 : la retenue entrante vaut 1, peu importe la valeur de la retenue entrante.
[[File:Signaux P et G fournis par un demi-additionneur.png|vignette|Signaux P et G fournis par un demi-additionneur]]
Maintenant, créons un circuit qui nous dise si une retenue est propagée ou générée. Il a deux bits de sortie, nommés P et G : P pour ''Propagate'', G pour ''Generate''. Le bit P est à 1 si la retenue entrante est propagée, il est à 0 sinon. Le bit G est à 1 si une retenue est générée, à 0 sinon. Une retenue est considérée comme absorbée si elle n'est pas ni propagée ni générée, pas besoin d'un troisième bit pour gérer ce cas.
Il se trouve que ce circuit n'est autre qu'un demi-additionneur ! Pour vous en rendre compte, regardez la table de vérité d'un additionneur complet, illustrée ci-dessous. Lorsque les deux bits d'opérande sont à 0, la retenue sortante vaut toujours 0. Si ils sont tous deux à 1, alors la retenue sortante vaut 1. S'ils sont différents, alors retenues sortante et entrante sont égales. Le bit P est donc généré par une simple porte XOR. Quant au bit G, il est à 1 si les deux bits d'opérandes sont à 1, ce qui correspond à une porte ET. Il se trouve que ces deux portes forment un demi-additionneur !
{|class="wikitable"
|-
! Opérande 1 !! Opérande 2 !! Retenue entrante !! !! Retenue sortante
|- class="f_rouge"
||0||0||0|| ||0
|- class="f_rouge"
||0||0||1|| ||0
|- class="f_bleu"
||0||1||0|| ||0
|- class="f_bleu"
||0||1||1|| ||1
|- class="f_bleu"
||1||0||0|| ||0
|- class="f_bleu"
||1||0||1|| ||1
|- class="f_vert"
||1||1||0|| ||1
|- class="f_vert"
||1||1||1|| ||1
|}
Ensuite, créons un circuit qui prend ces deux sorties P et G, et calcule la retenue sortante en fonction. Le circuit en question a trois entrées : la retenue entrante, les deux bits P et G. La retenue sortante vaut 1 soit si une retenue est générée, soit si la retenue entrante est propagée et qu'elle vaut 1. En notant <math>{C_{in}}</math> et <math>{C_{out}}</math> les retenues entrantes et sortante, on a :
: <math>{C_{out}} = G + \left( P . C_{in} \right)</math>
Le circuit est donc composé d'une porte OU et d'une porte ET. En combinant un demi-additionneur avec le circuit de calcul de retenue sortante vu plus haut, on a :
[[File:Additionneur complet avec propagation et génération de retenue.png|class=transparent|centre|vignette|upright=2|Additionneur complet avec propagation et génération de retenue.]]
Certes, on retombe sur le circuit vu plus haut. Mais il est possible de remplacer le circuit en orange par un autre. Par exemple, il est possible de le remplacer par multiplexeur, qui choisit entre la retenue générée et la retenue entrante (propagée). Le choix se fait selon la valeur du bit P, qui chosiit entre propager la retenue et la générer.
[[File:Additionneur crée avec un multiplexeur.png|centre|vignette|upright=1.5|Additionneur crée avec un multiplexeur]]
Le circuit exact est illustré ci-dessous. Il semble utiliser plus de portes logiques que l'additionneur complet précédent. Cependant, nous verrons dans quelques chapitres qu'il est possible d'implémenter un multiplexeur avec seulement 6 transistors, voire moins ! L'implémentation utilise des portes à transmission, mais nous en reparlerons dans le chapitre sur les transistors, quand nous verrons les additionneurs à ''Manchester Carry Chain''. Au passage, une variante de ce circuit a été utilisée dans le processeur processeur 8086 d'Intel, comme on le verra dans le chapitre suivant.
[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
[[File:FulladderPG.png|vignette|upright=1|Additionneur complet avec deux sorties P et G.]]
Pour finir, sachez qu'il existe des additionneurs qui fournissent : le bit de résultat, deux sorties P et G qui indiquent si l'addition propage ou génère une retenue. Il ne s'agit pas d'additionneurs complets, car il manque de quoi calculer la retenue sortante. De tels additionneurs seront appelés des '''additionneurs partiels'''. De tels additionneurs sont utilisés dans certains additionneurs pour gagner en performance.
Enfin, il existe aussi ce que j'ai décidé d'appeler des '''additionneurs P/G''', qui sont des additionneurs complets auxquels on a ajouté deux sorties P et G, en plus de la sortie de retenue. Leur circuit est le même que celui d'un additionneur complet, auquel on a ajouté deux fils. Vu que ces deux sorties sont fournies par le premier demi-additionneur, ajouter ces deux sorties demande d'ajouter des fils, pas de portes logiques. De tels additionneurs sont aussi utilisés dans certains additionneurs pour gagner en performance.
===L'additionneur complet basé sur une porte à majorité===
Maintenant, voyons une dernière implémentation possible de l'additionneur complet. Mais avant de voir comment l'additionneur est implémenté, nous allons devoir faire un petit focus sur la retenue sortante. Reprenons la table de vérité, et regardons ce qu'il en est pour la retenue sortante uniquement.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante
|-
||0||0||0|| ||0
|-
||0||0||1|| ||0
|-
||0||1||0|| ||0
|-
||0||1||1|| ||1
|-
||1||0||0|| ||0
|-
||1||0||1|| ||1
|-
||1||1||0|| ||1
|-
||1||1||1|| ||1
|}
Vous remarquerez que la retenue sortante est égale au bit majoritaire, parmi les trois bits d'opérande. Si deux ou trois bits sont à 1, la retenue sortante vaut 1. Et réciproquement avec 0. Le circuit de calcul de la retenue peut donc être remplacé par une '''porte à majorité'''.
[[File:Additionneur crée avec une porte à majorité.png|centre|vignette|upright=2|Additionneur crée avec une porte à majorité]]
Mais une porte à majorité est assez gourmande en circuit, ce qui fait qu'un additionneur pareil ne serait pas pratique, ni utile. Du moins, ce serait le cas s'il n'y avait pas une possibilité d'optimisation extrêmement intéressante. Il est possible de calculer le 'bit de somme' à partir de la retenue sortante ! En effet, le bit de somme est l'inverse de la retenue sortante, sauf dans deux cas : les trois bits d'entrée sont à 0, où ils sont tous à 1. Le bit de somme vaut 0 dans le premier cas, 1 dans le second cas.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|- class="f_bleu"
||0||0||0|| ||0||0
|- class="f_vert"
||0||0||1|| ||0||1
|- class="f_vert"
||0||1||0|| ||0||1
|- class="f_vert"
||0||1||1|| ||1||0
|- class="f_vert"
||1||0||0|| ||0||1
|- class="f_vert"
||1||0||1|| ||1||0
|- class="f_vert"
||1||1||0|| ||1||0
|- class="f_rouge"
||1||1||1|| ||1||1
|}
En clair, 6 lignes de la table de vérité sur 8 peuvent se calculer avec une porte à majorité, le reste demandant quelques portes logiques pour faire la correction. L'implémentation en circuit demande donc trois choses :
* de calculer la retenue sortante et de quoi l'inverser ;
* un circuit qui vérifie si tous les bits opérande valent 0 : une porte NOR ou OU fait l'affaire ;
* un circuit qui vérifie s'ils valent tous 1, à savoir une porte ET.
Ensuite, on combine le résultat des trois circuits précédents pour obtenir le résultat final. La combinaison est le fait du circuit en jaune dans le schéma ci-dessous.
[[File:Full adder basé sur une modification de la retenue.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue]]
Le circuit en jaune est assez simple. Quand la porte ET sort un 1, la sortie doit être forcée à 1. Par contre, quand la porte OU sort un zéro, la sortie doit être forcée à 0. Il suffit donc d'enchainer un circuit de mise à 1 et un circuit de mise à 0. Nous avons vu dans le chapitre sur les opérations de masquage qu'il s'agit respectivement d'une porte OU et d'une porte ET.
[[File:Full adder basé sur une modification de la retenue interne, au complet.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue interne, au complet]]
Divers processeurs utilisaient des additionneurs complets de ce type : le fameux Z80, l'Intel 4004 et de l'Intel 8008 sont les exemples les plus notables. Pourtant, on pourrait s'étonner qu'un tel circuit ait existé. Il utilise beaucoup de portes logiques, a une profondeur logique supérieure : il n'a rien d'avantageux. Sauf qu'il était utilisé sur d'anciens processeurs, qui utilisaient la technologie dite TTL, différente de la technologie CMOS des transistors modernes. Et avec la technologie TTL, il est possible de fusionner plusieurs portes logiques ET et NOR en une seule porte logique ET/OU/NON ! Un additionneur complet construit ainsi ne prenait que deux portes logiques : une pour le calcul de la retenue sortante, une autre pour le reste du circuit.
==L'addition non signée==
Voyons maintenant un circuit capable d'additionner deux nombres entiers: l''''additionneur'''. Dans la version qu'on va voir, ce circuit manipulera des nombres strictement positifs. L'addition des nombres codés en complètement à deux sera vu dans une section ultérieure.
===L'additionneur série===
Il est possible d'additionner deux nombres bit par bit, gr$ace à un additionneur complet associé à plusieurs registres à décalages. Les opérandes sont placées chacune dans un registre à décalage, et l'additionneur complet utilise les bits sortants de ces registres à décalage. Le bit du résultat est envoyé au registre à décalage pour le résultat. La retenue de l'addition est stockée dans une bascule de 1 bit, en attente du prochain cycle d'horloge. Un tel additionneur est appelé un '''additionneur série'''. Il a été utilisé sur d'anciens ordinateurs dans les années 50-60, aussi bien des prototypes que des ordinateurs commerciaux.
[[File:Additionneur série.jpg|centre|vignette|upright=2.5|Additionneur série.]]
===L'additionneur à propagation de retenue===
L''''additionneur à propagation de retenue''' est aussi appelé l'additionneur ''ripple carry'', c'est son nom anglais. J'utiliserais occasionnellement le nom anglais dans ce qui suit, car celui-ci est plus court. Ilutilise un additionneur complet pour chaque colonne de l'addition. Pour gérer les retenues, il suffit ainsi de câbler des additionneurs complets les uns à la suite des autres, chacun envoyant sa retenue à la colonne suivante.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Notons la présence de la retenue sortante, qui est utilisée pour détecter les débordements d'entier, ainsi que pour d'autres opérations. Le bit de retenue final est souvent stocké dans un registre spécial du processeur (généralement appelé ''carry flag'').
Notez aussi, à droite du schéma précédent, l'entrée de retenue <math>\text{R}_\text{Entrée}</math>. Elle est très utile pour l'implémentation de certaines opérations comme l'inversion de signe, la soustraction, l'incrémentation, etc. Certains processeurs sont capables de faire une opération appelée ADC, ADDC ou autre nom signifiant ''Addition with Carry'', qui permet de faire le calcul A + B + Retenue (la retenue en question est la retenue sortante de l'addition précédente, stockée dans le registre ''carry flag''). Son utilité principale est de permettre des additions d'entiers plus grands que ceux supportés par le processeur. Par exemple, cela permet de faire des additions d'entiers 32 bits sur un processeur 16 bits.
[[File:Adder Network 3 Bit Sum Delay 003.svg|vignette|upright=0.5|Propagation de retenue dans l'additionneur.]]
Cet additionneur utilise très peu de portes logiques, ce qui fait qu'il était utilisé sur certains processeurs 8 et 16 bits, dont le budget en portes logiques était limité. Bien que très simple, cet additionneur est cependant peu performant. Son temps de calcul est proportionnel à la taille des opérandes. Par exemple, additionner deux opérandes de 32 bits prendra deux fois plus de temps que pour des opérandes de 16 bits. La raison est que les retenues doivent se propager à travers le circuit, du premier additionneur jusqu'au dernier.
L'addition étant une opération fréquente, d'autres additionneurs ont été inventés. Ils utilisent des optimisations qui utilisent plus de circuits pour gagner en rapidité. Mais avant de voir ces optimisations, nous allons voir s'il est possible d'optimiser les additionneurs à propagation de retenue.
===L'additionneur à saut de retenue===
L'additionneur à propagation de retenue peut être optimisé de deux manières. La première accélère la propagation de la retenue, en agissant au niveau des additionneurs complets. La ''Manchester carry chain'' est une optimisation de ce type, mais on ne peut pas encore expliquer à ce stade du cours, car elle optimisait les additionneurs complets au niveau des transistors eux-mêmes. Elle a été utilisée sur de nombreux processeurs connus, comme le 8086 d'Intel ou le Z80.
La seconde manière rend l'addition plus rapide dans certains cas. Le résultat est un additionneur dont le temps de calcul est variable. Le calcul prendra quelques cycles d'horloges avec certains opérandes, mais d'autres opérandes ne verront aucune amélioration. Il n'améliore pas le pire des cas, dans lequel la retenue doit être propagée du début à la fin, du bit de poids faible au bit de poids fort. Mais dans d'autres cas, il permet d'avoir le résultat en avance. L'optimisation en question donne l''''additionneur à saut de retenue''' (''carry-skip adder'').
Il est composé de blocs qui additionnent 4/5 bits, rarement plus. La taille d'un bloc varie d'un additionneur à l'autre, elle peut même varier dans l'additionneur, mais nous allons prendre un exemple où les blocs font 4 bits. L'idée est de détecter précocement si la retenue entrante est propagée à travers tout le bloc. Si la retenue est propagée dans tous le bloc, on peut directement l'envoyer sur la sortie de retenue, la retenue saute le bloc entier. Dans le cas contraire, on doit calculer la retenue normalement. Le choix entre les deux est le fait d'un multiplexeur. Toute la difficulté est de savoir comment commander le multiplexeur.
[[File:Carry skip adder, principe de base.png|centre|vignette|upright=2.5|Carry skip adder : principe de base]]
Pour savoir si une retenue est propagée dans un bloc entier, il faut utiliser des additionneurs complets, qui ont en plus une sortie P indiquant que l'additionneur propage la retenue. Le bloc propage une retenue si chaque additionneur complet propage la retenue, donc s'ils ont tous leur sortie P à 1. Un simple ET logique suffit à déterminer si c'est le cas. Le signal de commande du multiplexeur est donc un vulgaire ET entre toutes les sorties P des additionneurs complets du bloc.
[[File:Additionneur à saut de retenue.png|centre|vignette|upright=2.5|Calcul de la commande du MUX.]]
Voici ce que ça donne pour un bloc de 4 bits :
[[File:CSAdder4Bit.svg|centre|vignette|upright=1.5|Additionneur ''carry skip'' : bloc de 4 bits.]]
L'additionneur à saut de retenue est construit en assemblant plusieurs blocs de ce type.
[[File:BCSAdder16Bit.svg|centre|vignette|upright=3|Additionneur à saut de retenue.]]
===L'additionneur à sélection de retenue===
L''''additionneur à sélection de retenue''' utilise aussi des blocs, comme les additionneurs précédents. L'addition se fait en deux versions : une avec la retenue du bloc précédent valant zéro, et une autre version avec la retenue du bloc précédent valant 1. Il suffira alors de choisir le bon résultat avec un multiplexeur, une fois cette retenue connue. On gagne ainsi du temps en calculant à l'avance les valeurs de certains bits du résultat, sans connaître la valeur de la retenue. Petit détail : sur certains additionneurs à sélection de retenue, les blocs de base n'ont pas la même taille. Cela permet de tenir compte des temps de propagation des retenues entre les blocs.
[[File:Additionneur à sélection de retenue.png|centre|vignette|upright=1.5|Additionneur à sélection de retenue avec seulement deux blocs.]]
Dans les exemples du dessus, chaque sous-additionneur étaient des additionneurs à propagation de retenue. Mais ce n'est pas une obligation, et tout autre type d’additionneur peut être utilisé. Par exemple, on peut faire en sorte que les sous-additionneurs soient eux-mêmes des additionneurs à sélection de retenue, et poursuivre ainsi de suite, récursivement. On obtient alors un '''additionneur à somme conditionnelle''', plus rapide que l'additionneur à sélection de retenue, mais qui utilise beaucoup plus de portes logiques.
===Les additionneurs à anticipation de retenue===
Les '''additionneurs à anticipation de retenue''' calculent chaque retenue sans avoir à propager les retenues précédentes. Au lieu de calculer les retenues une par une, ils calculent toutes les retenues en parallèle, à partir des bits des opérandes. Une fois les retenues pré-calculées, il suffit de les additionner avec les deux bits adéquats, pour obtenir le résultat. Ces additionneurs sont composés de deux parties :
* un circuit qui pré-calcule les retenues, sans les propager, directement à partir des opérandes ;
* d'une couche d'additionneurs complets, qui additionnent chacun deux bits d'opérande et la retenue pré-calculée.
[[File:Additionneur à anticipation de retenue.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue.]]
Le calcul des retenues se fait en deux étapes. La première détermine si les retenues sont propagées ou générées, pour chaque colonne. Elle détermine, pour chaque colonne, les bits P et G qui indiquent respectivement : qu'une retenue a été générée sur cette colonne, que la colonne propage la retenue précédente. La seconde étape utilise ces signaux P et G pour déterminer la retenue sur chaque colonne. Le circuit de calcul des retenues est donc composé de deux parties : une couche de demi-additionneurs pour générer les signaux P et G, un circuit de calcul des retenues proprement dit.
[[File:Circuit complet d'un additionneur à anticipation de retenue.png|centre|vignette|upright=2|Circuit complet d'un additionneur à anticipation de retenue.]]
Pour rappel, la retenue de la colonne i, notée <math>C_i</math> est égale à :
: <math>G_i + (P_i . C_{i-1})</math>, avec G le signal de génération de retenue, P le signal de propagation de retenue.
Si on utilisait cette formule sans trop réfléchir, on retomberait sur un additionneur à propagation de retenue inutilement compliqué. L'astuce des additionneurs à anticipation de retenue consiste à remplacer le terme Ci−1 par sa valeur calculée avant. Par exemple, je prends un additionneur 4 bits. Je dispose de deux nombres A et B, contenant chacun 4 bits : A3, A2, A1, et A0 pour le nombre A, et B3, B2, B1, et B0 pour le nombre B. Si j'effectue les remplacements, j'obtiens les formules suivantes :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Ces formules nous permettent de déduire la valeur d'une retenue directement : il reste alors à créer un circuit qui implémente ces formules, et le tour est joué.
[[File:Four bit adder with carry lookahead.svg|centre|vignette|upright=1.5|Additionneur à anticipation de retenue de 4 bits.]]
Plus haut, j'ai dit que le circuit était composé de trois couches : une couche de demi-additionneur pour les signaux P et G ; un circuit d'anticipation de retenue, puis l'addition finale. Mais les deux bits d'opérandes sont déjà additionnés dans la couche de demi-additionneur, autant réutiliser le résultat pour l'additionner à la retenue. Pour éliminer toute redondance de ce type, il est possible d'utiliser des additionneurs partiels (des additionneurs où la sortie de retenue a été remplacée par deux sorties P et G). L'additionneur final est alors composé de plusieurs additionneurs complets de ce type, couplé à un circuit d'anticipation de retenue.
[[File:Cla4bits.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue de 4 bits.]]
Les additionneurs à anticipation de retenue sont plus rapides que les additionneurs à propagation de retenue. La raison est que les retenues sont calculées rapidement, dans un temps plus petit qu'en propageant la retenue. Pour rentrer dans les détails mathématiques, le temps de calcul n'est pas proportionnel au nombre de bits, mais proportionnel au logarithme de ce nombre de bits. Par contre, le gain en temps de calcul s'accompagne d'un cout en prote logique conséquent. Le circuit qui calcule les retenues anticipées utilise beaucoup de portes logiques.
La conséquence est qu'utiliser un additionneur à anticipation de retenue est une solution pratique pour additionner 4 ou 8 bits. Mais pour des opérandes plus grandes, de 16 ou 32 bits, cela utiliserait trop de portes logiques. Une partie de ce cout en portes logiques est cependant lié à des redondances. Si on analyse deux retenues différentes, à des colonnes différentes. Les circuits qui calculent ces deux retenues seront différents, mais ils contiendront des sous-circuits identiques. Pour vous en rendre compte, reprenons les quatre équations obtenues précédemment et essayez de trouver des redondances :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Vous devriez trouver :
* le terme ''P0 · C0'' dans toutes les lignes ;
* les termes ''P1 · P0 · C0'' et ''P1 · P0'' dans les trois dernières lignes ;
* le terme ''P2 · P1 · P0 · C0'' ; ''P2 · P1 · G0'' et ''P2 · G1'' dans les deux dernières lignes.
===Les additionneurs à calcul parallèle de préfixe===
Il existe plusieurs manières d'exploiter de telles redondances, exploitées dans les '''additionneurs à calcul parallèle de préfixe'''. L'optimisation apportée est de générer des signaux ''propagate'' et ''generate'' pour des groupes de 2, 3, 4, ..., N colonnes. Par exemple, il est possible de générer un signal ''P 0 vers 3'', qui indique si la retenue de la première colonne est propagée jusqu'à la 4ème colonne. Un autre exemple est un signal qui indique qu'une retenue a été générée entre les colonnes 4 à 7 génèrent une retenue ou non. En clair, les signaux P et G ont maintenant un intervalle, qui précise de quelle colonne vers quelle colonne se fait la propagation, ou entre quelles colonnes se fait la génération.
Il est possible de combiner les signaux P et G de deux groupes de colonne, s'ils sont contiguës (ils peuvent aussi se recouvrir). Par exemple, il est possible de calculer les bits P et G pour les colonnes 0 à 10, à partir des deux signaux P/G des colonnes 0-4 et 5-10. Pour cela, les équations sont assez simples. Si je prends deux groupes nommés A et B, avec A pour les colonnes de poids fort et B celles de poids faible, on a :
: <math>P_{sortie} = P_A . P_B</math>
: <math>G_{sortie} = G_A . P_A . G_B</math>
Pour les bits des colonnes 2, 4, 8, 16, 32, 64, et autres, le calcul est simple. L'idée est de grouper les colonnes par groupes de 2. Puis, on calcule chaque groupe de 4 bits à partir de deux groupes de 2. Puis les groupes de 8 bits sont calculés à partir de deux groupes de 4 bits, et ainsi de suite pour toute puissance de deux.
[[File:Squelette d'un additionneur à calcul parallèle de préfixe.jpg|centre|vignette|upright=2|Squelette d'un additionneur à calcul parallèle de préfixe]]
Les seules difficultés surviennent pour les colonnes dont le nombre n'est pas une puissance de deux. Il y a plusieurs manières pour subdiviser ces colonnes en intervalles plus petits et combiner le tout. Et elles donnent chacune des additionneurs différents. Ils portent les noms d'''additionneur de Ladner-Fisher'', d'''additionneur de Brent-Kung'', d'''additionneur de Kogge-Stone'', etc. Dans le cas général, l'additionneur de Kogge-Stone est un des plus rapide, mais aussi un des plus gourmand en circuits, alors que c'est l'inverse pour l'additionneur de Brent-Kung. Les deux additionneurs sont illustrés ci-dessous, pour des opérandes de 8 bits. Dans ceux-ci, chaque losange calculent les bits P et G, à partir des signaux P et G de deux groupes précédents.
{|
|[[File:Brent-kung-8-bit.png|vignette|Brent-kung-8-bit]]
|[[File:Kogge-stone-8-bit.png|vignette|upright=1.2|Kogge-stone-8-bit]]
|}
===Les additionneurs avec anticipation de retenue entre plusieurs additionneurs===
Dans cette section, nous allons voir des additionneurs conçus en assemblant des additionneurs à plus simples, qui additionnent environ 4 à 5 bits, parfois plus, parfois moins. Ces additionneurs simples seront nommés ''blocs'' dans ce qui suit, et l'un d'entre eux est illustré ci-contre. Chaque bloc prend en entrée un morceau des deux opérandes à additionner, mais aussi une retenue d'entrée.
Les blocs sont tous identiques dans le cas le plus simple, mais il est possible d'utiliser des blocs de taille variable. Par exemple, le premier bloc peut avoir des opérandes de 6 bits, le second des opérandes de 7 bits, etc. Faire ainsi permet de gagner un petit peu en performances, si la taille de chaque bloc est bien choisie. La raison est une question de temps de propagation des retenues. La retenue met plus de temps à se propager à travers 8 blocs qu'à travers 4, ce qui prend plus de temps qu'à travers 2 blocs, etc. En tenir compte fait que la taille des blocs tend à augmenter ou diminuer quand on se rapproche des bits de poids fort.
====Le calcul parallèle de la retenue====
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Plus haut, nous avons vu l'anticipation de retenue. Il est possible de l'adapter pour fonctionner avec des blocs. Une première idée est d'utiliser plusieurs additionneurs à anticipation de retenue, chacun étant un bloc. Vu qu'il s'agit d'additionneurs, ils disposent d'une sortie de retenue pour la retenue sortante, et d'une entrée de retenue pour la retenue entrante. Par exemple, le schéma ci-contre illustre un additionneur 4 bits de ce type.
De ce fait, il est possible de propager les retenues d'un bloc à l'autre. par exemple, on peut utiliser 4 additionneurs à anticipation de retenue de 4 bits, et propager les retenues entre eux. Le premier bloc calcule une retenue, qui est propagée au second bloc. Le second bloc calcule lui aussi une retenue, qui est propagée au troisième bloc, etc.
[[File:4008 Functional Diagram.svg|vignette|upright=1|4008 Functional Diagram]]
Le gain en performance est significatif, mais il est possible de faire presque aussi rapide, tout en économisant beaucoup de circuits. L'idée est de simplifier les additionneurs 4 bits, afin de ne pas utiliser une anticipation de retenue complète. L'anticipation de retenue n'est utilisée que pour la retenue sortante, alors que les bits de somme sont calculées avec propagation de retenue. La retenue sortante est donc calculée en parallèle de l'addition. Chaque bloc contient, à côté d'un additionneur proprement dit, un circuit qui calcule la retenue sortante. Il existe de nombreuses manières de calculer la retenue sortante.
Le gain en portes logiques est assez significatif. En effet, on se passe de circuits d'anticipation de retenue pour les 4 bits de somme, on ne garde que celui pour la retenue sortante. Le fait d'utiliser la propagation de retenue pour calculer les 4 bits de somme est censé avoir un léger cout en performance, mais il se trouve que ce cout est très faible. En effet, le résultat n'est connu qu'une fois les retenues propagées jusqu'au dernier bloc. Et malgré l'anticipation des retenues, la propagation est assez lente. les additionneurs à propagation de retenue auront terminé leur travail bien avant.
Un exemple est celui de l'additionneur CMOS 4008, un additionneur de 4 bit. Il est composé en trois sections. Une première couche de demi-additionneurs calcule les signaux P et G utilisés à la fois pour l'anticipation de la retenue sortante, que pour calculer les bits de somme. Le circuit de calcul de la retenue sortante utilise l'anticipation de retenue. Le reste du circuit fait l'addition en propageant les retenues.
[[File:CMOS 4008, circuit découpé en sections.png|centre|vignette|upright=1.5|CMOS 4008, circuit découpé en sections]]
====Les additionneurs à anticipation de retenue multi-niveau====
Il est possible d'utiliser l'anticipation de retenue entre deux blocs. Par exemple, il est possible de créer un additionneur 16 bits en utilisant 4 additionneurs/blocs de 4 bits. L'additionneur utilisé peut être un additionneur à propagation de retenue, ou à anticipation de retenue, peu importe.
Pour cela, les additionneurs de 4 bits doivent fournir deux sorties P et G. Les deux sorties indiquent que le bloc soit a généré une retenue, soit a propagé la retenue entrante. Par propagé, on veut dire que la retenue entrante du bloc s'est retrouvée sur la sortie de retenue du bloc. C'est le cas si les 4 colonnes propagent la retenue, ce qui signifie que :
: <math>P_\text{4 bits} = P_0 \cdot P_1 \cdot P_2 \cdot P_3</math>
Pour la génération, cela veut dire qu'une colonne a générée une retenue, qui a été propagée jusqu'à la sortie. En clair, pour chaque colonne, il faut que le G = 1, et que les signaux P des colonnes soient à 1. Pour les 4 colonnes, cela donne :
: <math>G_\text{4 bits} = G_3 + G_2 \cdot P_3 + G_1 \cdot P_3 \cdot P_2 + G_0 \cdot P_3 \cdot P_2 \cdot P_1</math>
[[File:Cla4bitsPG.png|centre|vignette|upright=2|Additionneur à anticipation de retenue de 4 bits.]]
Pour combiner 4 blocs de 4 bits, on devra donc combiner 4 bits P et G, avec un circuit d'anticipation de retenue.
[[File:16-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 16 bits.]]
Il est même possible d'aller plus loin et de combiner 4 additionneurs précédents pour obtenir un additionneur 64 bits.
[[File:64-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 64 bits.]]
Le résultat est un additionneur à plusieurs niveaux d’anticipation de retenue. Une anticipation de retenue dans un blocs de 4 bits (facultative), entre blocs de 4 bits, entre blocs de 16 bits. La même logique peut être utilisée avec des blocs de taille différente de 4, 16 et 64 bits.
==L'addition signée et la soustraction==
Après avoir vu l'addition, il est logique de passer à la soustraction, les deux opérations étant très proches. Si on sait câbler une addition entre entiers positifs, câbler une soustraction n'est pas très compliqué. De plus, la soustraction permet de faire des additions de nombres signés.
===Le soustracteur pour opérandes entiers===
Pour soustraire deux nombres entiers, on peut adapter l'algorithme de soustraction utilisé en décimal, celui que vous avez appris à l'école. Celui-ci ressemble fortement à l'algorithme d'addition : on soustrait les bits de même poids, et on propage éventuellement une retenue sur la colonne suivante. À la différence de l'addition, la retenue est soustraite, et non ajoutée. La table de soustraction nous dit quel est le résultat de la soustraction de deux bits. La voici :
* 0 - 0 = 0 ;
* 0 - 1 = 1 et une retenue ;
* 1 - 0 = 1 ;
* 1 - 1 = 0.
[[File:Algebra1 05 fig014.svg|centre|vignette|Soustraction en binaire, avec les retenues en rouge.]]
La table de soustraction peut servir de table de vérité pour construire un circuit qui soustrait deux bits. Celui-ci est appelé un '''demi-soustracteur'''. Il ressemble beaucoup à un demi-additionneur, les différences se résumant à une porte NON ajoutée pour le calcul de la retenue.
[[File:Half Subtractor Vektor.svg|centre|vignette|Demi-soustracteur.]]
Comme pour l'additionneur, deux demi-soustracteurs peuvent être combinés pour donner un '''soustracteur complet'''. Le calcul de la retenue se fait en combinant les deux retenues des demi-soustracteurs avec une porte OU. Les soustracteurs complets sont utilisés pour créer des soustracteurs à propagation de retenue ou tout autre circuit soustracteur, sur le même modèle que les additionneurs.
[[File:FullSubtractor.svg|centre|vignette|upright=2|Soustracteur complet.]]
Vous l'aurez sans doute remarqué, mais le demi-soustracteur ressemble beaucoup au demi-additionneur. La seule différence est une porte NON ajoutée au bon endroit dans le demi-soustracteur.
{|
|[[File:Half-adder.svg|vignette|Demi-additionneur]]
|[[File:Half Subtractor Vektor.svg|vignette|upright=1.3|Demi-soustracteur]]
|}
La conséquence est qu'il est possible de créer un circuit capable de faire à la fois des additions et des soustractions. L'idée est de remplacer la porte NON du demi-soustracteur par un inverseur commandable. Si on envoie un zéro sur l'entrée de commande, l'inverseur commandable se comportera comme une porte OUI. Le circuit sera alors identique à un demi-additionneur. Si on envoie un 1 sur l'entrée de commande, l'inverseur commandable se comportera comme une porte NON. le circuit sera alors un demi-soustracteur. Il faut faire cela pour chaque demi-soustracteur, ce qui donne ceci pour un soustracteur complet. le résultat est alors appelé un '''additionneur-soustracteur complet'''.
[[File:Additionneur-soustracteur complet.png|centre|vignette|upright=2|Additionneur-soustracteur complet]]
===L'additionneur-soustracteur pour opérandes codées en complément à deux===
Étudions le cas de la soustraction en complément à deux, dans l'objectif de créer un circuit soustracteur. Vous savez sûrement que a−b et a+(−b) sont deux expressions équivalentes. Et en complément à deux, − b = not(b) + 1. Dit autrement, a − b = a + not(b) + 1. On pourrait se dire qu'il faut deux additionneurs pour faire le calcul, mais la majorité des additionneurs possède une entrée de retenue pour incrémenter le résultat de l'addition. Un soustracteur en complément à deux est donc simplement composé d'un additionneur et d'un inverseur.
[[File:Additionneur soustracteur.png|centre|vignette|upright=2|Soustracteur en complément à deux.]]
Il est possible de créer un circuit capable d'effectuer soit une addition, soit une soustraction : il suffit de remplacer l'inverseur par un inverseur commandable, qui peut être désactivé. On a vu comment créer un tel inverseur commandable dans le chapitre sur les circuits combinatoires. On peut remarquer que l'entrée de retenue et l'entrée de commande de l'inverseur sont activées en même temps : on peut fusionner les deux signaux en un seul.
[[File:Additionneur-soustracteur en complément à deux.png|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux.]]
Une implémentation alternative est la suivante. Elle remplace l'inverseur commandable par un multiplexeur.
[[File:4-bit ripple carry adder-subtracter.svg|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux, version alternative.]]
===L'additionneur-soustracteur pour opérandes codées en signe-magnitude===
Passons maintenant aux nombres codés en signe-valeur absolue, les deux opérandes étant notées A et B. Suivant les signes des deux opérandes, on a quatre cas possibles : A + B, A − B (B négatif), −A + B (A négatif) et −A − B (A et B négatifs). Une astuce est que le circuit n'a besoin que de calculer A + B et A − B : il peut les inverser pour obtenir − A − B ou B − A. A + B et A − B peuvent se calculer avec un additionneur-soustracteur, reste à corriger le résultat. Il suffit de lui ajouter un inverseur commandable pour obtenir le circuit d'addition finale.
[[File:Additionneur en signe-valeur absolue.png|centre|vignette|upright=2|Additionneur en signe-valeur absolue.]]
Toute la difficulté tient dans le calcul du bit de signe du résultat, quand interviennent des soustractions. Autant l'addition de deux nombres de même signe (A + B et −A − B) ne pose aucun problème, autant les soustractions posent problème (A − B et −A + B). Suivant que <math>A<B</math> ou que <math>A>B</math>, le signe du résultat ne sera pas le même. Déterminer le signe du résultat se fait en regardant les bits de débordement d'entier, comme on le verra plus bas.
===L'additionneur-soustracteur pour opérandes codées en représentation par excès===
Passons maintenant aux nombres codés en représentation par excès. On pourrait croire que ces nombres s'additionnent comme des nombres non-signés, mais ce serait oublier la présence du biais, qui pose problème. Dans les cas de nombres signés gérés avec un biais, voyons ce que donne l'addition de deux nombres :
: <math>( n_1 + biais ) + ( n_2 + biais ) = n_1 + n_2 + 2 \times biais</math>
Or, le résultat correct serait :
: <math>n_1 + n_2 + biais</math>
En effectuant l'addition telle quelle, le biais est compté deux fois. On doit donc le soustraire après l'addition pour obtenir le résultat correct.
Même chose pour la soustraction qui donne ceci :
: <math>(n_1 + biais) - (n_2 + biais) = n_1 - n_2</math>
Or, le résultat correct serait :
: <math> ( n_1 - n_2 ) + biais </math>
Il faut rajouter le biais pour obtenir l'exposant correct.
On a donc besoin de deux additionneurs/soustracteurs : un pour additionner/soustraire les représentations binaires des opérandes, et un autre pour ajouter/retirer le biais en trop/manquant.
==L'additionneur BCD==
Maintenant, voyons un additionneur qui additionne deux entiers au format BCD. Pour cela, nous allons devoir passer par deux étapes. La première est de créer un circuit capable d'additionneur deux chiffres BCD. Ensuite, nous allons voir comment enchaîner ces circuits pour créer un additionneur BCD complet.
===L'additionneur BCD qui fait l'opération chiffre par chiffre===
Nous allons commencer par voir un additionneur qui additionne deux chiffres en BCD, une sorte d'équivalent BCD de l'additionneur complet. Il fournit un résultat sur 4 bits et une retenue qui est mise à 1 si le résultat dépasse 10 (la limite d'un chiffre BCD). Les deux opérandes sont des chiffres BCD codés sur 4 bits et sont additionnés en binaire par un additionneur des plus normaux, similaire à ceux vus plus haut. Le résultat est alors un entier codé en binaire, sur 5 bits, qu'on corrige/convertit pour obtenir un chiffre BCD et une retenue sortante.
Pour corriger le résultat, une idée intuitive serait de prendre le résultat et de faire une division par 10. Le quotient donne la retenue, alors que le reste est le résultat, le chiffre BCD. Mais un circuit diviseur par 10 utilise beaucoup de portes logiques, ce qui ne vaut pas le coup. Une autre méthode détecte si le résultat est égal ou supérieur à 10, ce qui correspond à un "débordement" (on dépasse les limites d'un chiffre BCD). Si le résultat est plus petit que 10, il n'y a rien à faire : le résultat est bon et la retenue est de zéro. Par contre, si le résultat vaut 10 ou plus, il faut corriger le résultat et générer une retenue à 1.
Il faut donc ajouter un circuit qui détecte si le résultat est supérieur à 9, qui calcule directement la retenue. Ce circuit peut se fabriquer simplement à partir de sa table de vérité, ou en utilisant les techniques que nous verrons dans un chapitre ultérieur sur les comparateurs. La solution la plus simple est clairement d'utiliser la table de vérité, ce qui est très simple, assez pour être laissé en exercice au lecteur. Pour comprendre comment corriger le résultat, établissons une table de vérité qui associe le résultat et le résultat corrigé. L'entrée vaut au minimum 10 et au maximum 9 + 9 = 18. On considère la sortie comme un tout, la retenue étant un 5ème bit, le bit de poids fort.
{|class="wikitable"
|-
! colspan="5" | Entrée
!
! rowspan="10" |
! Retenue
! Résultat corrigé (sans retenue)
! interprétation de la sortie en binaire (retenue inclue)
|-
| 0 || 1 || 0 || 1 || 0
| (10)
| 1 || 0000
| (16)
|-
| 0 || 1 || 0 || 1 || 1
| (11)
| 1 || 0001
| (17)
|-
| 0 || 1 || 1 || 0 || 0
| (12)
| 1 || 0010
| (18)
|-
| 0 || 1 || 1 || 0 || 1
| (13)
| 1 || 0011
| (19)
|-
| 0 || 1 || 1 || 1 || 0
| (14)
| 1 || 0100
| (20)
|-
| 0 || 1 || 1 || 1 || 1
| (15)
| 1 || 0101
| (21)
|-
| 1 || 0 || 0 || 0 || 0
| (16)
| 1 || 0110
| (22)
|-
| 1 || 0 || 0 || 0 || 1
| (17)
| 1 || 0111
| (23)
|-
| 1 || 0 || 0 || 1 || 0
| (18)
| 1 || 1000
| (24)
|}
En analysant le tableau, on voit que pour corriger le résultat, il suffit d'ajouter 6. La raison est que le résultat déborde d'un nibble à 16 en binaire, mais à 10 en décimal : il suffit d'ajouter la différence entre les deux, à savoir 6, et le débordement binaire fait son travail. Donc, la correction après une addition est très simple : si le résultat dépasse 9, on ajoute 6.
On peut maintenant implémenter l'additionneur BCD, en combinant le comparateur avec 10, le circuit de correction, et l'additionneur. La première solution calcule deux versions du résultat : la version corrigée, la version normale. Le choix entre les deux est réalisée par un multiplexeur, commandé par le comparateur.
[[File:Additionneur BCD.png|centre|vignette|upright=1.5|Additionneur BCD]]
L'autre solution utilise un circuit commandable qui soit additionne 6, soit ne fait rien. Le choix entre les deux est commandé par le bit calculé par le comparateur.
[[File:Additionneur BCD, seconde version.png|centre|vignette|upright=1.5|Additionneur BCD, seconde version.]]
Une version alternative du circuit précédent est la suivante. Il contient deux additionneurs : un pour additionner les deux chiffres BCD, un autre pour additionner 6 si besoin. Le résultat du comparateur est directement utilisé pour générer l'opérande du second additionneur : 0 ou 6. Le circuit est simple à concevoir, mais gaspille beaucoup de circuit. Idéalement, il vaudrait mieux utiliser un circuit combinatoire d'addition avec une constante.
[[File:Adder 4 Bit BCD.svg|centre|vignette|upright=2|Additionneur BCD, circuit complet.]]
Pour obtenir un additionneur BCD complet, il suffit d’enchaîner les additionneurs précédents, comme on le ferait avec les additionneurs complets dans un additionneur à propagation de retenue. Au final, l'additionneur BCD est beaucoup plus compliqué qu'un additionneur normal, car il rajoute un comparateur ">9", un petit additionneur pour ajouter 6 et éventuellement d'autres circuits. De plus, il est difficile d'appliquer les optimisations disponibles sur les additionneurs non-BCD. Notamment, les circuits d'anticipation de retenue sont totalement à refaire et le résultat est relativement compliqué. C'est ce qui explique pourquoi le BCD a progressivement été abandonné au profit du binaire simple.
La soustraction en BCD se fait comme en binaire : le nombre à soustraire est remplacé par son complément, le circuit additionne le complément et l'autre opérande, le débordement d'entier fait que le résultat marche. Sauf qu'ici, le complément est un complément à 9. Il se calcule chiffre par chiffre : chaque chiffre est remplacé par (9 - le chiffre en question).
===L'additionneur BCD par ajustement décimal===
L'additionneur BCD précédent effectuait son travail chiffre BCD par chiffre BCD, mais il existe des additionneurs BCD qui font autrement. Sur les premiers processeurs x86, il n'y avait pas d'opération d'addition BCD proprement dit, seulement une addition binaire normale de 8, 16 ou 32 bits. Par contre, elle était secondée par une opération dite d''''ajustement décimal''' qui transformait un nombre binaire en nombre codé en BCD. L'opération d'ajustement décimal prenait un opérande de 8 bits codé en binaire et fournissait un résultat de la même taille, c'est à dire deux chiffres BCD. Effectuer une addition BCD demandait donc de faire deux opérations à la suite : une addition binaire simple, suivie par l'opération d'ajustement décimal. Cela permettait de gérer des nombres entiers en binaire usuel et des entiers BCD sans avoir deux instructions d'addition séparées pour les deux, sans compter que cela simplifiait aussi les circuits d'addition.
L'ajustement décimal s'effectue en ajoutant une constante bien précise à l'opérande à convertir en BCD. L'idée est que la constante est découpée en morceaux de 4 bits, correspondant chacun à un chiffre BCD de l'opérande, chaque morceau contenant soit un 0, soit 6. Cela permet d'ajouter soit 0, soit 6, à chaque chiffre BCD, et donc de le corriger. La propagation des retenues d'un chiffre à l'autre est effectuée automatiquement par l'addition binaire de la constante. L'opération d'ajustement décimal calcule automatiquement la constante. Elle découpe l'opérande en ''nibbles'', vérifie si chaque ''nibble'' est supérieur ou égal à 10, puis détermine la valeur de chaque ''nibble'' de la constante finale. Par exemple, si je prends l'opérande 1001 1110, le ''nibble'' de poids faible déborde, alors que celui de poids fort non. La constante sera donc 0000 0110 : 0x06. Inversement, si le ''nibble'' de poids fort déborde et pas celui de poids faible, la constante sera alors 0x60. Et la constante est de 0x66 si les deux ''nibbles'' débordent, de 0x00 si aucun ne déborde.
Le circuit d’ajustement décimal est donc composé de trois étapes : deux étapes pour calculer la constante, et un circuit d'addition pour additionner cette constante au nombre de départ. La première étape découpe l'opérande en morceaux de 4 bits, en chiffres BCD, et vérifie si chacun d'entre eux vaut 10 ou plus. La seconde étape prend les résultats de la première étape, et les combine pour calculer la constante. Enfin, on trouve l'addition finale, qui était réalisée par un circuit d'addition utilisé à la fois pour l'ajustement décimal et l'addition binaire. La différence entre une addition normale et une opération d'ajustement décimal tient dans le fait que les deux premières étapes sont désactivées dans une addition normale.
[[File:Additionneur BCD parallèle.png|centre|vignette|upright=1.5|Additionneur BCD parallèle]]
===L'additionneur biquinaire===
Les entiers BCD ne sont qu'un des encodages hybrides entre décimal et binaire. L'encodage biquinaire est l'un d'entre eux et nous allons faire un rappel rapide à ce sujet. Pour simplifier, un chiffre encodé en biquinaire est composé de deux parties : un bit, couplé à une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits (0, 1, 2, 3 et 4). Le bit indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9.
Additionner deux nombres de biquinaire demande donc d'additionner deux parties quinaires encodées en ''one-hot'' et d'additionner deux bits. Mais attention : il faut tenir compte de la retenue de l'addition des parties quinaires. Et idéalement, il faut aussi tenir compte d'une retenue entrante, provenant de l'addition de la colonne de chiffres précédente. Toute la difficulté vient de la création de l'additionneur ''one-hot''. Heureusement, vu qu'il n'y a que 4-5 bits à additionner, il est souvent fabriqué à partir de sa table de vérité.
[[File:Additionneur bi-quinaire.png|centre|vignette|upright=2|Additionneur bi-quinaire]]
Un avantage du biquinaire est que le calcul du complément à 9 est très simple. Il faut pour cela : inverser la partie binaire avec une porte NON, puis inverser l'ordre des bits de la partie quinaire. Concrètement, le bit de poids faible devient le bit de poids fort, et ainsi de suite. Par exemple, une partie quinaire 01000 devient 00010, 10000 devient 00001, 00100 ne change pas, etc. Le tout peut se calculer avec une porte NON et 5 multiplexeurs.
===L'additionneur BCD avec calculs intermédiaires en biquinaire===
L'ordinateur IBM 1401, un ancien ''mainframe'' des années 60, utilisait un additionneur BCD un peu particulier. Les nombres étaient encodés en BCD dans la mémoire de l'ordinateur, mais les circuits de calcul utilisaient la représentation biquinaire. Lors d'un calcul, le processeur de l'ordinateur traduisait les chiffres BCD en représentation biquinaire, faisait une addition en biquinaire, avant de traduire le résultat en BCD normal.
Pour être précis, l'IBM 1401 utilisait une variante du biquinaire. L'encodage biquinaire de l'IBM 1401 est le suivant : la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre pair encodé par la partie quinaire. Si l'IBM 1401 utilisait cette variante du biquinaire, c'est car elle donnait des circuits de conversion BCD-biquinaire plus économes en portes logiques et plus rapides.
La partie binaire est le bit de poids faible du chiffre BCD, la partie biquinaire est calculée par un simple décodeur qui prend en entrée le chiffre BCD, amputé de son bit de poids faible. La traduction inverse demande d'utiliser un encodeur, à la place du décodeur. Par contre, le circuit d'addition biquinaire était plus compliqué du fait de la gestion des retenues. L'addition des parties binaires et quinaires se faisait en parallèle, dans deux additionneurs séparés. Cependant, l'addition des parties binaire fournit une retenue, qu'il faut prendre en compte. Pour cela, l'IBM 1401 disposait d'un troisième additionneur qui fournissait le résultat final, encodé en biquinaire.
[[File:Additionneur bi-quinaire de l'IBM 1401.png|centre|vignette|upright=2|Additionneur biquinaire de l'IBM 1401]]
Une implémentation moderne demanderait d'utiliser des portes ET combinées à des portes OU, le circuit pouvant être construit simplement à partir de sa table de vérité. Sur l'IBM 1401, le circuit était cependant différent, en raison de l'utilisation de OU câblés, des croisements de fils qui fonctionnent comme des portes OU, que nous n'avons pas encore vu pour le moment, mais qui seront détaillés dans quelques chapitres. Les OU câblés étaient utilisés pour simplifier le design du circuit, mais demandaient des portes logiques spécifiques, ce qui collait avec le fait que ce ''mainframe'' utilisait des transistors en Germanium. L'implémentation exacte est décrite dans cet article de blog, mais je ne recommande sa lecture qu'à ceux qui savent ce qu'est un OU câblé :
* [https://www.righto.com/2015/10/qui-binary-arithmetic-how-1960s-ibm.html Qui-binary arithmetic: how a 1960s IBM mainframe does math].
==Les débordements d'entier lors d'une addition/soustraction==
Les instructions arithmétiques manipulent des entiers codés sur un nombre fixe de bits, qui ne peuvent prendre leurs valeurs que dans un intervalle. Pour les nombres positifs, un ordinateur qui code ses entiers sur n bits pourra coder tous les entiers allant de 0 à <math>2^n -1</math>. Pour les nombres négatifs, l'intervalle est différent et dépend de la représentation utilisée. Dans le cas général, l'ordinateur peut coder les valeurs comprises de <math>N_\text{min}</math> à <math>N_\text{max}</math>. Si le résultat d'un calcul sort de cet intervalle, il ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math> , alors que la valeur basse vaut <math>N_\text{min} - 1</math> (avec <math>N_\text{max}</math> et <math>N_\text{min}</math> respectivement la plus grande et la plus petite valeur codable par l'ordinateur).
===La correction des débordements d'entier : l'arithmétique saturée===
Quand un débordement d'entier survient, tous les circuits de calcul ne procèdent pas de la même manière. Dans les grandes lignes, il y a deux réactions possibles : soit on corrige automatiquement le résultat du débordement, soit on ne fait rien et on se contente de détecter le débordement.
Si le débordement n'est pas corrigé automatiquement, le circuit ne conserve que les bits de poids faibles du résultat. Les bits en trop sont simplement ignorés. On dit qu'on utilise l''''arithmétique modulaire'''. Le problème avec ce genre d'arithmétique, c'est qu'une opération entre deux grands nombres peut donner un résultat très petit. Par exemple, si je dispose de registres 4 bits et que je souhaite faire l'addition 1111 + 0010 (ce qui donne 15 + 2), le résultat est censé être 10001 (17), ce qui est un résultat plus grand que la taille d'un registre. En conservant les 4 bits de poids faible, j’obtiens 0001 (1). En clair, un résultat très grand est transformé en un résultat très petit. Cela peut poser problèmes si on travaille uniquement avec des nombres positifs, mais c'est aussi utilisé pour coder des nombres en complément à deux.
D'autres circuits utilisent ce qu'on appelle l''''arithmétique saturée''' : si un calcul génère un débordement, on arrondi le résultat au plus grand entier supporté par le circuit. Les circuits capables de calculer en arithmétique saturée sont un peu plus complexes, vu qu'il faut rajouter des circuits pour corriger le résultat en cas de débordement. Il suffit généralement de rajouter un ''circuit de saturation'', qui prend en entrée le résultat et le corrige en cas de débordement. Ce circuit de saturation met la valeur maximale en sortie si un débordement survient, mais se contente de recopier le résultat du calcul sur sa sortie s'il n'y a pas de débordement. Typiquement, il est composé d'une couche de multiplexeurs, qui sélectionnent quelle valeur mettre sur la sortie : soit le résultat du calcul, soit le plus grand nombre entier géré par le processeur, soit le plus petit (pour les nombres négatifs/soustractions).
L'arithmétique saturée est utilisée pour les additions et soustractions, mais c'est plus rare pour les multiplications/divisions. Une des raisons est que le résultat d'une addition/soustraction prend un bit de plus que le résultat, là où les multiplications doublent le nombre de bits. Quand une addition déborde, le résultat réel est proche de la valeur maximale codable. mais quand une multiplication déborde, le résultat peut parfois valoir 200 à 60000 fois plus que la valeur maximale codable. Les calculs avec une valeur saturée/corrigée sont donc crédibles pour une suite d'additions, mais pas pour une suite de multiplications.
===La détection des débordements entiers===
Quand un débordement d'entier a eu lieu, il vaut mieux que l'additionneur prévienne ! Pour cela, l'additionneur a une '''sortie de débordement''', parfois nommée ''Overflow'', dont la valeur indique si l'addition a généré un débordement d'entier ou non. Reste que détecter un débordement ne se fait pas de la même manière selon que l'on parle d'un additionneur non-signé ou signé.
Pour les additionneur non-signés, l'additionneur calcule un bit de plus que ce qui est supporté par l'ordinateur. Par exemple, un additionneur 32 bits fournit un résultat sur 33 bits, un débordement d'entier a lieu quand le 33ème bit est à 1. Précisément, la sortie de débordement n'est autre que la retenue finale, celle fournie par le dernier additionneur complet. Le seul type de débordement possible est un débordement par le haut, où le résultat dépasse la valeur maximale. Avec l'arithmétique saturée, le circuit de saturation consiste en une seule couche de multiplexeurs, voire en un circuit de mise à la valeur maximale tel que vu dans le chapitre sur les opérations bits à bits.
[[File:Gestion des débordements d'entiers.png|centre|vignette|upright=2.5|Gestion des débordements d'entiers lors d'une addition non-signée.]]
Pour les additionneurs non-signés, la gestion des débordements d'entiers dépend fortement de la représentation signée. Nous allons étudier le cas du complément à deux. Si vous vous rappelez le chapitre 1, les calculs sur des nombres en complètement à deux utilisent les règles de l'arithmétique modulaire, c'est une condition nécessaire. À priori, on peut penser que dans ces conditions, les débordements d'entiers sont une chose parfaitement normale, qui nous permet d'avoir des résultats corrects. Néanmoins, certains débordements d'entiers peuvent survenir malgré tout et produire des bugs assez ennuyeux.
Si l'on tient en compte les règles du complément à deux, on sait que le bit de poids fort (le plus à gauche) permet de déterminer si le nombre est positif ou négatif : il indique le signe du nombre. Tout se passe comme si les entiers en complément à deux étaient codés sur un bit de moins, et avaient leur longueur amputé du bit de poids fort. Si le résultat d'un calcul écrase le bit de poids fort, il y a un débordement d'entiers. Il existe une règle simple qui permet de détecter ces débordements d'entiers. L'addition de deux nombres positifs ne peut pas être un nombre négatif. Si on additionne deux nombres dont le bit de signe est à 0 et que le bit de signe du résultat est à 1, on est en face d'un débordement d'entiers. Même chose pour deux nombres négatifs : le résultat de l'addition ne peut pas être positif. On peut résumer cela en une phrase : si deux nombres de même signe sont ajoutés, un débordement a lieu quand le bit du signe du résultat a le signe opposé.
Modifier les circuits d'au-dessus pour qu'ils détectent les débordements en complément à deux est simple comme bonjour : il suffit créer un petit circuit combinatoire qui prenne en entrée les bits de signe des opérandes et du résultat, et qui fasse le calcul de l'indicateur de débordements. Si l'on rédige sa table de vérité, on doit se retrouver avec la table suivante :
{|class="wikitable"
|-
!Entrées
!Sortie
|-
|000||0
|-
|001||1
|-
|010||0
|-
|011||0
|-
|100||0
|-
|101||0
|-
|110||1
|-
|111||0
|}
L'équation de ce circuit est la suivante, avec <math>S_a</math> et <math>S_b</math> les signes des deux opérandes, et <math>C_i</math> la retenue de la colonne précédente :
: <math> ( S_a . S_b . \overline{C_i} ) + ( \overline{S_a} . \overline{S_b} . C_i )</math>
En simplifiant, on obtient alors :
: <math>( S_a . S_b ) \oplus C_i</math>
Or, il se trouve que <math>S_a . S_b</math> est tout simplement la retenue en sortie du dernier additionneur, que nous noterons <math>C_o</math>. On trouve donc :
: <math>C_o \oplus C_i</math>
Il suffit donc de faire un XOR entre la dernière retenue et la précédente pour obtenir le bit de débordement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de décalage et de rotation
| prevText=Les circuits de décalage et de rotation
| next=Les circuits de comparaison
| nextText=Les circuits de comparaison
}}
</noinclude>
awhzc47y0c2gckjrhcxp0yss5ndfns4
768652
768651
2026-06-25T17:57:21Z
Mewtow
31375
/* Les additionneurs à anticipation de retenue */
768652
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir les circuits capables de faire une addition ou une soustraction, ainsi que quelques circuits spécialisés. Précisons cependant que les fabricants de processeurs travaillent d'arrache-pied pour trouver des moyens de rendre ces circuits de calcul plus rapides et plus économes en énergie. Autant vous dire que les circuits que vous allez voir sont vraiment des circuits qui font pâle figure comparé à ce que l'on peut trouver dans un vrai processeur commercial !
==Les circuits pour additionner 2 ou 3 bits==
L'addition se fait en binaire de la même manière qu'en décimal. On additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. La soustraction fonctionne sur le même principe, sur le même modèle qu'en décimal.
[[File:Binary Addition Demonstration.svg|centre|vignette|Exemple d'addition en binaire.]]
En clair, additionner deux nombres demande d'additionner 2 bits et une retenue sur chaque colonne, et de propager les retenues d'une colonne à l'autre. La propagation des retenues est quelque chose de simple en apparence, mais qui est sujet à des optimisations extraordinairement nombreuses. Aussi, pour simplifier l'exposition, nous allons voir comment gérer une colonne avant de voir comment sont propagées les retenues.
[[File:Full Adder Block.svg|vignette|upright=1|class=transparent|Additionneur complet.]]
Si on effectue une addition en colonne, on doit additionner les deux bits sur la colonne, mais aussi additionner une éventuelle retenue. Il faut donc créer un circuit qui additionne trois bits : deux bits de données, plus une retenue. Ce circuit qui additionne trois bits est appelé un '''additionneur complet'''. Il fournit en sortie deux bits : un bit de somme et une retenue sortante. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|-
||0||0||0|| ||0||0
|-
||0||0||1|| ||0||1
|-
||0||1||0|| ||0||1
|-
||0||1||1|| ||1||0
|-
||1||0||0|| ||0||1
|-
||1||0||1|| ||1||0
|-
||1||1||0|| ||1||0
|-
||1||1||1|| ||1||1
|}
Il est possible d'utiliser un tableau de Karnaugh pour traduire la table de vérité, mais elle donne un résultat assez compliqué. La retenue et le bit de somme sont calculés à part. La retenue est calculée avec trois portes ET et une porte OU. Le bit de somme est calculé lui avec 4 portes ET, une porte OU, et trois portes NON.
[[File:Full Adler with 3 entries.png|centre|vignette|upright=2|Additionneur complet, fabriqué à partir de la table de vérité.]]
D'autres méthodes donnent des résultats plus compréhensibles. Nous allons les voir dans la suite de cette section.
===L'additionneur complet basé sur des demi-additionneurs===
[[File:1-bit half-adder.svg|class=transparent|vignette|Demi-addtionneur.]]
Nous avons déjà vu comment additionner deux bits dans le chapitre sur les incrémenteurs, mais quelques rappels ne seront pas de trop. Le '''demi-additionneur''' est un circuit qui additionne deux bits. Il implémente la table d'addition, qui est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Mais un demi-additionneur n'additionne que deux bits, il manque de quoi additionner la retenue. Une solution pour cela est d'enchaîner deux demi-additionneurs : un qui additionne les deux bits de données, et un second qui additionne la retenue au résultat du précédent. La retenue finale se calcule en combinant les sorties de retenue des deux demi-additionneurs, avec une porte OU. Le résultat est un additionneur complet fabriqué avec deux demi-additionneurs. Pour vous en convaincre, établissez la table de vérité de ce circuit, vous verrez que ça marche.
[[File:Full Adder Blocks.svg|centre|vignette|upright=2|Additionneur complet fabriqué avec deux demi-additionneurs.]]
[[File:Half-adder.svg|vignette|upright=1|Circuit d'un demi-additionneur naïf.]]
L'implémentation naïve d'un demi-additionneur utilise une porte XOR et une porte ET, comme illustré ci-contre. Le circuit s'obtient en faisant uen traduction littérale de la table de vérité du circuit. Si je dis qu'elle est naïve, c'est car il est possible de l'optimiser, de manière à éliminer des redondances cachées. Mais nous avions déjà vu cela dans le chapitre sur les incrémenteurs, aussi laissons cela pour plus tard.
En combinant deux demi-additionneurs de ce type, on obtient l'additionneur complet suivant. Les deux sorties de retenue des demi-additionneurs sont combinées avec une porte OU, pour calculer la retenue finale.
{| class="flexible"
|[[File:Full Adder Modules.svg|class=transparent|Composition d'un additionneur complet. On voit bien que celui-ci est composé de deux demi-additionneurs, en rouge et en bleu, auxquels on a ajouté une porte OU pour calculer la retenue finale.]]
|[[File:Full-adder.svg|300px|class=transparent|Circuit d'un additionneur complet.]]
|}
Le fait de combiner les deux retenues avec une porte OU n'est pas ce qu'il y a de plus intuitif. La table de vérité nous dit que ça fonctionne, mais on comprend mal pourquoi. Mais tout devient plus clair quand on sait que les deux retenues ne peuvent pas être à 1 en même temps. Même dans l'addition 1 + 1 + 1, seule une retenue est à 1. Soit l'addition des deux bits d'opérande donne naissance à une retenue, soit c'est l'addition ''résultat + retenue entrante''. La retenue sortant est donc à 1 quand une des deux addition donne une retenue. D'où l'usage d'une porte OU. Remarquez qu'une porte XOR donne le même résultat, vu que le cas où les deux retenues sont à 1 n'est jamais rencontré.
===La propagation et la génération des retenues===
L'additionneur complet que nous allons voir dans cette section sert à introduire les concepts de propagation, génération et absorption de retenue. Il s'agit de concepts très importants quand on étudie les additionneurs. l'idée part d'un principe très simple : la retenue sortante dépend de la retenue d'entrée. La relation entre les deux se résume à trois cas, qui dépendent de la valeur des deux bits additionnés, nommés A et B.
* Dans le premier cas, la retenue entrante est égale à la retenue sortante. On dit que la retenue entrante est propagée par l'additionneur.
* Dans le second cas, la retenue est forcée à 0 : la retenue sortante vaut 0, peu importe la valeur de la retenue entrante.
* Dans le troisième cas, la retenue est forcée à 1 : la retenue entrante vaut 1, peu importe la valeur de la retenue entrante.
[[File:Signaux P et G fournis par un demi-additionneur.png|vignette|Signaux P et G fournis par un demi-additionneur]]
Maintenant, créons un circuit qui nous dise si une retenue est propagée ou générée. Il a deux bits de sortie, nommés P et G : P pour ''Propagate'', G pour ''Generate''. Le bit P est à 1 si la retenue entrante est propagée, il est à 0 sinon. Le bit G est à 1 si une retenue est générée, à 0 sinon. Une retenue est considérée comme absorbée si elle n'est pas ni propagée ni générée, pas besoin d'un troisième bit pour gérer ce cas.
Il se trouve que ce circuit n'est autre qu'un demi-additionneur ! Pour vous en rendre compte, regardez la table de vérité d'un additionneur complet, illustrée ci-dessous. Lorsque les deux bits d'opérande sont à 0, la retenue sortante vaut toujours 0. Si ils sont tous deux à 1, alors la retenue sortante vaut 1. S'ils sont différents, alors retenues sortante et entrante sont égales. Le bit P est donc généré par une simple porte XOR. Quant au bit G, il est à 1 si les deux bits d'opérandes sont à 1, ce qui correspond à une porte ET. Il se trouve que ces deux portes forment un demi-additionneur !
{|class="wikitable"
|-
! Opérande 1 !! Opérande 2 !! Retenue entrante !! !! Retenue sortante
|- class="f_rouge"
||0||0||0|| ||0
|- class="f_rouge"
||0||0||1|| ||0
|- class="f_bleu"
||0||1||0|| ||0
|- class="f_bleu"
||0||1||1|| ||1
|- class="f_bleu"
||1||0||0|| ||0
|- class="f_bleu"
||1||0||1|| ||1
|- class="f_vert"
||1||1||0|| ||1
|- class="f_vert"
||1||1||1|| ||1
|}
Ensuite, créons un circuit qui prend ces deux sorties P et G, et calcule la retenue sortante en fonction. Le circuit en question a trois entrées : la retenue entrante, les deux bits P et G. La retenue sortante vaut 1 soit si une retenue est générée, soit si la retenue entrante est propagée et qu'elle vaut 1. En notant <math>{C_{in}}</math> et <math>{C_{out}}</math> les retenues entrantes et sortante, on a :
: <math>{C_{out}} = G + \left( P . C_{in} \right)</math>
Le circuit est donc composé d'une porte OU et d'une porte ET. En combinant un demi-additionneur avec le circuit de calcul de retenue sortante vu plus haut, on a :
[[File:Additionneur complet avec propagation et génération de retenue.png|class=transparent|centre|vignette|upright=2|Additionneur complet avec propagation et génération de retenue.]]
Certes, on retombe sur le circuit vu plus haut. Mais il est possible de remplacer le circuit en orange par un autre. Par exemple, il est possible de le remplacer par multiplexeur, qui choisit entre la retenue générée et la retenue entrante (propagée). Le choix se fait selon la valeur du bit P, qui chosiit entre propager la retenue et la générer.
[[File:Additionneur crée avec un multiplexeur.png|centre|vignette|upright=1.5|Additionneur crée avec un multiplexeur]]
Le circuit exact est illustré ci-dessous. Il semble utiliser plus de portes logiques que l'additionneur complet précédent. Cependant, nous verrons dans quelques chapitres qu'il est possible d'implémenter un multiplexeur avec seulement 6 transistors, voire moins ! L'implémentation utilise des portes à transmission, mais nous en reparlerons dans le chapitre sur les transistors, quand nous verrons les additionneurs à ''Manchester Carry Chain''. Au passage, une variante de ce circuit a été utilisée dans le processeur processeur 8086 d'Intel, comme on le verra dans le chapitre suivant.
[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
[[File:FulladderPG.png|vignette|upright=1|Additionneur complet avec deux sorties P et G.]]
Pour finir, sachez qu'il existe des additionneurs qui fournissent : le bit de résultat, deux sorties P et G qui indiquent si l'addition propage ou génère une retenue. Il ne s'agit pas d'additionneurs complets, car il manque de quoi calculer la retenue sortante. De tels additionneurs seront appelés des '''additionneurs partiels'''. De tels additionneurs sont utilisés dans certains additionneurs pour gagner en performance.
Enfin, il existe aussi ce que j'ai décidé d'appeler des '''additionneurs P/G''', qui sont des additionneurs complets auxquels on a ajouté deux sorties P et G, en plus de la sortie de retenue. Leur circuit est le même que celui d'un additionneur complet, auquel on a ajouté deux fils. Vu que ces deux sorties sont fournies par le premier demi-additionneur, ajouter ces deux sorties demande d'ajouter des fils, pas de portes logiques. De tels additionneurs sont aussi utilisés dans certains additionneurs pour gagner en performance.
===L'additionneur complet basé sur une porte à majorité===
Maintenant, voyons une dernière implémentation possible de l'additionneur complet. Mais avant de voir comment l'additionneur est implémenté, nous allons devoir faire un petit focus sur la retenue sortante. Reprenons la table de vérité, et regardons ce qu'il en est pour la retenue sortante uniquement.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante
|-
||0||0||0|| ||0
|-
||0||0||1|| ||0
|-
||0||1||0|| ||0
|-
||0||1||1|| ||1
|-
||1||0||0|| ||0
|-
||1||0||1|| ||1
|-
||1||1||0|| ||1
|-
||1||1||1|| ||1
|}
Vous remarquerez que la retenue sortante est égale au bit majoritaire, parmi les trois bits d'opérande. Si deux ou trois bits sont à 1, la retenue sortante vaut 1. Et réciproquement avec 0. Le circuit de calcul de la retenue peut donc être remplacé par une '''porte à majorité'''.
[[File:Additionneur crée avec une porte à majorité.png|centre|vignette|upright=2|Additionneur crée avec une porte à majorité]]
Mais une porte à majorité est assez gourmande en circuit, ce qui fait qu'un additionneur pareil ne serait pas pratique, ni utile. Du moins, ce serait le cas s'il n'y avait pas une possibilité d'optimisation extrêmement intéressante. Il est possible de calculer le 'bit de somme' à partir de la retenue sortante ! En effet, le bit de somme est l'inverse de la retenue sortante, sauf dans deux cas : les trois bits d'entrée sont à 0, où ils sont tous à 1. Le bit de somme vaut 0 dans le premier cas, 1 dans le second cas.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|- class="f_bleu"
||0||0||0|| ||0||0
|- class="f_vert"
||0||0||1|| ||0||1
|- class="f_vert"
||0||1||0|| ||0||1
|- class="f_vert"
||0||1||1|| ||1||0
|- class="f_vert"
||1||0||0|| ||0||1
|- class="f_vert"
||1||0||1|| ||1||0
|- class="f_vert"
||1||1||0|| ||1||0
|- class="f_rouge"
||1||1||1|| ||1||1
|}
En clair, 6 lignes de la table de vérité sur 8 peuvent se calculer avec une porte à majorité, le reste demandant quelques portes logiques pour faire la correction. L'implémentation en circuit demande donc trois choses :
* de calculer la retenue sortante et de quoi l'inverser ;
* un circuit qui vérifie si tous les bits opérande valent 0 : une porte NOR ou OU fait l'affaire ;
* un circuit qui vérifie s'ils valent tous 1, à savoir une porte ET.
Ensuite, on combine le résultat des trois circuits précédents pour obtenir le résultat final. La combinaison est le fait du circuit en jaune dans le schéma ci-dessous.
[[File:Full adder basé sur une modification de la retenue.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue]]
Le circuit en jaune est assez simple. Quand la porte ET sort un 1, la sortie doit être forcée à 1. Par contre, quand la porte OU sort un zéro, la sortie doit être forcée à 0. Il suffit donc d'enchainer un circuit de mise à 1 et un circuit de mise à 0. Nous avons vu dans le chapitre sur les opérations de masquage qu'il s'agit respectivement d'une porte OU et d'une porte ET.
[[File:Full adder basé sur une modification de la retenue interne, au complet.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue interne, au complet]]
Divers processeurs utilisaient des additionneurs complets de ce type : le fameux Z80, l'Intel 4004 et de l'Intel 8008 sont les exemples les plus notables. Pourtant, on pourrait s'étonner qu'un tel circuit ait existé. Il utilise beaucoup de portes logiques, a une profondeur logique supérieure : il n'a rien d'avantageux. Sauf qu'il était utilisé sur d'anciens processeurs, qui utilisaient la technologie dite TTL, différente de la technologie CMOS des transistors modernes. Et avec la technologie TTL, il est possible de fusionner plusieurs portes logiques ET et NOR en une seule porte logique ET/OU/NON ! Un additionneur complet construit ainsi ne prenait que deux portes logiques : une pour le calcul de la retenue sortante, une autre pour le reste du circuit.
==L'addition non signée==
Voyons maintenant un circuit capable d'additionner deux nombres entiers: l''''additionneur'''. Dans la version qu'on va voir, ce circuit manipulera des nombres strictement positifs. L'addition des nombres codés en complètement à deux sera vu dans une section ultérieure.
===L'additionneur série===
Il est possible d'additionner deux nombres bit par bit, gr$ace à un additionneur complet associé à plusieurs registres à décalages. Les opérandes sont placées chacune dans un registre à décalage, et l'additionneur complet utilise les bits sortants de ces registres à décalage. Le bit du résultat est envoyé au registre à décalage pour le résultat. La retenue de l'addition est stockée dans une bascule de 1 bit, en attente du prochain cycle d'horloge. Un tel additionneur est appelé un '''additionneur série'''. Il a été utilisé sur d'anciens ordinateurs dans les années 50-60, aussi bien des prototypes que des ordinateurs commerciaux.
[[File:Additionneur série.jpg|centre|vignette|upright=2.5|Additionneur série.]]
===L'additionneur à propagation de retenue===
L''''additionneur à propagation de retenue''' est aussi appelé l'additionneur ''ripple carry'', c'est son nom anglais. J'utiliserais occasionnellement le nom anglais dans ce qui suit, car celui-ci est plus court. Ilutilise un additionneur complet pour chaque colonne de l'addition. Pour gérer les retenues, il suffit ainsi de câbler des additionneurs complets les uns à la suite des autres, chacun envoyant sa retenue à la colonne suivante.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Notons la présence de la retenue sortante, qui est utilisée pour détecter les débordements d'entier, ainsi que pour d'autres opérations. Le bit de retenue final est souvent stocké dans un registre spécial du processeur (généralement appelé ''carry flag'').
Notez aussi, à droite du schéma précédent, l'entrée de retenue <math>\text{R}_\text{Entrée}</math>. Elle est très utile pour l'implémentation de certaines opérations comme l'inversion de signe, la soustraction, l'incrémentation, etc. Certains processeurs sont capables de faire une opération appelée ADC, ADDC ou autre nom signifiant ''Addition with Carry'', qui permet de faire le calcul A + B + Retenue (la retenue en question est la retenue sortante de l'addition précédente, stockée dans le registre ''carry flag''). Son utilité principale est de permettre des additions d'entiers plus grands que ceux supportés par le processeur. Par exemple, cela permet de faire des additions d'entiers 32 bits sur un processeur 16 bits.
[[File:Adder Network 3 Bit Sum Delay 003.svg|vignette|upright=0.5|Propagation de retenue dans l'additionneur.]]
Cet additionneur utilise très peu de portes logiques, ce qui fait qu'il était utilisé sur certains processeurs 8 et 16 bits, dont le budget en portes logiques était limité. Bien que très simple, cet additionneur est cependant peu performant. Son temps de calcul est proportionnel à la taille des opérandes. Par exemple, additionner deux opérandes de 32 bits prendra deux fois plus de temps que pour des opérandes de 16 bits. La raison est que les retenues doivent se propager à travers le circuit, du premier additionneur jusqu'au dernier.
L'addition étant une opération fréquente, d'autres additionneurs ont été inventés. Ils utilisent des optimisations qui utilisent plus de circuits pour gagner en rapidité. Mais avant de voir ces optimisations, nous allons voir s'il est possible d'optimiser les additionneurs à propagation de retenue.
===L'additionneur à saut de retenue===
L'additionneur à propagation de retenue peut être optimisé de deux manières. La première accélère la propagation de la retenue, en agissant au niveau des additionneurs complets. La ''Manchester carry chain'' est une optimisation de ce type, mais on ne peut pas encore expliquer à ce stade du cours, car elle optimisait les additionneurs complets au niveau des transistors eux-mêmes. Elle a été utilisée sur de nombreux processeurs connus, comme le 8086 d'Intel ou le Z80.
La seconde manière rend l'addition plus rapide dans certains cas. Le résultat est un additionneur dont le temps de calcul est variable. Le calcul prendra quelques cycles d'horloges avec certains opérandes, mais d'autres opérandes ne verront aucune amélioration. Il n'améliore pas le pire des cas, dans lequel la retenue doit être propagée du début à la fin, du bit de poids faible au bit de poids fort. Mais dans d'autres cas, il permet d'avoir le résultat en avance. L'optimisation en question donne l''''additionneur à saut de retenue''' (''carry-skip adder'').
Il est composé de blocs qui additionnent 4/5 bits, rarement plus. La taille d'un bloc varie d'un additionneur à l'autre, elle peut même varier dans l'additionneur, mais nous allons prendre un exemple où les blocs font 4 bits. L'idée est de détecter précocement si la retenue entrante est propagée à travers tout le bloc. Si la retenue est propagée dans tous le bloc, on peut directement l'envoyer sur la sortie de retenue, la retenue saute le bloc entier. Dans le cas contraire, on doit calculer la retenue normalement. Le choix entre les deux est le fait d'un multiplexeur. Toute la difficulté est de savoir comment commander le multiplexeur.
[[File:Carry skip adder, principe de base.png|centre|vignette|upright=2.5|Carry skip adder : principe de base]]
Pour savoir si une retenue est propagée dans un bloc entier, il faut utiliser des additionneurs complets, qui ont en plus une sortie P indiquant que l'additionneur propage la retenue. Le bloc propage une retenue si chaque additionneur complet propage la retenue, donc s'ils ont tous leur sortie P à 1. Un simple ET logique suffit à déterminer si c'est le cas. Le signal de commande du multiplexeur est donc un vulgaire ET entre toutes les sorties P des additionneurs complets du bloc.
[[File:Additionneur à saut de retenue.png|centre|vignette|upright=2.5|Calcul de la commande du MUX.]]
Voici ce que ça donne pour un bloc de 4 bits :
[[File:CSAdder4Bit.svg|centre|vignette|upright=1.5|Additionneur ''carry skip'' : bloc de 4 bits.]]
L'additionneur à saut de retenue est construit en assemblant plusieurs blocs de ce type.
[[File:BCSAdder16Bit.svg|centre|vignette|upright=3|Additionneur à saut de retenue.]]
===L'additionneur à sélection de retenue===
L''''additionneur à sélection de retenue''' utilise aussi des blocs, comme les additionneurs précédents. L'addition se fait en deux versions : une avec la retenue du bloc précédent valant zéro, et une autre version avec la retenue du bloc précédent valant 1. Il suffira alors de choisir le bon résultat avec un multiplexeur, une fois cette retenue connue. On gagne ainsi du temps en calculant à l'avance les valeurs de certains bits du résultat, sans connaître la valeur de la retenue. Petit détail : sur certains additionneurs à sélection de retenue, les blocs de base n'ont pas la même taille. Cela permet de tenir compte des temps de propagation des retenues entre les blocs.
[[File:Additionneur à sélection de retenue.png|centre|vignette|upright=1.5|Additionneur à sélection de retenue avec seulement deux blocs.]]
Dans les exemples du dessus, chaque sous-additionneur étaient des additionneurs à propagation de retenue. Mais ce n'est pas une obligation, et tout autre type d’additionneur peut être utilisé. Par exemple, on peut faire en sorte que les sous-additionneurs soient eux-mêmes des additionneurs à sélection de retenue, et poursuivre ainsi de suite, récursivement. On obtient alors un '''additionneur à somme conditionnelle''', plus rapide que l'additionneur à sélection de retenue, mais qui utilise beaucoup plus de portes logiques.
===Les additionneurs à anticipation de retenue===
Les '''additionneurs à anticipation de retenue''' calculent chaque retenue sans avoir à propager les retenues précédentes. Au lieu de calculer les retenues une par une, ils calculent toutes les retenues en parallèle, à partir des bits des opérandes. Une fois les retenues pré-calculées, il suffit de les additionner avec les deux bits adéquats, pour obtenir le résultat. Ces additionneurs sont composés de deux parties :
* un circuit qui pré-calcule les retenues, sans les propager, directement à partir des opérandes ;
* d'une couche d'additionneurs complets, qui additionnent chacun deux bits d'opérande et la retenue pré-calculée.
[[File:Additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Additionneur à anticipation de retenue.]]
Le calcul des retenues se fait en deux étapes. La première détermine si les retenues sont propagées ou générées, pour chaque colonne. Elle détermine, pour chaque colonne, les bits P et G qui indiquent respectivement : qu'une retenue a été générée sur cette colonne, que la colonne propage la retenue précédente. La seconde étape utilise ces signaux P et G pour déterminer la retenue sur chaque colonne. Le circuit de calcul des retenues est donc composé de deux parties : une couche de demi-additionneurs pour générer les signaux P et G, un circuit de calcul des retenues proprement dit.
[[File:Circuit complet d'un additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Circuit complet d'un additionneur à anticipation de retenue.]]
Pour rappel, la retenue de la colonne i, notée <math>C_i</math> est égale à :
: <math>G_i + (P_i . C_{i-1})</math>, avec G le signal de génération de retenue, P le signal de propagation de retenue.
Si on utilisait cette formule sans trop réfléchir, on retomberait sur un additionneur à propagation de retenue inutilement compliqué. L'astuce des additionneurs à anticipation de retenue consiste à remplacer le terme Ci−1 par sa valeur calculée avant. Par exemple, je prends un additionneur 4 bits. Je dispose de deux nombres A et B, contenant chacun 4 bits : A3, A2, A1, et A0 pour le nombre A, et B3, B2, B1, et B0 pour le nombre B. Si j'effectue les remplacements, j'obtiens les formules suivantes :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Ces formules nous permettent de déduire la valeur d'une retenue directement : il reste alors à créer un circuit qui implémente ces formules, et le tour est joué.
[[File:Four bit adder with carry lookahead.svg|centre|vignette|upright=1.5|Additionneur à anticipation de retenue de 4 bits.]]
Plus haut, j'ai dit que le circuit était composé de trois couches : une couche de demi-additionneur pour les signaux P et G ; un circuit d'anticipation de retenue, puis l'addition finale. Mais les deux bits d'opérandes sont déjà additionnés dans la couche de demi-additionneur, autant réutiliser le résultat pour l'additionner à la retenue. Pour éliminer toute redondance de ce type, il est possible d'utiliser des additionneurs partiels (des additionneurs où la sortie de retenue a été remplacée par deux sorties P et G). L'additionneur final est alors composé de plusieurs additionneurs complets de ce type, couplé à un circuit d'anticipation de retenue.
[[File:Cla4bits.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue de 4 bits.]]
Les additionneurs à anticipation de retenue sont plus rapides que les additionneurs à propagation de retenue. La raison est que les retenues sont calculées rapidement, dans un temps plus petit qu'en propageant la retenue. Pour rentrer dans les détails mathématiques, le temps de calcul n'est pas proportionnel au nombre de bits, mais proportionnel au logarithme de ce nombre de bits. Par contre, le gain en temps de calcul s'accompagne d'un cout en prote logique conséquent. Le circuit qui calcule les retenues anticipées utilise beaucoup de portes logiques.
La conséquence est qu'utiliser un additionneur à anticipation de retenue est une solution pratique pour additionner 4 ou 8 bits. Mais pour des opérandes plus grandes, de 16 ou 32 bits, cela utiliserait trop de portes logiques. Une partie de ce cout en portes logiques est cependant lié à des redondances. Si on analyse deux retenues différentes, à des colonnes différentes. Les circuits qui calculent ces deux retenues seront différents, mais ils contiendront des sous-circuits identiques. Pour vous en rendre compte, reprenons les quatre équations obtenues précédemment et essayez de trouver des redondances :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Vous devriez trouver :
* le terme ''P0 · C0'' dans toutes les lignes ;
* les termes ''P1 · P0 · C0'' et ''P1 · P0'' dans les trois dernières lignes ;
* le terme ''P2 · P1 · P0 · C0'' ; ''P2 · P1 · G0'' et ''P2 · G1'' dans les deux dernières lignes.
===Les additionneurs à calcul parallèle de préfixe===
Il existe plusieurs manières d'exploiter de telles redondances, exploitées dans les '''additionneurs à calcul parallèle de préfixe'''. L'optimisation apportée est de générer des signaux ''propagate'' et ''generate'' pour des groupes de 2, 3, 4, ..., N colonnes. Par exemple, il est possible de générer un signal ''P 0 vers 3'', qui indique si la retenue de la première colonne est propagée jusqu'à la 4ème colonne. Un autre exemple est un signal qui indique qu'une retenue a été générée entre les colonnes 4 à 7 génèrent une retenue ou non. En clair, les signaux P et G ont maintenant un intervalle, qui précise de quelle colonne vers quelle colonne se fait la propagation, ou entre quelles colonnes se fait la génération.
Il est possible de combiner les signaux P et G de deux groupes de colonne, s'ils sont contiguës (ils peuvent aussi se recouvrir). Par exemple, il est possible de calculer les bits P et G pour les colonnes 0 à 10, à partir des deux signaux P/G des colonnes 0-4 et 5-10. Pour cela, les équations sont assez simples. Si je prends deux groupes nommés A et B, avec A pour les colonnes de poids fort et B celles de poids faible, on a :
: <math>P_{sortie} = P_A . P_B</math>
: <math>G_{sortie} = G_A . P_A . G_B</math>
Pour les bits des colonnes 2, 4, 8, 16, 32, 64, et autres, le calcul est simple. L'idée est de grouper les colonnes par groupes de 2. Puis, on calcule chaque groupe de 4 bits à partir de deux groupes de 2. Puis les groupes de 8 bits sont calculés à partir de deux groupes de 4 bits, et ainsi de suite pour toute puissance de deux.
[[File:Squelette d'un additionneur à calcul parallèle de préfixe.jpg|centre|vignette|upright=2|Squelette d'un additionneur à calcul parallèle de préfixe]]
Les seules difficultés surviennent pour les colonnes dont le nombre n'est pas une puissance de deux. Il y a plusieurs manières pour subdiviser ces colonnes en intervalles plus petits et combiner le tout. Et elles donnent chacune des additionneurs différents. Ils portent les noms d'''additionneur de Ladner-Fisher'', d'''additionneur de Brent-Kung'', d'''additionneur de Kogge-Stone'', etc. Dans le cas général, l'additionneur de Kogge-Stone est un des plus rapide, mais aussi un des plus gourmand en circuits, alors que c'est l'inverse pour l'additionneur de Brent-Kung. Les deux additionneurs sont illustrés ci-dessous, pour des opérandes de 8 bits. Dans ceux-ci, chaque losange calculent les bits P et G, à partir des signaux P et G de deux groupes précédents.
{|
|[[File:Brent-kung-8-bit.png|vignette|Brent-kung-8-bit]]
|[[File:Kogge-stone-8-bit.png|vignette|upright=1.2|Kogge-stone-8-bit]]
|}
===Les additionneurs avec anticipation de retenue entre plusieurs additionneurs===
Dans cette section, nous allons voir des additionneurs conçus en assemblant des additionneurs à plus simples, qui additionnent environ 4 à 5 bits, parfois plus, parfois moins. Ces additionneurs simples seront nommés ''blocs'' dans ce qui suit, et l'un d'entre eux est illustré ci-contre. Chaque bloc prend en entrée un morceau des deux opérandes à additionner, mais aussi une retenue d'entrée.
Les blocs sont tous identiques dans le cas le plus simple, mais il est possible d'utiliser des blocs de taille variable. Par exemple, le premier bloc peut avoir des opérandes de 6 bits, le second des opérandes de 7 bits, etc. Faire ainsi permet de gagner un petit peu en performances, si la taille de chaque bloc est bien choisie. La raison est une question de temps de propagation des retenues. La retenue met plus de temps à se propager à travers 8 blocs qu'à travers 4, ce qui prend plus de temps qu'à travers 2 blocs, etc. En tenir compte fait que la taille des blocs tend à augmenter ou diminuer quand on se rapproche des bits de poids fort.
====Le calcul parallèle de la retenue====
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Plus haut, nous avons vu l'anticipation de retenue. Il est possible de l'adapter pour fonctionner avec des blocs. Une première idée est d'utiliser plusieurs additionneurs à anticipation de retenue, chacun étant un bloc. Vu qu'il s'agit d'additionneurs, ils disposent d'une sortie de retenue pour la retenue sortante, et d'une entrée de retenue pour la retenue entrante. Par exemple, le schéma ci-contre illustre un additionneur 4 bits de ce type.
De ce fait, il est possible de propager les retenues d'un bloc à l'autre. par exemple, on peut utiliser 4 additionneurs à anticipation de retenue de 4 bits, et propager les retenues entre eux. Le premier bloc calcule une retenue, qui est propagée au second bloc. Le second bloc calcule lui aussi une retenue, qui est propagée au troisième bloc, etc.
[[File:4008 Functional Diagram.svg|vignette|upright=1|4008 Functional Diagram]]
Le gain en performance est significatif, mais il est possible de faire presque aussi rapide, tout en économisant beaucoup de circuits. L'idée est de simplifier les additionneurs 4 bits, afin de ne pas utiliser une anticipation de retenue complète. L'anticipation de retenue n'est utilisée que pour la retenue sortante, alors que les bits de somme sont calculées avec propagation de retenue. La retenue sortante est donc calculée en parallèle de l'addition. Chaque bloc contient, à côté d'un additionneur proprement dit, un circuit qui calcule la retenue sortante. Il existe de nombreuses manières de calculer la retenue sortante.
Le gain en portes logiques est assez significatif. En effet, on se passe de circuits d'anticipation de retenue pour les 4 bits de somme, on ne garde que celui pour la retenue sortante. Le fait d'utiliser la propagation de retenue pour calculer les 4 bits de somme est censé avoir un léger cout en performance, mais il se trouve que ce cout est très faible. En effet, le résultat n'est connu qu'une fois les retenues propagées jusqu'au dernier bloc. Et malgré l'anticipation des retenues, la propagation est assez lente. les additionneurs à propagation de retenue auront terminé leur travail bien avant.
Un exemple est celui de l'additionneur CMOS 4008, un additionneur de 4 bit. Il est composé en trois sections. Une première couche de demi-additionneurs calcule les signaux P et G utilisés à la fois pour l'anticipation de la retenue sortante, que pour calculer les bits de somme. Le circuit de calcul de la retenue sortante utilise l'anticipation de retenue. Le reste du circuit fait l'addition en propageant les retenues.
[[File:CMOS 4008, circuit découpé en sections.png|centre|vignette|upright=1.5|CMOS 4008, circuit découpé en sections]]
====Les additionneurs à anticipation de retenue multi-niveau====
Il est possible d'utiliser l'anticipation de retenue entre deux blocs. Par exemple, il est possible de créer un additionneur 16 bits en utilisant 4 additionneurs/blocs de 4 bits. L'additionneur utilisé peut être un additionneur à propagation de retenue, ou à anticipation de retenue, peu importe.
Pour cela, les additionneurs de 4 bits doivent fournir deux sorties P et G. Les deux sorties indiquent que le bloc soit a généré une retenue, soit a propagé la retenue entrante. Par propagé, on veut dire que la retenue entrante du bloc s'est retrouvée sur la sortie de retenue du bloc. C'est le cas si les 4 colonnes propagent la retenue, ce qui signifie que :
: <math>P_\text{4 bits} = P_0 \cdot P_1 \cdot P_2 \cdot P_3</math>
Pour la génération, cela veut dire qu'une colonne a générée une retenue, qui a été propagée jusqu'à la sortie. En clair, pour chaque colonne, il faut que le G = 1, et que les signaux P des colonnes soient à 1. Pour les 4 colonnes, cela donne :
: <math>G_\text{4 bits} = G_3 + G_2 \cdot P_3 + G_1 \cdot P_3 \cdot P_2 + G_0 \cdot P_3 \cdot P_2 \cdot P_1</math>
[[File:Cla4bitsPG.png|centre|vignette|upright=2|Additionneur à anticipation de retenue de 4 bits.]]
Pour combiner 4 blocs de 4 bits, on devra donc combiner 4 bits P et G, avec un circuit d'anticipation de retenue.
[[File:16-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 16 bits.]]
Il est même possible d'aller plus loin et de combiner 4 additionneurs précédents pour obtenir un additionneur 64 bits.
[[File:64-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 64 bits.]]
Le résultat est un additionneur à plusieurs niveaux d’anticipation de retenue. Une anticipation de retenue dans un blocs de 4 bits (facultative), entre blocs de 4 bits, entre blocs de 16 bits. La même logique peut être utilisée avec des blocs de taille différente de 4, 16 et 64 bits.
==L'addition signée et la soustraction==
Après avoir vu l'addition, il est logique de passer à la soustraction, les deux opérations étant très proches. Si on sait câbler une addition entre entiers positifs, câbler une soustraction n'est pas très compliqué. De plus, la soustraction permet de faire des additions de nombres signés.
===Le soustracteur pour opérandes entiers===
Pour soustraire deux nombres entiers, on peut adapter l'algorithme de soustraction utilisé en décimal, celui que vous avez appris à l'école. Celui-ci ressemble fortement à l'algorithme d'addition : on soustrait les bits de même poids, et on propage éventuellement une retenue sur la colonne suivante. À la différence de l'addition, la retenue est soustraite, et non ajoutée. La table de soustraction nous dit quel est le résultat de la soustraction de deux bits. La voici :
* 0 - 0 = 0 ;
* 0 - 1 = 1 et une retenue ;
* 1 - 0 = 1 ;
* 1 - 1 = 0.
[[File:Algebra1 05 fig014.svg|centre|vignette|Soustraction en binaire, avec les retenues en rouge.]]
La table de soustraction peut servir de table de vérité pour construire un circuit qui soustrait deux bits. Celui-ci est appelé un '''demi-soustracteur'''. Il ressemble beaucoup à un demi-additionneur, les différences se résumant à une porte NON ajoutée pour le calcul de la retenue.
[[File:Half Subtractor Vektor.svg|centre|vignette|Demi-soustracteur.]]
Comme pour l'additionneur, deux demi-soustracteurs peuvent être combinés pour donner un '''soustracteur complet'''. Le calcul de la retenue se fait en combinant les deux retenues des demi-soustracteurs avec une porte OU. Les soustracteurs complets sont utilisés pour créer des soustracteurs à propagation de retenue ou tout autre circuit soustracteur, sur le même modèle que les additionneurs.
[[File:FullSubtractor.svg|centre|vignette|upright=2|Soustracteur complet.]]
Vous l'aurez sans doute remarqué, mais le demi-soustracteur ressemble beaucoup au demi-additionneur. La seule différence est une porte NON ajoutée au bon endroit dans le demi-soustracteur.
{|
|[[File:Half-adder.svg|vignette|Demi-additionneur]]
|[[File:Half Subtractor Vektor.svg|vignette|upright=1.3|Demi-soustracteur]]
|}
La conséquence est qu'il est possible de créer un circuit capable de faire à la fois des additions et des soustractions. L'idée est de remplacer la porte NON du demi-soustracteur par un inverseur commandable. Si on envoie un zéro sur l'entrée de commande, l'inverseur commandable se comportera comme une porte OUI. Le circuit sera alors identique à un demi-additionneur. Si on envoie un 1 sur l'entrée de commande, l'inverseur commandable se comportera comme une porte NON. le circuit sera alors un demi-soustracteur. Il faut faire cela pour chaque demi-soustracteur, ce qui donne ceci pour un soustracteur complet. le résultat est alors appelé un '''additionneur-soustracteur complet'''.
[[File:Additionneur-soustracteur complet.png|centre|vignette|upright=2|Additionneur-soustracteur complet]]
===L'additionneur-soustracteur pour opérandes codées en complément à deux===
Étudions le cas de la soustraction en complément à deux, dans l'objectif de créer un circuit soustracteur. Vous savez sûrement que a−b et a+(−b) sont deux expressions équivalentes. Et en complément à deux, − b = not(b) + 1. Dit autrement, a − b = a + not(b) + 1. On pourrait se dire qu'il faut deux additionneurs pour faire le calcul, mais la majorité des additionneurs possède une entrée de retenue pour incrémenter le résultat de l'addition. Un soustracteur en complément à deux est donc simplement composé d'un additionneur et d'un inverseur.
[[File:Additionneur soustracteur.png|centre|vignette|upright=2|Soustracteur en complément à deux.]]
Il est possible de créer un circuit capable d'effectuer soit une addition, soit une soustraction : il suffit de remplacer l'inverseur par un inverseur commandable, qui peut être désactivé. On a vu comment créer un tel inverseur commandable dans le chapitre sur les circuits combinatoires. On peut remarquer que l'entrée de retenue et l'entrée de commande de l'inverseur sont activées en même temps : on peut fusionner les deux signaux en un seul.
[[File:Additionneur-soustracteur en complément à deux.png|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux.]]
Une implémentation alternative est la suivante. Elle remplace l'inverseur commandable par un multiplexeur.
[[File:4-bit ripple carry adder-subtracter.svg|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux, version alternative.]]
===L'additionneur-soustracteur pour opérandes codées en signe-magnitude===
Passons maintenant aux nombres codés en signe-valeur absolue, les deux opérandes étant notées A et B. Suivant les signes des deux opérandes, on a quatre cas possibles : A + B, A − B (B négatif), −A + B (A négatif) et −A − B (A et B négatifs). Une astuce est que le circuit n'a besoin que de calculer A + B et A − B : il peut les inverser pour obtenir − A − B ou B − A. A + B et A − B peuvent se calculer avec un additionneur-soustracteur, reste à corriger le résultat. Il suffit de lui ajouter un inverseur commandable pour obtenir le circuit d'addition finale.
[[File:Additionneur en signe-valeur absolue.png|centre|vignette|upright=2|Additionneur en signe-valeur absolue.]]
Toute la difficulté tient dans le calcul du bit de signe du résultat, quand interviennent des soustractions. Autant l'addition de deux nombres de même signe (A + B et −A − B) ne pose aucun problème, autant les soustractions posent problème (A − B et −A + B). Suivant que <math>A<B</math> ou que <math>A>B</math>, le signe du résultat ne sera pas le même. Déterminer le signe du résultat se fait en regardant les bits de débordement d'entier, comme on le verra plus bas.
===L'additionneur-soustracteur pour opérandes codées en représentation par excès===
Passons maintenant aux nombres codés en représentation par excès. On pourrait croire que ces nombres s'additionnent comme des nombres non-signés, mais ce serait oublier la présence du biais, qui pose problème. Dans les cas de nombres signés gérés avec un biais, voyons ce que donne l'addition de deux nombres :
: <math>( n_1 + biais ) + ( n_2 + biais ) = n_1 + n_2 + 2 \times biais</math>
Or, le résultat correct serait :
: <math>n_1 + n_2 + biais</math>
En effectuant l'addition telle quelle, le biais est compté deux fois. On doit donc le soustraire après l'addition pour obtenir le résultat correct.
Même chose pour la soustraction qui donne ceci :
: <math>(n_1 + biais) - (n_2 + biais) = n_1 - n_2</math>
Or, le résultat correct serait :
: <math> ( n_1 - n_2 ) + biais </math>
Il faut rajouter le biais pour obtenir l'exposant correct.
On a donc besoin de deux additionneurs/soustracteurs : un pour additionner/soustraire les représentations binaires des opérandes, et un autre pour ajouter/retirer le biais en trop/manquant.
==L'additionneur BCD==
Maintenant, voyons un additionneur qui additionne deux entiers au format BCD. Pour cela, nous allons devoir passer par deux étapes. La première est de créer un circuit capable d'additionneur deux chiffres BCD. Ensuite, nous allons voir comment enchaîner ces circuits pour créer un additionneur BCD complet.
===L'additionneur BCD qui fait l'opération chiffre par chiffre===
Nous allons commencer par voir un additionneur qui additionne deux chiffres en BCD, une sorte d'équivalent BCD de l'additionneur complet. Il fournit un résultat sur 4 bits et une retenue qui est mise à 1 si le résultat dépasse 10 (la limite d'un chiffre BCD). Les deux opérandes sont des chiffres BCD codés sur 4 bits et sont additionnés en binaire par un additionneur des plus normaux, similaire à ceux vus plus haut. Le résultat est alors un entier codé en binaire, sur 5 bits, qu'on corrige/convertit pour obtenir un chiffre BCD et une retenue sortante.
Pour corriger le résultat, une idée intuitive serait de prendre le résultat et de faire une division par 10. Le quotient donne la retenue, alors que le reste est le résultat, le chiffre BCD. Mais un circuit diviseur par 10 utilise beaucoup de portes logiques, ce qui ne vaut pas le coup. Une autre méthode détecte si le résultat est égal ou supérieur à 10, ce qui correspond à un "débordement" (on dépasse les limites d'un chiffre BCD). Si le résultat est plus petit que 10, il n'y a rien à faire : le résultat est bon et la retenue est de zéro. Par contre, si le résultat vaut 10 ou plus, il faut corriger le résultat et générer une retenue à 1.
Il faut donc ajouter un circuit qui détecte si le résultat est supérieur à 9, qui calcule directement la retenue. Ce circuit peut se fabriquer simplement à partir de sa table de vérité, ou en utilisant les techniques que nous verrons dans un chapitre ultérieur sur les comparateurs. La solution la plus simple est clairement d'utiliser la table de vérité, ce qui est très simple, assez pour être laissé en exercice au lecteur. Pour comprendre comment corriger le résultat, établissons une table de vérité qui associe le résultat et le résultat corrigé. L'entrée vaut au minimum 10 et au maximum 9 + 9 = 18. On considère la sortie comme un tout, la retenue étant un 5ème bit, le bit de poids fort.
{|class="wikitable"
|-
! colspan="5" | Entrée
!
! rowspan="10" |
! Retenue
! Résultat corrigé (sans retenue)
! interprétation de la sortie en binaire (retenue inclue)
|-
| 0 || 1 || 0 || 1 || 0
| (10)
| 1 || 0000
| (16)
|-
| 0 || 1 || 0 || 1 || 1
| (11)
| 1 || 0001
| (17)
|-
| 0 || 1 || 1 || 0 || 0
| (12)
| 1 || 0010
| (18)
|-
| 0 || 1 || 1 || 0 || 1
| (13)
| 1 || 0011
| (19)
|-
| 0 || 1 || 1 || 1 || 0
| (14)
| 1 || 0100
| (20)
|-
| 0 || 1 || 1 || 1 || 1
| (15)
| 1 || 0101
| (21)
|-
| 1 || 0 || 0 || 0 || 0
| (16)
| 1 || 0110
| (22)
|-
| 1 || 0 || 0 || 0 || 1
| (17)
| 1 || 0111
| (23)
|-
| 1 || 0 || 0 || 1 || 0
| (18)
| 1 || 1000
| (24)
|}
En analysant le tableau, on voit que pour corriger le résultat, il suffit d'ajouter 6. La raison est que le résultat déborde d'un nibble à 16 en binaire, mais à 10 en décimal : il suffit d'ajouter la différence entre les deux, à savoir 6, et le débordement binaire fait son travail. Donc, la correction après une addition est très simple : si le résultat dépasse 9, on ajoute 6.
On peut maintenant implémenter l'additionneur BCD, en combinant le comparateur avec 10, le circuit de correction, et l'additionneur. La première solution calcule deux versions du résultat : la version corrigée, la version normale. Le choix entre les deux est réalisée par un multiplexeur, commandé par le comparateur.
[[File:Additionneur BCD.png|centre|vignette|upright=1.5|Additionneur BCD]]
L'autre solution utilise un circuit commandable qui soit additionne 6, soit ne fait rien. Le choix entre les deux est commandé par le bit calculé par le comparateur.
[[File:Additionneur BCD, seconde version.png|centre|vignette|upright=1.5|Additionneur BCD, seconde version.]]
Une version alternative du circuit précédent est la suivante. Il contient deux additionneurs : un pour additionner les deux chiffres BCD, un autre pour additionner 6 si besoin. Le résultat du comparateur est directement utilisé pour générer l'opérande du second additionneur : 0 ou 6. Le circuit est simple à concevoir, mais gaspille beaucoup de circuit. Idéalement, il vaudrait mieux utiliser un circuit combinatoire d'addition avec une constante.
[[File:Adder 4 Bit BCD.svg|centre|vignette|upright=2|Additionneur BCD, circuit complet.]]
Pour obtenir un additionneur BCD complet, il suffit d’enchaîner les additionneurs précédents, comme on le ferait avec les additionneurs complets dans un additionneur à propagation de retenue. Au final, l'additionneur BCD est beaucoup plus compliqué qu'un additionneur normal, car il rajoute un comparateur ">9", un petit additionneur pour ajouter 6 et éventuellement d'autres circuits. De plus, il est difficile d'appliquer les optimisations disponibles sur les additionneurs non-BCD. Notamment, les circuits d'anticipation de retenue sont totalement à refaire et le résultat est relativement compliqué. C'est ce qui explique pourquoi le BCD a progressivement été abandonné au profit du binaire simple.
La soustraction en BCD se fait comme en binaire : le nombre à soustraire est remplacé par son complément, le circuit additionne le complément et l'autre opérande, le débordement d'entier fait que le résultat marche. Sauf qu'ici, le complément est un complément à 9. Il se calcule chiffre par chiffre : chaque chiffre est remplacé par (9 - le chiffre en question).
===L'additionneur BCD par ajustement décimal===
L'additionneur BCD précédent effectuait son travail chiffre BCD par chiffre BCD, mais il existe des additionneurs BCD qui font autrement. Sur les premiers processeurs x86, il n'y avait pas d'opération d'addition BCD proprement dit, seulement une addition binaire normale de 8, 16 ou 32 bits. Par contre, elle était secondée par une opération dite d''''ajustement décimal''' qui transformait un nombre binaire en nombre codé en BCD. L'opération d'ajustement décimal prenait un opérande de 8 bits codé en binaire et fournissait un résultat de la même taille, c'est à dire deux chiffres BCD. Effectuer une addition BCD demandait donc de faire deux opérations à la suite : une addition binaire simple, suivie par l'opération d'ajustement décimal. Cela permettait de gérer des nombres entiers en binaire usuel et des entiers BCD sans avoir deux instructions d'addition séparées pour les deux, sans compter que cela simplifiait aussi les circuits d'addition.
L'ajustement décimal s'effectue en ajoutant une constante bien précise à l'opérande à convertir en BCD. L'idée est que la constante est découpée en morceaux de 4 bits, correspondant chacun à un chiffre BCD de l'opérande, chaque morceau contenant soit un 0, soit 6. Cela permet d'ajouter soit 0, soit 6, à chaque chiffre BCD, et donc de le corriger. La propagation des retenues d'un chiffre à l'autre est effectuée automatiquement par l'addition binaire de la constante. L'opération d'ajustement décimal calcule automatiquement la constante. Elle découpe l'opérande en ''nibbles'', vérifie si chaque ''nibble'' est supérieur ou égal à 10, puis détermine la valeur de chaque ''nibble'' de la constante finale. Par exemple, si je prends l'opérande 1001 1110, le ''nibble'' de poids faible déborde, alors que celui de poids fort non. La constante sera donc 0000 0110 : 0x06. Inversement, si le ''nibble'' de poids fort déborde et pas celui de poids faible, la constante sera alors 0x60. Et la constante est de 0x66 si les deux ''nibbles'' débordent, de 0x00 si aucun ne déborde.
Le circuit d’ajustement décimal est donc composé de trois étapes : deux étapes pour calculer la constante, et un circuit d'addition pour additionner cette constante au nombre de départ. La première étape découpe l'opérande en morceaux de 4 bits, en chiffres BCD, et vérifie si chacun d'entre eux vaut 10 ou plus. La seconde étape prend les résultats de la première étape, et les combine pour calculer la constante. Enfin, on trouve l'addition finale, qui était réalisée par un circuit d'addition utilisé à la fois pour l'ajustement décimal et l'addition binaire. La différence entre une addition normale et une opération d'ajustement décimal tient dans le fait que les deux premières étapes sont désactivées dans une addition normale.
[[File:Additionneur BCD parallèle.png|centre|vignette|upright=1.5|Additionneur BCD parallèle]]
===L'additionneur biquinaire===
Les entiers BCD ne sont qu'un des encodages hybrides entre décimal et binaire. L'encodage biquinaire est l'un d'entre eux et nous allons faire un rappel rapide à ce sujet. Pour simplifier, un chiffre encodé en biquinaire est composé de deux parties : un bit, couplé à une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits (0, 1, 2, 3 et 4). Le bit indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9.
Additionner deux nombres de biquinaire demande donc d'additionner deux parties quinaires encodées en ''one-hot'' et d'additionner deux bits. Mais attention : il faut tenir compte de la retenue de l'addition des parties quinaires. Et idéalement, il faut aussi tenir compte d'une retenue entrante, provenant de l'addition de la colonne de chiffres précédente. Toute la difficulté vient de la création de l'additionneur ''one-hot''. Heureusement, vu qu'il n'y a que 4-5 bits à additionner, il est souvent fabriqué à partir de sa table de vérité.
[[File:Additionneur bi-quinaire.png|centre|vignette|upright=2|Additionneur bi-quinaire]]
Un avantage du biquinaire est que le calcul du complément à 9 est très simple. Il faut pour cela : inverser la partie binaire avec une porte NON, puis inverser l'ordre des bits de la partie quinaire. Concrètement, le bit de poids faible devient le bit de poids fort, et ainsi de suite. Par exemple, une partie quinaire 01000 devient 00010, 10000 devient 00001, 00100 ne change pas, etc. Le tout peut se calculer avec une porte NON et 5 multiplexeurs.
===L'additionneur BCD avec calculs intermédiaires en biquinaire===
L'ordinateur IBM 1401, un ancien ''mainframe'' des années 60, utilisait un additionneur BCD un peu particulier. Les nombres étaient encodés en BCD dans la mémoire de l'ordinateur, mais les circuits de calcul utilisaient la représentation biquinaire. Lors d'un calcul, le processeur de l'ordinateur traduisait les chiffres BCD en représentation biquinaire, faisait une addition en biquinaire, avant de traduire le résultat en BCD normal.
Pour être précis, l'IBM 1401 utilisait une variante du biquinaire. L'encodage biquinaire de l'IBM 1401 est le suivant : la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre pair encodé par la partie quinaire. Si l'IBM 1401 utilisait cette variante du biquinaire, c'est car elle donnait des circuits de conversion BCD-biquinaire plus économes en portes logiques et plus rapides.
La partie binaire est le bit de poids faible du chiffre BCD, la partie biquinaire est calculée par un simple décodeur qui prend en entrée le chiffre BCD, amputé de son bit de poids faible. La traduction inverse demande d'utiliser un encodeur, à la place du décodeur. Par contre, le circuit d'addition biquinaire était plus compliqué du fait de la gestion des retenues. L'addition des parties binaires et quinaires se faisait en parallèle, dans deux additionneurs séparés. Cependant, l'addition des parties binaire fournit une retenue, qu'il faut prendre en compte. Pour cela, l'IBM 1401 disposait d'un troisième additionneur qui fournissait le résultat final, encodé en biquinaire.
[[File:Additionneur bi-quinaire de l'IBM 1401.png|centre|vignette|upright=2|Additionneur biquinaire de l'IBM 1401]]
Une implémentation moderne demanderait d'utiliser des portes ET combinées à des portes OU, le circuit pouvant être construit simplement à partir de sa table de vérité. Sur l'IBM 1401, le circuit était cependant différent, en raison de l'utilisation de OU câblés, des croisements de fils qui fonctionnent comme des portes OU, que nous n'avons pas encore vu pour le moment, mais qui seront détaillés dans quelques chapitres. Les OU câblés étaient utilisés pour simplifier le design du circuit, mais demandaient des portes logiques spécifiques, ce qui collait avec le fait que ce ''mainframe'' utilisait des transistors en Germanium. L'implémentation exacte est décrite dans cet article de blog, mais je ne recommande sa lecture qu'à ceux qui savent ce qu'est un OU câblé :
* [https://www.righto.com/2015/10/qui-binary-arithmetic-how-1960s-ibm.html Qui-binary arithmetic: how a 1960s IBM mainframe does math].
==Les débordements d'entier lors d'une addition/soustraction==
Les instructions arithmétiques manipulent des entiers codés sur un nombre fixe de bits, qui ne peuvent prendre leurs valeurs que dans un intervalle. Pour les nombres positifs, un ordinateur qui code ses entiers sur n bits pourra coder tous les entiers allant de 0 à <math>2^n -1</math>. Pour les nombres négatifs, l'intervalle est différent et dépend de la représentation utilisée. Dans le cas général, l'ordinateur peut coder les valeurs comprises de <math>N_\text{min}</math> à <math>N_\text{max}</math>. Si le résultat d'un calcul sort de cet intervalle, il ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math> , alors que la valeur basse vaut <math>N_\text{min} - 1</math> (avec <math>N_\text{max}</math> et <math>N_\text{min}</math> respectivement la plus grande et la plus petite valeur codable par l'ordinateur).
===La correction des débordements d'entier : l'arithmétique saturée===
Quand un débordement d'entier survient, tous les circuits de calcul ne procèdent pas de la même manière. Dans les grandes lignes, il y a deux réactions possibles : soit on corrige automatiquement le résultat du débordement, soit on ne fait rien et on se contente de détecter le débordement.
Si le débordement n'est pas corrigé automatiquement, le circuit ne conserve que les bits de poids faibles du résultat. Les bits en trop sont simplement ignorés. On dit qu'on utilise l''''arithmétique modulaire'''. Le problème avec ce genre d'arithmétique, c'est qu'une opération entre deux grands nombres peut donner un résultat très petit. Par exemple, si je dispose de registres 4 bits et que je souhaite faire l'addition 1111 + 0010 (ce qui donne 15 + 2), le résultat est censé être 10001 (17), ce qui est un résultat plus grand que la taille d'un registre. En conservant les 4 bits de poids faible, j’obtiens 0001 (1). En clair, un résultat très grand est transformé en un résultat très petit. Cela peut poser problèmes si on travaille uniquement avec des nombres positifs, mais c'est aussi utilisé pour coder des nombres en complément à deux.
D'autres circuits utilisent ce qu'on appelle l''''arithmétique saturée''' : si un calcul génère un débordement, on arrondi le résultat au plus grand entier supporté par le circuit. Les circuits capables de calculer en arithmétique saturée sont un peu plus complexes, vu qu'il faut rajouter des circuits pour corriger le résultat en cas de débordement. Il suffit généralement de rajouter un ''circuit de saturation'', qui prend en entrée le résultat et le corrige en cas de débordement. Ce circuit de saturation met la valeur maximale en sortie si un débordement survient, mais se contente de recopier le résultat du calcul sur sa sortie s'il n'y a pas de débordement. Typiquement, il est composé d'une couche de multiplexeurs, qui sélectionnent quelle valeur mettre sur la sortie : soit le résultat du calcul, soit le plus grand nombre entier géré par le processeur, soit le plus petit (pour les nombres négatifs/soustractions).
L'arithmétique saturée est utilisée pour les additions et soustractions, mais c'est plus rare pour les multiplications/divisions. Une des raisons est que le résultat d'une addition/soustraction prend un bit de plus que le résultat, là où les multiplications doublent le nombre de bits. Quand une addition déborde, le résultat réel est proche de la valeur maximale codable. mais quand une multiplication déborde, le résultat peut parfois valoir 200 à 60000 fois plus que la valeur maximale codable. Les calculs avec une valeur saturée/corrigée sont donc crédibles pour une suite d'additions, mais pas pour une suite de multiplications.
===La détection des débordements entiers===
Quand un débordement d'entier a eu lieu, il vaut mieux que l'additionneur prévienne ! Pour cela, l'additionneur a une '''sortie de débordement''', parfois nommée ''Overflow'', dont la valeur indique si l'addition a généré un débordement d'entier ou non. Reste que détecter un débordement ne se fait pas de la même manière selon que l'on parle d'un additionneur non-signé ou signé.
Pour les additionneur non-signés, l'additionneur calcule un bit de plus que ce qui est supporté par l'ordinateur. Par exemple, un additionneur 32 bits fournit un résultat sur 33 bits, un débordement d'entier a lieu quand le 33ème bit est à 1. Précisément, la sortie de débordement n'est autre que la retenue finale, celle fournie par le dernier additionneur complet. Le seul type de débordement possible est un débordement par le haut, où le résultat dépasse la valeur maximale. Avec l'arithmétique saturée, le circuit de saturation consiste en une seule couche de multiplexeurs, voire en un circuit de mise à la valeur maximale tel que vu dans le chapitre sur les opérations bits à bits.
[[File:Gestion des débordements d'entiers.png|centre|vignette|upright=2.5|Gestion des débordements d'entiers lors d'une addition non-signée.]]
Pour les additionneurs non-signés, la gestion des débordements d'entiers dépend fortement de la représentation signée. Nous allons étudier le cas du complément à deux. Si vous vous rappelez le chapitre 1, les calculs sur des nombres en complètement à deux utilisent les règles de l'arithmétique modulaire, c'est une condition nécessaire. À priori, on peut penser que dans ces conditions, les débordements d'entiers sont une chose parfaitement normale, qui nous permet d'avoir des résultats corrects. Néanmoins, certains débordements d'entiers peuvent survenir malgré tout et produire des bugs assez ennuyeux.
Si l'on tient en compte les règles du complément à deux, on sait que le bit de poids fort (le plus à gauche) permet de déterminer si le nombre est positif ou négatif : il indique le signe du nombre. Tout se passe comme si les entiers en complément à deux étaient codés sur un bit de moins, et avaient leur longueur amputé du bit de poids fort. Si le résultat d'un calcul écrase le bit de poids fort, il y a un débordement d'entiers. Il existe une règle simple qui permet de détecter ces débordements d'entiers. L'addition de deux nombres positifs ne peut pas être un nombre négatif. Si on additionne deux nombres dont le bit de signe est à 0 et que le bit de signe du résultat est à 1, on est en face d'un débordement d'entiers. Même chose pour deux nombres négatifs : le résultat de l'addition ne peut pas être positif. On peut résumer cela en une phrase : si deux nombres de même signe sont ajoutés, un débordement a lieu quand le bit du signe du résultat a le signe opposé.
Modifier les circuits d'au-dessus pour qu'ils détectent les débordements en complément à deux est simple comme bonjour : il suffit créer un petit circuit combinatoire qui prenne en entrée les bits de signe des opérandes et du résultat, et qui fasse le calcul de l'indicateur de débordements. Si l'on rédige sa table de vérité, on doit se retrouver avec la table suivante :
{|class="wikitable"
|-
!Entrées
!Sortie
|-
|000||0
|-
|001||1
|-
|010||0
|-
|011||0
|-
|100||0
|-
|101||0
|-
|110||1
|-
|111||0
|}
L'équation de ce circuit est la suivante, avec <math>S_a</math> et <math>S_b</math> les signes des deux opérandes, et <math>C_i</math> la retenue de la colonne précédente :
: <math> ( S_a . S_b . \overline{C_i} ) + ( \overline{S_a} . \overline{S_b} . C_i )</math>
En simplifiant, on obtient alors :
: <math>( S_a . S_b ) \oplus C_i</math>
Or, il se trouve que <math>S_a . S_b</math> est tout simplement la retenue en sortie du dernier additionneur, que nous noterons <math>C_o</math>. On trouve donc :
: <math>C_o \oplus C_i</math>
Il suffit donc de faire un XOR entre la dernière retenue et la précédente pour obtenir le bit de débordement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de décalage et de rotation
| prevText=Les circuits de décalage et de rotation
| next=Les circuits de comparaison
| nextText=Les circuits de comparaison
}}
</noinclude>
g040ny634n7ocy8umsawohqvewxl0l1
768657
768652
2026-06-25T19:43:55Z
Mewtow
31375
/* Les additionneurs avec anticipation de retenue entre plusieurs additionneurs */
768657
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir les circuits capables de faire une addition ou une soustraction, ainsi que quelques circuits spécialisés. Précisons cependant que les fabricants de processeurs travaillent d'arrache-pied pour trouver des moyens de rendre ces circuits de calcul plus rapides et plus économes en énergie. Autant vous dire que les circuits que vous allez voir sont vraiment des circuits qui font pâle figure comparé à ce que l'on peut trouver dans un vrai processeur commercial !
==Les circuits pour additionner 2 ou 3 bits==
L'addition se fait en binaire de la même manière qu'en décimal. On additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. La soustraction fonctionne sur le même principe, sur le même modèle qu'en décimal.
[[File:Binary Addition Demonstration.svg|centre|vignette|Exemple d'addition en binaire.]]
En clair, additionner deux nombres demande d'additionner 2 bits et une retenue sur chaque colonne, et de propager les retenues d'une colonne à l'autre. La propagation des retenues est quelque chose de simple en apparence, mais qui est sujet à des optimisations extraordinairement nombreuses. Aussi, pour simplifier l'exposition, nous allons voir comment gérer une colonne avant de voir comment sont propagées les retenues.
[[File:Full Adder Block.svg|vignette|upright=1|class=transparent|Additionneur complet.]]
Si on effectue une addition en colonne, on doit additionner les deux bits sur la colonne, mais aussi additionner une éventuelle retenue. Il faut donc créer un circuit qui additionne trois bits : deux bits de données, plus une retenue. Ce circuit qui additionne trois bits est appelé un '''additionneur complet'''. Il fournit en sortie deux bits : un bit de somme et une retenue sortante. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|-
||0||0||0|| ||0||0
|-
||0||0||1|| ||0||1
|-
||0||1||0|| ||0||1
|-
||0||1||1|| ||1||0
|-
||1||0||0|| ||0||1
|-
||1||0||1|| ||1||0
|-
||1||1||0|| ||1||0
|-
||1||1||1|| ||1||1
|}
Il est possible d'utiliser un tableau de Karnaugh pour traduire la table de vérité, mais elle donne un résultat assez compliqué. La retenue et le bit de somme sont calculés à part. La retenue est calculée avec trois portes ET et une porte OU. Le bit de somme est calculé lui avec 4 portes ET, une porte OU, et trois portes NON.
[[File:Full Adler with 3 entries.png|centre|vignette|upright=2|Additionneur complet, fabriqué à partir de la table de vérité.]]
D'autres méthodes donnent des résultats plus compréhensibles. Nous allons les voir dans la suite de cette section.
===L'additionneur complet basé sur des demi-additionneurs===
[[File:1-bit half-adder.svg|class=transparent|vignette|Demi-addtionneur.]]
Nous avons déjà vu comment additionner deux bits dans le chapitre sur les incrémenteurs, mais quelques rappels ne seront pas de trop. Le '''demi-additionneur''' est un circuit qui additionne deux bits. Il implémente la table d'addition, qui est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Mais un demi-additionneur n'additionne que deux bits, il manque de quoi additionner la retenue. Une solution pour cela est d'enchaîner deux demi-additionneurs : un qui additionne les deux bits de données, et un second qui additionne la retenue au résultat du précédent. La retenue finale se calcule en combinant les sorties de retenue des deux demi-additionneurs, avec une porte OU. Le résultat est un additionneur complet fabriqué avec deux demi-additionneurs. Pour vous en convaincre, établissez la table de vérité de ce circuit, vous verrez que ça marche.
[[File:Full Adder Blocks.svg|centre|vignette|upright=2|Additionneur complet fabriqué avec deux demi-additionneurs.]]
[[File:Half-adder.svg|vignette|upright=1|Circuit d'un demi-additionneur naïf.]]
L'implémentation naïve d'un demi-additionneur utilise une porte XOR et une porte ET, comme illustré ci-contre. Le circuit s'obtient en faisant uen traduction littérale de la table de vérité du circuit. Si je dis qu'elle est naïve, c'est car il est possible de l'optimiser, de manière à éliminer des redondances cachées. Mais nous avions déjà vu cela dans le chapitre sur les incrémenteurs, aussi laissons cela pour plus tard.
En combinant deux demi-additionneurs de ce type, on obtient l'additionneur complet suivant. Les deux sorties de retenue des demi-additionneurs sont combinées avec une porte OU, pour calculer la retenue finale.
{| class="flexible"
|[[File:Full Adder Modules.svg|class=transparent|Composition d'un additionneur complet. On voit bien que celui-ci est composé de deux demi-additionneurs, en rouge et en bleu, auxquels on a ajouté une porte OU pour calculer la retenue finale.]]
|[[File:Full-adder.svg|300px|class=transparent|Circuit d'un additionneur complet.]]
|}
Le fait de combiner les deux retenues avec une porte OU n'est pas ce qu'il y a de plus intuitif. La table de vérité nous dit que ça fonctionne, mais on comprend mal pourquoi. Mais tout devient plus clair quand on sait que les deux retenues ne peuvent pas être à 1 en même temps. Même dans l'addition 1 + 1 + 1, seule une retenue est à 1. Soit l'addition des deux bits d'opérande donne naissance à une retenue, soit c'est l'addition ''résultat + retenue entrante''. La retenue sortant est donc à 1 quand une des deux addition donne une retenue. D'où l'usage d'une porte OU. Remarquez qu'une porte XOR donne le même résultat, vu que le cas où les deux retenues sont à 1 n'est jamais rencontré.
===La propagation et la génération des retenues===
L'additionneur complet que nous allons voir dans cette section sert à introduire les concepts de propagation, génération et absorption de retenue. Il s'agit de concepts très importants quand on étudie les additionneurs. l'idée part d'un principe très simple : la retenue sortante dépend de la retenue d'entrée. La relation entre les deux se résume à trois cas, qui dépendent de la valeur des deux bits additionnés, nommés A et B.
* Dans le premier cas, la retenue entrante est égale à la retenue sortante. On dit que la retenue entrante est propagée par l'additionneur.
* Dans le second cas, la retenue est forcée à 0 : la retenue sortante vaut 0, peu importe la valeur de la retenue entrante.
* Dans le troisième cas, la retenue est forcée à 1 : la retenue entrante vaut 1, peu importe la valeur de la retenue entrante.
[[File:Signaux P et G fournis par un demi-additionneur.png|vignette|Signaux P et G fournis par un demi-additionneur]]
Maintenant, créons un circuit qui nous dise si une retenue est propagée ou générée. Il a deux bits de sortie, nommés P et G : P pour ''Propagate'', G pour ''Generate''. Le bit P est à 1 si la retenue entrante est propagée, il est à 0 sinon. Le bit G est à 1 si une retenue est générée, à 0 sinon. Une retenue est considérée comme absorbée si elle n'est pas ni propagée ni générée, pas besoin d'un troisième bit pour gérer ce cas.
Il se trouve que ce circuit n'est autre qu'un demi-additionneur ! Pour vous en rendre compte, regardez la table de vérité d'un additionneur complet, illustrée ci-dessous. Lorsque les deux bits d'opérande sont à 0, la retenue sortante vaut toujours 0. Si ils sont tous deux à 1, alors la retenue sortante vaut 1. S'ils sont différents, alors retenues sortante et entrante sont égales. Le bit P est donc généré par une simple porte XOR. Quant au bit G, il est à 1 si les deux bits d'opérandes sont à 1, ce qui correspond à une porte ET. Il se trouve que ces deux portes forment un demi-additionneur !
{|class="wikitable"
|-
! Opérande 1 !! Opérande 2 !! Retenue entrante !! !! Retenue sortante
|- class="f_rouge"
||0||0||0|| ||0
|- class="f_rouge"
||0||0||1|| ||0
|- class="f_bleu"
||0||1||0|| ||0
|- class="f_bleu"
||0||1||1|| ||1
|- class="f_bleu"
||1||0||0|| ||0
|- class="f_bleu"
||1||0||1|| ||1
|- class="f_vert"
||1||1||0|| ||1
|- class="f_vert"
||1||1||1|| ||1
|}
Ensuite, créons un circuit qui prend ces deux sorties P et G, et calcule la retenue sortante en fonction. Le circuit en question a trois entrées : la retenue entrante, les deux bits P et G. La retenue sortante vaut 1 soit si une retenue est générée, soit si la retenue entrante est propagée et qu'elle vaut 1. En notant <math>{C_{in}}</math> et <math>{C_{out}}</math> les retenues entrantes et sortante, on a :
: <math>{C_{out}} = G + \left( P . C_{in} \right)</math>
Le circuit est donc composé d'une porte OU et d'une porte ET. En combinant un demi-additionneur avec le circuit de calcul de retenue sortante vu plus haut, on a :
[[File:Additionneur complet avec propagation et génération de retenue.png|class=transparent|centre|vignette|upright=2|Additionneur complet avec propagation et génération de retenue.]]
Certes, on retombe sur le circuit vu plus haut. Mais il est possible de remplacer le circuit en orange par un autre. Par exemple, il est possible de le remplacer par multiplexeur, qui choisit entre la retenue générée et la retenue entrante (propagée). Le choix se fait selon la valeur du bit P, qui chosiit entre propager la retenue et la générer.
[[File:Additionneur crée avec un multiplexeur.png|centre|vignette|upright=1.5|Additionneur crée avec un multiplexeur]]
Le circuit exact est illustré ci-dessous. Il semble utiliser plus de portes logiques que l'additionneur complet précédent. Cependant, nous verrons dans quelques chapitres qu'il est possible d'implémenter un multiplexeur avec seulement 6 transistors, voire moins ! L'implémentation utilise des portes à transmission, mais nous en reparlerons dans le chapitre sur les transistors, quand nous verrons les additionneurs à ''Manchester Carry Chain''. Au passage, une variante de ce circuit a été utilisée dans le processeur processeur 8086 d'Intel, comme on le verra dans le chapitre suivant.
[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
[[File:FulladderPG.png|vignette|upright=1|Additionneur complet avec deux sorties P et G.]]
Pour finir, sachez qu'il existe des additionneurs qui fournissent : le bit de résultat, deux sorties P et G qui indiquent si l'addition propage ou génère une retenue. Il ne s'agit pas d'additionneurs complets, car il manque de quoi calculer la retenue sortante. De tels additionneurs seront appelés des '''additionneurs partiels'''. De tels additionneurs sont utilisés dans certains additionneurs pour gagner en performance.
Enfin, il existe aussi ce que j'ai décidé d'appeler des '''additionneurs P/G''', qui sont des additionneurs complets auxquels on a ajouté deux sorties P et G, en plus de la sortie de retenue. Leur circuit est le même que celui d'un additionneur complet, auquel on a ajouté deux fils. Vu que ces deux sorties sont fournies par le premier demi-additionneur, ajouter ces deux sorties demande d'ajouter des fils, pas de portes logiques. De tels additionneurs sont aussi utilisés dans certains additionneurs pour gagner en performance.
===L'additionneur complet basé sur une porte à majorité===
Maintenant, voyons une dernière implémentation possible de l'additionneur complet. Mais avant de voir comment l'additionneur est implémenté, nous allons devoir faire un petit focus sur la retenue sortante. Reprenons la table de vérité, et regardons ce qu'il en est pour la retenue sortante uniquement.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante
|-
||0||0||0|| ||0
|-
||0||0||1|| ||0
|-
||0||1||0|| ||0
|-
||0||1||1|| ||1
|-
||1||0||0|| ||0
|-
||1||0||1|| ||1
|-
||1||1||0|| ||1
|-
||1||1||1|| ||1
|}
Vous remarquerez que la retenue sortante est égale au bit majoritaire, parmi les trois bits d'opérande. Si deux ou trois bits sont à 1, la retenue sortante vaut 1. Et réciproquement avec 0. Le circuit de calcul de la retenue peut donc être remplacé par une '''porte à majorité'''.
[[File:Additionneur crée avec une porte à majorité.png|centre|vignette|upright=2|Additionneur crée avec une porte à majorité]]
Mais une porte à majorité est assez gourmande en circuit, ce qui fait qu'un additionneur pareil ne serait pas pratique, ni utile. Du moins, ce serait le cas s'il n'y avait pas une possibilité d'optimisation extrêmement intéressante. Il est possible de calculer le 'bit de somme' à partir de la retenue sortante ! En effet, le bit de somme est l'inverse de la retenue sortante, sauf dans deux cas : les trois bits d'entrée sont à 0, où ils sont tous à 1. Le bit de somme vaut 0 dans le premier cas, 1 dans le second cas.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|- class="f_bleu"
||0||0||0|| ||0||0
|- class="f_vert"
||0||0||1|| ||0||1
|- class="f_vert"
||0||1||0|| ||0||1
|- class="f_vert"
||0||1||1|| ||1||0
|- class="f_vert"
||1||0||0|| ||0||1
|- class="f_vert"
||1||0||1|| ||1||0
|- class="f_vert"
||1||1||0|| ||1||0
|- class="f_rouge"
||1||1||1|| ||1||1
|}
En clair, 6 lignes de la table de vérité sur 8 peuvent se calculer avec une porte à majorité, le reste demandant quelques portes logiques pour faire la correction. L'implémentation en circuit demande donc trois choses :
* de calculer la retenue sortante et de quoi l'inverser ;
* un circuit qui vérifie si tous les bits opérande valent 0 : une porte NOR ou OU fait l'affaire ;
* un circuit qui vérifie s'ils valent tous 1, à savoir une porte ET.
Ensuite, on combine le résultat des trois circuits précédents pour obtenir le résultat final. La combinaison est le fait du circuit en jaune dans le schéma ci-dessous.
[[File:Full adder basé sur une modification de la retenue.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue]]
Le circuit en jaune est assez simple. Quand la porte ET sort un 1, la sortie doit être forcée à 1. Par contre, quand la porte OU sort un zéro, la sortie doit être forcée à 0. Il suffit donc d'enchainer un circuit de mise à 1 et un circuit de mise à 0. Nous avons vu dans le chapitre sur les opérations de masquage qu'il s'agit respectivement d'une porte OU et d'une porte ET.
[[File:Full adder basé sur une modification de la retenue interne, au complet.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue interne, au complet]]
Divers processeurs utilisaient des additionneurs complets de ce type : le fameux Z80, l'Intel 4004 et de l'Intel 8008 sont les exemples les plus notables. Pourtant, on pourrait s'étonner qu'un tel circuit ait existé. Il utilise beaucoup de portes logiques, a une profondeur logique supérieure : il n'a rien d'avantageux. Sauf qu'il était utilisé sur d'anciens processeurs, qui utilisaient la technologie dite TTL, différente de la technologie CMOS des transistors modernes. Et avec la technologie TTL, il est possible de fusionner plusieurs portes logiques ET et NOR en une seule porte logique ET/OU/NON ! Un additionneur complet construit ainsi ne prenait que deux portes logiques : une pour le calcul de la retenue sortante, une autre pour le reste du circuit.
==L'addition non signée==
Voyons maintenant un circuit capable d'additionner deux nombres entiers: l''''additionneur'''. Dans la version qu'on va voir, ce circuit manipulera des nombres strictement positifs. L'addition des nombres codés en complètement à deux sera vu dans une section ultérieure.
===L'additionneur série===
Il est possible d'additionner deux nombres bit par bit, gr$ace à un additionneur complet associé à plusieurs registres à décalages. Les opérandes sont placées chacune dans un registre à décalage, et l'additionneur complet utilise les bits sortants de ces registres à décalage. Le bit du résultat est envoyé au registre à décalage pour le résultat. La retenue de l'addition est stockée dans une bascule de 1 bit, en attente du prochain cycle d'horloge. Un tel additionneur est appelé un '''additionneur série'''. Il a été utilisé sur d'anciens ordinateurs dans les années 50-60, aussi bien des prototypes que des ordinateurs commerciaux.
[[File:Additionneur série.jpg|centre|vignette|upright=2.5|Additionneur série.]]
===L'additionneur à propagation de retenue===
L''''additionneur à propagation de retenue''' est aussi appelé l'additionneur ''ripple carry'', c'est son nom anglais. J'utiliserais occasionnellement le nom anglais dans ce qui suit, car celui-ci est plus court. Ilutilise un additionneur complet pour chaque colonne de l'addition. Pour gérer les retenues, il suffit ainsi de câbler des additionneurs complets les uns à la suite des autres, chacun envoyant sa retenue à la colonne suivante.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Notons la présence de la retenue sortante, qui est utilisée pour détecter les débordements d'entier, ainsi que pour d'autres opérations. Le bit de retenue final est souvent stocké dans un registre spécial du processeur (généralement appelé ''carry flag'').
Notez aussi, à droite du schéma précédent, l'entrée de retenue <math>\text{R}_\text{Entrée}</math>. Elle est très utile pour l'implémentation de certaines opérations comme l'inversion de signe, la soustraction, l'incrémentation, etc. Certains processeurs sont capables de faire une opération appelée ADC, ADDC ou autre nom signifiant ''Addition with Carry'', qui permet de faire le calcul A + B + Retenue (la retenue en question est la retenue sortante de l'addition précédente, stockée dans le registre ''carry flag''). Son utilité principale est de permettre des additions d'entiers plus grands que ceux supportés par le processeur. Par exemple, cela permet de faire des additions d'entiers 32 bits sur un processeur 16 bits.
[[File:Adder Network 3 Bit Sum Delay 003.svg|vignette|upright=0.5|Propagation de retenue dans l'additionneur.]]
Cet additionneur utilise très peu de portes logiques, ce qui fait qu'il était utilisé sur certains processeurs 8 et 16 bits, dont le budget en portes logiques était limité. Bien que très simple, cet additionneur est cependant peu performant. Son temps de calcul est proportionnel à la taille des opérandes. Par exemple, additionner deux opérandes de 32 bits prendra deux fois plus de temps que pour des opérandes de 16 bits. La raison est que les retenues doivent se propager à travers le circuit, du premier additionneur jusqu'au dernier.
L'addition étant une opération fréquente, d'autres additionneurs ont été inventés. Ils utilisent des optimisations qui utilisent plus de circuits pour gagner en rapidité. Mais avant de voir ces optimisations, nous allons voir s'il est possible d'optimiser les additionneurs à propagation de retenue.
===L'additionneur à saut de retenue===
L'additionneur à propagation de retenue peut être optimisé de deux manières. La première accélère la propagation de la retenue, en agissant au niveau des additionneurs complets. La ''Manchester carry chain'' est une optimisation de ce type, mais on ne peut pas encore expliquer à ce stade du cours, car elle optimisait les additionneurs complets au niveau des transistors eux-mêmes. Elle a été utilisée sur de nombreux processeurs connus, comme le 8086 d'Intel ou le Z80.
La seconde manière rend l'addition plus rapide dans certains cas. Le résultat est un additionneur dont le temps de calcul est variable. Le calcul prendra quelques cycles d'horloges avec certains opérandes, mais d'autres opérandes ne verront aucune amélioration. Il n'améliore pas le pire des cas, dans lequel la retenue doit être propagée du début à la fin, du bit de poids faible au bit de poids fort. Mais dans d'autres cas, il permet d'avoir le résultat en avance. L'optimisation en question donne l''''additionneur à saut de retenue''' (''carry-skip adder'').
Il est composé de blocs qui additionnent 4/5 bits, rarement plus. La taille d'un bloc varie d'un additionneur à l'autre, elle peut même varier dans l'additionneur, mais nous allons prendre un exemple où les blocs font 4 bits. L'idée est de détecter précocement si la retenue entrante est propagée à travers tout le bloc. Si la retenue est propagée dans tous le bloc, on peut directement l'envoyer sur la sortie de retenue, la retenue saute le bloc entier. Dans le cas contraire, on doit calculer la retenue normalement. Le choix entre les deux est le fait d'un multiplexeur. Toute la difficulté est de savoir comment commander le multiplexeur.
[[File:Carry skip adder, principe de base.png|centre|vignette|upright=2.5|Carry skip adder : principe de base]]
Pour savoir si une retenue est propagée dans un bloc entier, il faut utiliser des additionneurs complets, qui ont en plus une sortie P indiquant que l'additionneur propage la retenue. Le bloc propage une retenue si chaque additionneur complet propage la retenue, donc s'ils ont tous leur sortie P à 1. Un simple ET logique suffit à déterminer si c'est le cas. Le signal de commande du multiplexeur est donc un vulgaire ET entre toutes les sorties P des additionneurs complets du bloc.
[[File:Additionneur à saut de retenue.png|centre|vignette|upright=2.5|Calcul de la commande du MUX.]]
Voici ce que ça donne pour un bloc de 4 bits :
[[File:CSAdder4Bit.svg|centre|vignette|upright=1.5|Additionneur ''carry skip'' : bloc de 4 bits.]]
L'additionneur à saut de retenue est construit en assemblant plusieurs blocs de ce type.
[[File:BCSAdder16Bit.svg|centre|vignette|upright=3|Additionneur à saut de retenue.]]
===L'additionneur à sélection de retenue===
L''''additionneur à sélection de retenue''' utilise aussi des blocs, comme les additionneurs précédents. L'addition se fait en deux versions : une avec la retenue du bloc précédent valant zéro, et une autre version avec la retenue du bloc précédent valant 1. Il suffira alors de choisir le bon résultat avec un multiplexeur, une fois cette retenue connue. On gagne ainsi du temps en calculant à l'avance les valeurs de certains bits du résultat, sans connaître la valeur de la retenue. Petit détail : sur certains additionneurs à sélection de retenue, les blocs de base n'ont pas la même taille. Cela permet de tenir compte des temps de propagation des retenues entre les blocs.
[[File:Additionneur à sélection de retenue.png|centre|vignette|upright=1.5|Additionneur à sélection de retenue avec seulement deux blocs.]]
Dans les exemples du dessus, chaque sous-additionneur étaient des additionneurs à propagation de retenue. Mais ce n'est pas une obligation, et tout autre type d’additionneur peut être utilisé. Par exemple, on peut faire en sorte que les sous-additionneurs soient eux-mêmes des additionneurs à sélection de retenue, et poursuivre ainsi de suite, récursivement. On obtient alors un '''additionneur à somme conditionnelle''', plus rapide que l'additionneur à sélection de retenue, mais qui utilise beaucoup plus de portes logiques.
===Les additionneurs à anticipation de retenue===
Les '''additionneurs à anticipation de retenue''' calculent chaque retenue sans avoir à propager les retenues précédentes. Au lieu de calculer les retenues une par une, ils calculent toutes les retenues en parallèle, à partir des bits des opérandes. Une fois les retenues pré-calculées, il suffit de les additionner avec les deux bits adéquats, pour obtenir le résultat. Ces additionneurs sont composés de deux parties :
* un circuit qui pré-calcule les retenues, sans les propager, directement à partir des opérandes ;
* d'une couche d'additionneurs complets, qui additionnent chacun deux bits d'opérande et la retenue pré-calculée.
[[File:Additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Additionneur à anticipation de retenue.]]
Le calcul des retenues se fait en deux étapes. La première détermine si les retenues sont propagées ou générées, pour chaque colonne. Elle détermine, pour chaque colonne, les bits P et G qui indiquent respectivement : qu'une retenue a été générée sur cette colonne, que la colonne propage la retenue précédente. La seconde étape utilise ces signaux P et G pour déterminer la retenue sur chaque colonne. Le circuit de calcul des retenues est donc composé de deux parties : une couche de demi-additionneurs pour générer les signaux P et G, un circuit de calcul des retenues proprement dit.
[[File:Circuit complet d'un additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Circuit complet d'un additionneur à anticipation de retenue.]]
Pour rappel, la retenue de la colonne i, notée <math>C_i</math> est égale à :
: <math>G_i + (P_i . C_{i-1})</math>, avec G le signal de génération de retenue, P le signal de propagation de retenue.
Si on utilisait cette formule sans trop réfléchir, on retomberait sur un additionneur à propagation de retenue inutilement compliqué. L'astuce des additionneurs à anticipation de retenue consiste à remplacer le terme Ci−1 par sa valeur calculée avant. Par exemple, je prends un additionneur 4 bits. Je dispose de deux nombres A et B, contenant chacun 4 bits : A3, A2, A1, et A0 pour le nombre A, et B3, B2, B1, et B0 pour le nombre B. Si j'effectue les remplacements, j'obtiens les formules suivantes :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Ces formules nous permettent de déduire la valeur d'une retenue directement : il reste alors à créer un circuit qui implémente ces formules, et le tour est joué.
[[File:Four bit adder with carry lookahead.svg|centre|vignette|upright=1.5|Additionneur à anticipation de retenue de 4 bits.]]
Plus haut, j'ai dit que le circuit était composé de trois couches : une couche de demi-additionneur pour les signaux P et G ; un circuit d'anticipation de retenue, puis l'addition finale. Mais les deux bits d'opérandes sont déjà additionnés dans la couche de demi-additionneur, autant réutiliser le résultat pour l'additionner à la retenue. Pour éliminer toute redondance de ce type, il est possible d'utiliser des additionneurs partiels (des additionneurs où la sortie de retenue a été remplacée par deux sorties P et G). L'additionneur final est alors composé de plusieurs additionneurs complets de ce type, couplé à un circuit d'anticipation de retenue.
[[File:Cla4bits.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue de 4 bits.]]
Les additionneurs à anticipation de retenue sont plus rapides que les additionneurs à propagation de retenue. La raison est que les retenues sont calculées rapidement, dans un temps plus petit qu'en propageant la retenue. Pour rentrer dans les détails mathématiques, le temps de calcul n'est pas proportionnel au nombre de bits, mais proportionnel au logarithme de ce nombre de bits. Par contre, le gain en temps de calcul s'accompagne d'un cout en prote logique conséquent. Le circuit qui calcule les retenues anticipées utilise beaucoup de portes logiques.
La conséquence est qu'utiliser un additionneur à anticipation de retenue est une solution pratique pour additionner 4 ou 8 bits. Mais pour des opérandes plus grandes, de 16 ou 32 bits, cela utiliserait trop de portes logiques. Une partie de ce cout en portes logiques est cependant lié à des redondances. Si on analyse deux retenues différentes, à des colonnes différentes. Les circuits qui calculent ces deux retenues seront différents, mais ils contiendront des sous-circuits identiques. Pour vous en rendre compte, reprenons les quatre équations obtenues précédemment et essayez de trouver des redondances :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Vous devriez trouver :
* le terme ''P0 · C0'' dans toutes les lignes ;
* les termes ''P1 · P0 · C0'' et ''P1 · P0'' dans les trois dernières lignes ;
* le terme ''P2 · P1 · P0 · C0'' ; ''P2 · P1 · G0'' et ''P2 · G1'' dans les deux dernières lignes.
===Les additionneurs à calcul parallèle de préfixe===
Il existe plusieurs manières d'exploiter de telles redondances, exploitées dans les '''additionneurs à calcul parallèle de préfixe'''. L'optimisation apportée est de générer des signaux ''propagate'' et ''generate'' pour des groupes de 2, 3, 4, ..., N colonnes. Par exemple, il est possible de générer un signal ''P 0 vers 3'', qui indique si la retenue de la première colonne est propagée jusqu'à la 4ème colonne. Un autre exemple est un signal qui indique qu'une retenue a été générée entre les colonnes 4 à 7 génèrent une retenue ou non. En clair, les signaux P et G ont maintenant un intervalle, qui précise de quelle colonne vers quelle colonne se fait la propagation, ou entre quelles colonnes se fait la génération.
Il est possible de combiner les signaux P et G de deux groupes de colonne, s'ils sont contiguës (ils peuvent aussi se recouvrir). Par exemple, il est possible de calculer les bits P et G pour les colonnes 0 à 10, à partir des deux signaux P/G des colonnes 0-4 et 5-10. Pour cela, les équations sont assez simples. Si je prends deux groupes nommés A et B, avec A pour les colonnes de poids fort et B celles de poids faible, on a :
: <math>P_{sortie} = P_A . P_B</math>
: <math>G_{sortie} = G_A . P_A . G_B</math>
Pour les bits des colonnes 2, 4, 8, 16, 32, 64, et autres, le calcul est simple. L'idée est de grouper les colonnes par groupes de 2. Puis, on calcule chaque groupe de 4 bits à partir de deux groupes de 2. Puis les groupes de 8 bits sont calculés à partir de deux groupes de 4 bits, et ainsi de suite pour toute puissance de deux.
[[File:Squelette d'un additionneur à calcul parallèle de préfixe.jpg|centre|vignette|upright=2|Squelette d'un additionneur à calcul parallèle de préfixe]]
Les seules difficultés surviennent pour les colonnes dont le nombre n'est pas une puissance de deux. Il y a plusieurs manières pour subdiviser ces colonnes en intervalles plus petits et combiner le tout. Et elles donnent chacune des additionneurs différents. Ils portent les noms d'''additionneur de Ladner-Fisher'', d'''additionneur de Brent-Kung'', d'''additionneur de Kogge-Stone'', etc. Dans le cas général, l'additionneur de Kogge-Stone est un des plus rapide, mais aussi un des plus gourmand en circuits, alors que c'est l'inverse pour l'additionneur de Brent-Kung. Les deux additionneurs sont illustrés ci-dessous, pour des opérandes de 8 bits. Dans ceux-ci, chaque losange calculent les bits P et G, à partir des signaux P et G de deux groupes précédents.
{|
|[[File:Brent-kung-8-bit.png|vignette|Brent-kung-8-bit]]
|[[File:Kogge-stone-8-bit.png|vignette|upright=1.2|Kogge-stone-8-bit]]
|}
===Les additionneurs à anticipation de retenue multi-niveau===
Dans cette section, nous allons voir des additionneurs conçus en assemblant des additionneurs à plus simples, qui additionnent environ 4 à 5 bits, parfois plus, parfois moins. Ces additionneurs simples seront nommés ''blocs'' dans ce qui suit, et l'un d'entre eux est illustré ci-contre. Chaque bloc prend en entrée un morceau des deux opérandes à additionner, mais aussi une retenue d'entrée.
Les blocs sont tous identiques dans le cas le plus simple, mais il est possible d'utiliser des blocs de taille variable. Par exemple, le premier bloc peut avoir des opérandes de 6 bits, le second des opérandes de 7 bits, etc. Faire ainsi permet de gagner un petit peu en performances, si la taille de chaque bloc est bien choisie. La raison est une question de temps de propagation des retenues. La retenue met plus de temps à se propager à travers 8 blocs qu'à travers 4, ce qui prend plus de temps qu'à travers 2 blocs, etc. En tenir compte fait que la taille des blocs tend à augmenter ou diminuer quand on se rapproche des bits de poids fort.
Il est possible d'utiliser l'anticipation de retenue entre deux blocs. Par exemple, il est possible de créer un additionneur 16 bits en utilisant 4 additionneurs/blocs de 4 bits. L'additionneur utilisé peut être un additionneur à propagation de retenue, ou à anticipation de retenue, peu importe.
Pour cela, les additionneurs de 4 bits doivent fournir deux sorties P et G. Les deux sorties indiquent que le bloc soit a généré une retenue, soit a propagé la retenue entrante. Par propagé, on veut dire que la retenue entrante du bloc s'est retrouvée sur la sortie de retenue du bloc. C'est le cas si les 4 colonnes propagent la retenue, ce qui signifie que :
: <math>P_\text{4 bits} = P_0 \cdot P_1 \cdot P_2 \cdot P_3</math>
Pour la génération, cela veut dire qu'une colonne a générée une retenue, qui a été propagée jusqu'à la sortie. En clair, pour chaque colonne, il faut que le G = 1, et que les signaux P des colonnes soient à 1. Pour les 4 colonnes, cela donne :
: <math>G_\text{4 bits} = G_3 + G_2 \cdot P_3 + G_1 \cdot P_3 \cdot P_2 + G_0 \cdot P_3 \cdot P_2 \cdot P_1</math>
[[File:Cla4bitsPG.png|centre|vignette|upright=2|Additionneur à anticipation de retenue de 4 bits.]]
Pour combiner 4 blocs de 4 bits, on devra donc combiner 4 bits P et G, avec un circuit d'anticipation de retenue.
[[File:16-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 16 bits.]]
Il est même possible d'aller plus loin et de combiner 4 additionneurs précédents pour obtenir un additionneur 64 bits.
[[File:64-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 64 bits.]]
Le résultat est un additionneur à plusieurs niveaux d’anticipation de retenue. Une anticipation de retenue dans un blocs de 4 bits (facultative), entre blocs de 4 bits, entre blocs de 16 bits. La même logique peut être utilisée avec des blocs de taille différente de 4, 16 et 64 bits.
===Le calcul parallèle de la retenue===
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Plus haut, nous avons vu l'anticipation de retenue. Il est possible de l'adapter pour fonctionner avec des blocs. Une première idée est d'utiliser plusieurs additionneurs à anticipation de retenue, chacun étant un bloc. Vu qu'il s'agit d'additionneurs, ils disposent d'une sortie de retenue pour la retenue sortante, et d'une entrée de retenue pour la retenue entrante. Par exemple, le schéma ci-contre illustre un additionneur 4 bits de ce type.
De ce fait, il est possible de propager les retenues d'un bloc à l'autre. par exemple, on peut utiliser 4 additionneurs à anticipation de retenue de 4 bits, et propager les retenues entre eux. Le premier bloc calcule une retenue, qui est propagée au second bloc. Le second bloc calcule lui aussi une retenue, qui est propagée au troisième bloc, etc.
[[File:4008 Functional Diagram.svg|vignette|upright=1|4008 Functional Diagram]]
Le gain en performance est significatif, mais il est possible de faire presque aussi rapide, tout en économisant beaucoup de circuits. L'idée est de simplifier les additionneurs 4 bits, afin de ne pas utiliser une anticipation de retenue complète. L'anticipation de retenue n'est utilisée que pour la retenue sortante, alors que les bits de somme sont calculées avec propagation de retenue. La retenue sortante est donc calculée en parallèle de l'addition. Chaque bloc contient, à côté d'un additionneur proprement dit, un circuit qui calcule la retenue sortante. Il existe de nombreuses manières de calculer la retenue sortante.
Le gain en portes logiques est assez significatif. En effet, on se passe de circuits d'anticipation de retenue pour les 4 bits de somme, on ne garde que celui pour la retenue sortante. Le fait d'utiliser la propagation de retenue pour calculer les 4 bits de somme est censé avoir un léger cout en performance, mais il se trouve que ce cout est très faible. En effet, le résultat n'est connu qu'une fois les retenues propagées jusqu'au dernier bloc. Et malgré l'anticipation des retenues, la propagation est assez lente. les additionneurs à propagation de retenue auront terminé leur travail bien avant.
Un exemple est celui de l'additionneur CMOS 4008, un additionneur de 4 bit. Il est composé en trois sections. Une première couche de demi-additionneurs calcule les signaux P et G utilisés à la fois pour l'anticipation de la retenue sortante, que pour calculer les bits de somme. Le circuit de calcul de la retenue sortante utilise l'anticipation de retenue. Le reste du circuit fait l'addition en propageant les retenues.
[[File:CMOS 4008, circuit découpé en sections.png|centre|vignette|upright=1.5|CMOS 4008, circuit découpé en sections]]
==L'addition signée et la soustraction==
Après avoir vu l'addition, il est logique de passer à la soustraction, les deux opérations étant très proches. Si on sait câbler une addition entre entiers positifs, câbler une soustraction n'est pas très compliqué. De plus, la soustraction permet de faire des additions de nombres signés.
===Le soustracteur pour opérandes entiers===
Pour soustraire deux nombres entiers, on peut adapter l'algorithme de soustraction utilisé en décimal, celui que vous avez appris à l'école. Celui-ci ressemble fortement à l'algorithme d'addition : on soustrait les bits de même poids, et on propage éventuellement une retenue sur la colonne suivante. À la différence de l'addition, la retenue est soustraite, et non ajoutée. La table de soustraction nous dit quel est le résultat de la soustraction de deux bits. La voici :
* 0 - 0 = 0 ;
* 0 - 1 = 1 et une retenue ;
* 1 - 0 = 1 ;
* 1 - 1 = 0.
[[File:Algebra1 05 fig014.svg|centre|vignette|Soustraction en binaire, avec les retenues en rouge.]]
La table de soustraction peut servir de table de vérité pour construire un circuit qui soustrait deux bits. Celui-ci est appelé un '''demi-soustracteur'''. Il ressemble beaucoup à un demi-additionneur, les différences se résumant à une porte NON ajoutée pour le calcul de la retenue.
[[File:Half Subtractor Vektor.svg|centre|vignette|Demi-soustracteur.]]
Comme pour l'additionneur, deux demi-soustracteurs peuvent être combinés pour donner un '''soustracteur complet'''. Le calcul de la retenue se fait en combinant les deux retenues des demi-soustracteurs avec une porte OU. Les soustracteurs complets sont utilisés pour créer des soustracteurs à propagation de retenue ou tout autre circuit soustracteur, sur le même modèle que les additionneurs.
[[File:FullSubtractor.svg|centre|vignette|upright=2|Soustracteur complet.]]
Vous l'aurez sans doute remarqué, mais le demi-soustracteur ressemble beaucoup au demi-additionneur. La seule différence est une porte NON ajoutée au bon endroit dans le demi-soustracteur.
{|
|[[File:Half-adder.svg|vignette|Demi-additionneur]]
|[[File:Half Subtractor Vektor.svg|vignette|upright=1.3|Demi-soustracteur]]
|}
La conséquence est qu'il est possible de créer un circuit capable de faire à la fois des additions et des soustractions. L'idée est de remplacer la porte NON du demi-soustracteur par un inverseur commandable. Si on envoie un zéro sur l'entrée de commande, l'inverseur commandable se comportera comme une porte OUI. Le circuit sera alors identique à un demi-additionneur. Si on envoie un 1 sur l'entrée de commande, l'inverseur commandable se comportera comme une porte NON. le circuit sera alors un demi-soustracteur. Il faut faire cela pour chaque demi-soustracteur, ce qui donne ceci pour un soustracteur complet. le résultat est alors appelé un '''additionneur-soustracteur complet'''.
[[File:Additionneur-soustracteur complet.png|centre|vignette|upright=2|Additionneur-soustracteur complet]]
===L'additionneur-soustracteur pour opérandes codées en complément à deux===
Étudions le cas de la soustraction en complément à deux, dans l'objectif de créer un circuit soustracteur. Vous savez sûrement que a−b et a+(−b) sont deux expressions équivalentes. Et en complément à deux, − b = not(b) + 1. Dit autrement, a − b = a + not(b) + 1. On pourrait se dire qu'il faut deux additionneurs pour faire le calcul, mais la majorité des additionneurs possède une entrée de retenue pour incrémenter le résultat de l'addition. Un soustracteur en complément à deux est donc simplement composé d'un additionneur et d'un inverseur.
[[File:Additionneur soustracteur.png|centre|vignette|upright=2|Soustracteur en complément à deux.]]
Il est possible de créer un circuit capable d'effectuer soit une addition, soit une soustraction : il suffit de remplacer l'inverseur par un inverseur commandable, qui peut être désactivé. On a vu comment créer un tel inverseur commandable dans le chapitre sur les circuits combinatoires. On peut remarquer que l'entrée de retenue et l'entrée de commande de l'inverseur sont activées en même temps : on peut fusionner les deux signaux en un seul.
[[File:Additionneur-soustracteur en complément à deux.png|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux.]]
Une implémentation alternative est la suivante. Elle remplace l'inverseur commandable par un multiplexeur.
[[File:4-bit ripple carry adder-subtracter.svg|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux, version alternative.]]
===L'additionneur-soustracteur pour opérandes codées en signe-magnitude===
Passons maintenant aux nombres codés en signe-valeur absolue, les deux opérandes étant notées A et B. Suivant les signes des deux opérandes, on a quatre cas possibles : A + B, A − B (B négatif), −A + B (A négatif) et −A − B (A et B négatifs). Une astuce est que le circuit n'a besoin que de calculer A + B et A − B : il peut les inverser pour obtenir − A − B ou B − A. A + B et A − B peuvent se calculer avec un additionneur-soustracteur, reste à corriger le résultat. Il suffit de lui ajouter un inverseur commandable pour obtenir le circuit d'addition finale.
[[File:Additionneur en signe-valeur absolue.png|centre|vignette|upright=2|Additionneur en signe-valeur absolue.]]
Toute la difficulté tient dans le calcul du bit de signe du résultat, quand interviennent des soustractions. Autant l'addition de deux nombres de même signe (A + B et −A − B) ne pose aucun problème, autant les soustractions posent problème (A − B et −A + B). Suivant que <math>A<B</math> ou que <math>A>B</math>, le signe du résultat ne sera pas le même. Déterminer le signe du résultat se fait en regardant les bits de débordement d'entier, comme on le verra plus bas.
===L'additionneur-soustracteur pour opérandes codées en représentation par excès===
Passons maintenant aux nombres codés en représentation par excès. On pourrait croire que ces nombres s'additionnent comme des nombres non-signés, mais ce serait oublier la présence du biais, qui pose problème. Dans les cas de nombres signés gérés avec un biais, voyons ce que donne l'addition de deux nombres :
: <math>( n_1 + biais ) + ( n_2 + biais ) = n_1 + n_2 + 2 \times biais</math>
Or, le résultat correct serait :
: <math>n_1 + n_2 + biais</math>
En effectuant l'addition telle quelle, le biais est compté deux fois. On doit donc le soustraire après l'addition pour obtenir le résultat correct.
Même chose pour la soustraction qui donne ceci :
: <math>(n_1 + biais) - (n_2 + biais) = n_1 - n_2</math>
Or, le résultat correct serait :
: <math> ( n_1 - n_2 ) + biais </math>
Il faut rajouter le biais pour obtenir l'exposant correct.
On a donc besoin de deux additionneurs/soustracteurs : un pour additionner/soustraire les représentations binaires des opérandes, et un autre pour ajouter/retirer le biais en trop/manquant.
==L'additionneur BCD==
Maintenant, voyons un additionneur qui additionne deux entiers au format BCD. Pour cela, nous allons devoir passer par deux étapes. La première est de créer un circuit capable d'additionneur deux chiffres BCD. Ensuite, nous allons voir comment enchaîner ces circuits pour créer un additionneur BCD complet.
===L'additionneur BCD qui fait l'opération chiffre par chiffre===
Nous allons commencer par voir un additionneur qui additionne deux chiffres en BCD, une sorte d'équivalent BCD de l'additionneur complet. Il fournit un résultat sur 4 bits et une retenue qui est mise à 1 si le résultat dépasse 10 (la limite d'un chiffre BCD). Les deux opérandes sont des chiffres BCD codés sur 4 bits et sont additionnés en binaire par un additionneur des plus normaux, similaire à ceux vus plus haut. Le résultat est alors un entier codé en binaire, sur 5 bits, qu'on corrige/convertit pour obtenir un chiffre BCD et une retenue sortante.
Pour corriger le résultat, une idée intuitive serait de prendre le résultat et de faire une division par 10. Le quotient donne la retenue, alors que le reste est le résultat, le chiffre BCD. Mais un circuit diviseur par 10 utilise beaucoup de portes logiques, ce qui ne vaut pas le coup. Une autre méthode détecte si le résultat est égal ou supérieur à 10, ce qui correspond à un "débordement" (on dépasse les limites d'un chiffre BCD). Si le résultat est plus petit que 10, il n'y a rien à faire : le résultat est bon et la retenue est de zéro. Par contre, si le résultat vaut 10 ou plus, il faut corriger le résultat et générer une retenue à 1.
Il faut donc ajouter un circuit qui détecte si le résultat est supérieur à 9, qui calcule directement la retenue. Ce circuit peut se fabriquer simplement à partir de sa table de vérité, ou en utilisant les techniques que nous verrons dans un chapitre ultérieur sur les comparateurs. La solution la plus simple est clairement d'utiliser la table de vérité, ce qui est très simple, assez pour être laissé en exercice au lecteur. Pour comprendre comment corriger le résultat, établissons une table de vérité qui associe le résultat et le résultat corrigé. L'entrée vaut au minimum 10 et au maximum 9 + 9 = 18. On considère la sortie comme un tout, la retenue étant un 5ème bit, le bit de poids fort.
{|class="wikitable"
|-
! colspan="5" | Entrée
!
! rowspan="10" |
! Retenue
! Résultat corrigé (sans retenue)
! interprétation de la sortie en binaire (retenue inclue)
|-
| 0 || 1 || 0 || 1 || 0
| (10)
| 1 || 0000
| (16)
|-
| 0 || 1 || 0 || 1 || 1
| (11)
| 1 || 0001
| (17)
|-
| 0 || 1 || 1 || 0 || 0
| (12)
| 1 || 0010
| (18)
|-
| 0 || 1 || 1 || 0 || 1
| (13)
| 1 || 0011
| (19)
|-
| 0 || 1 || 1 || 1 || 0
| (14)
| 1 || 0100
| (20)
|-
| 0 || 1 || 1 || 1 || 1
| (15)
| 1 || 0101
| (21)
|-
| 1 || 0 || 0 || 0 || 0
| (16)
| 1 || 0110
| (22)
|-
| 1 || 0 || 0 || 0 || 1
| (17)
| 1 || 0111
| (23)
|-
| 1 || 0 || 0 || 1 || 0
| (18)
| 1 || 1000
| (24)
|}
En analysant le tableau, on voit que pour corriger le résultat, il suffit d'ajouter 6. La raison est que le résultat déborde d'un nibble à 16 en binaire, mais à 10 en décimal : il suffit d'ajouter la différence entre les deux, à savoir 6, et le débordement binaire fait son travail. Donc, la correction après une addition est très simple : si le résultat dépasse 9, on ajoute 6.
On peut maintenant implémenter l'additionneur BCD, en combinant le comparateur avec 10, le circuit de correction, et l'additionneur. La première solution calcule deux versions du résultat : la version corrigée, la version normale. Le choix entre les deux est réalisée par un multiplexeur, commandé par le comparateur.
[[File:Additionneur BCD.png|centre|vignette|upright=1.5|Additionneur BCD]]
L'autre solution utilise un circuit commandable qui soit additionne 6, soit ne fait rien. Le choix entre les deux est commandé par le bit calculé par le comparateur.
[[File:Additionneur BCD, seconde version.png|centre|vignette|upright=1.5|Additionneur BCD, seconde version.]]
Une version alternative du circuit précédent est la suivante. Il contient deux additionneurs : un pour additionner les deux chiffres BCD, un autre pour additionner 6 si besoin. Le résultat du comparateur est directement utilisé pour générer l'opérande du second additionneur : 0 ou 6. Le circuit est simple à concevoir, mais gaspille beaucoup de circuit. Idéalement, il vaudrait mieux utiliser un circuit combinatoire d'addition avec une constante.
[[File:Adder 4 Bit BCD.svg|centre|vignette|upright=2|Additionneur BCD, circuit complet.]]
Pour obtenir un additionneur BCD complet, il suffit d’enchaîner les additionneurs précédents, comme on le ferait avec les additionneurs complets dans un additionneur à propagation de retenue. Au final, l'additionneur BCD est beaucoup plus compliqué qu'un additionneur normal, car il rajoute un comparateur ">9", un petit additionneur pour ajouter 6 et éventuellement d'autres circuits. De plus, il est difficile d'appliquer les optimisations disponibles sur les additionneurs non-BCD. Notamment, les circuits d'anticipation de retenue sont totalement à refaire et le résultat est relativement compliqué. C'est ce qui explique pourquoi le BCD a progressivement été abandonné au profit du binaire simple.
La soustraction en BCD se fait comme en binaire : le nombre à soustraire est remplacé par son complément, le circuit additionne le complément et l'autre opérande, le débordement d'entier fait que le résultat marche. Sauf qu'ici, le complément est un complément à 9. Il se calcule chiffre par chiffre : chaque chiffre est remplacé par (9 - le chiffre en question).
===L'additionneur BCD par ajustement décimal===
L'additionneur BCD précédent effectuait son travail chiffre BCD par chiffre BCD, mais il existe des additionneurs BCD qui font autrement. Sur les premiers processeurs x86, il n'y avait pas d'opération d'addition BCD proprement dit, seulement une addition binaire normale de 8, 16 ou 32 bits. Par contre, elle était secondée par une opération dite d''''ajustement décimal''' qui transformait un nombre binaire en nombre codé en BCD. L'opération d'ajustement décimal prenait un opérande de 8 bits codé en binaire et fournissait un résultat de la même taille, c'est à dire deux chiffres BCD. Effectuer une addition BCD demandait donc de faire deux opérations à la suite : une addition binaire simple, suivie par l'opération d'ajustement décimal. Cela permettait de gérer des nombres entiers en binaire usuel et des entiers BCD sans avoir deux instructions d'addition séparées pour les deux, sans compter que cela simplifiait aussi les circuits d'addition.
L'ajustement décimal s'effectue en ajoutant une constante bien précise à l'opérande à convertir en BCD. L'idée est que la constante est découpée en morceaux de 4 bits, correspondant chacun à un chiffre BCD de l'opérande, chaque morceau contenant soit un 0, soit 6. Cela permet d'ajouter soit 0, soit 6, à chaque chiffre BCD, et donc de le corriger. La propagation des retenues d'un chiffre à l'autre est effectuée automatiquement par l'addition binaire de la constante. L'opération d'ajustement décimal calcule automatiquement la constante. Elle découpe l'opérande en ''nibbles'', vérifie si chaque ''nibble'' est supérieur ou égal à 10, puis détermine la valeur de chaque ''nibble'' de la constante finale. Par exemple, si je prends l'opérande 1001 1110, le ''nibble'' de poids faible déborde, alors que celui de poids fort non. La constante sera donc 0000 0110 : 0x06. Inversement, si le ''nibble'' de poids fort déborde et pas celui de poids faible, la constante sera alors 0x60. Et la constante est de 0x66 si les deux ''nibbles'' débordent, de 0x00 si aucun ne déborde.
Le circuit d’ajustement décimal est donc composé de trois étapes : deux étapes pour calculer la constante, et un circuit d'addition pour additionner cette constante au nombre de départ. La première étape découpe l'opérande en morceaux de 4 bits, en chiffres BCD, et vérifie si chacun d'entre eux vaut 10 ou plus. La seconde étape prend les résultats de la première étape, et les combine pour calculer la constante. Enfin, on trouve l'addition finale, qui était réalisée par un circuit d'addition utilisé à la fois pour l'ajustement décimal et l'addition binaire. La différence entre une addition normale et une opération d'ajustement décimal tient dans le fait que les deux premières étapes sont désactivées dans une addition normale.
[[File:Additionneur BCD parallèle.png|centre|vignette|upright=1.5|Additionneur BCD parallèle]]
===L'additionneur biquinaire===
Les entiers BCD ne sont qu'un des encodages hybrides entre décimal et binaire. L'encodage biquinaire est l'un d'entre eux et nous allons faire un rappel rapide à ce sujet. Pour simplifier, un chiffre encodé en biquinaire est composé de deux parties : un bit, couplé à une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits (0, 1, 2, 3 et 4). Le bit indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9.
Additionner deux nombres de biquinaire demande donc d'additionner deux parties quinaires encodées en ''one-hot'' et d'additionner deux bits. Mais attention : il faut tenir compte de la retenue de l'addition des parties quinaires. Et idéalement, il faut aussi tenir compte d'une retenue entrante, provenant de l'addition de la colonne de chiffres précédente. Toute la difficulté vient de la création de l'additionneur ''one-hot''. Heureusement, vu qu'il n'y a que 4-5 bits à additionner, il est souvent fabriqué à partir de sa table de vérité.
[[File:Additionneur bi-quinaire.png|centre|vignette|upright=2|Additionneur bi-quinaire]]
Un avantage du biquinaire est que le calcul du complément à 9 est très simple. Il faut pour cela : inverser la partie binaire avec une porte NON, puis inverser l'ordre des bits de la partie quinaire. Concrètement, le bit de poids faible devient le bit de poids fort, et ainsi de suite. Par exemple, une partie quinaire 01000 devient 00010, 10000 devient 00001, 00100 ne change pas, etc. Le tout peut se calculer avec une porte NON et 5 multiplexeurs.
===L'additionneur BCD avec calculs intermédiaires en biquinaire===
L'ordinateur IBM 1401, un ancien ''mainframe'' des années 60, utilisait un additionneur BCD un peu particulier. Les nombres étaient encodés en BCD dans la mémoire de l'ordinateur, mais les circuits de calcul utilisaient la représentation biquinaire. Lors d'un calcul, le processeur de l'ordinateur traduisait les chiffres BCD en représentation biquinaire, faisait une addition en biquinaire, avant de traduire le résultat en BCD normal.
Pour être précis, l'IBM 1401 utilisait une variante du biquinaire. L'encodage biquinaire de l'IBM 1401 est le suivant : la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre pair encodé par la partie quinaire. Si l'IBM 1401 utilisait cette variante du biquinaire, c'est car elle donnait des circuits de conversion BCD-biquinaire plus économes en portes logiques et plus rapides.
La partie binaire est le bit de poids faible du chiffre BCD, la partie biquinaire est calculée par un simple décodeur qui prend en entrée le chiffre BCD, amputé de son bit de poids faible. La traduction inverse demande d'utiliser un encodeur, à la place du décodeur. Par contre, le circuit d'addition biquinaire était plus compliqué du fait de la gestion des retenues. L'addition des parties binaires et quinaires se faisait en parallèle, dans deux additionneurs séparés. Cependant, l'addition des parties binaire fournit une retenue, qu'il faut prendre en compte. Pour cela, l'IBM 1401 disposait d'un troisième additionneur qui fournissait le résultat final, encodé en biquinaire.
[[File:Additionneur bi-quinaire de l'IBM 1401.png|centre|vignette|upright=2|Additionneur biquinaire de l'IBM 1401]]
Une implémentation moderne demanderait d'utiliser des portes ET combinées à des portes OU, le circuit pouvant être construit simplement à partir de sa table de vérité. Sur l'IBM 1401, le circuit était cependant différent, en raison de l'utilisation de OU câblés, des croisements de fils qui fonctionnent comme des portes OU, que nous n'avons pas encore vu pour le moment, mais qui seront détaillés dans quelques chapitres. Les OU câblés étaient utilisés pour simplifier le design du circuit, mais demandaient des portes logiques spécifiques, ce qui collait avec le fait que ce ''mainframe'' utilisait des transistors en Germanium. L'implémentation exacte est décrite dans cet article de blog, mais je ne recommande sa lecture qu'à ceux qui savent ce qu'est un OU câblé :
* [https://www.righto.com/2015/10/qui-binary-arithmetic-how-1960s-ibm.html Qui-binary arithmetic: how a 1960s IBM mainframe does math].
==Les débordements d'entier lors d'une addition/soustraction==
Les instructions arithmétiques manipulent des entiers codés sur un nombre fixe de bits, qui ne peuvent prendre leurs valeurs que dans un intervalle. Pour les nombres positifs, un ordinateur qui code ses entiers sur n bits pourra coder tous les entiers allant de 0 à <math>2^n -1</math>. Pour les nombres négatifs, l'intervalle est différent et dépend de la représentation utilisée. Dans le cas général, l'ordinateur peut coder les valeurs comprises de <math>N_\text{min}</math> à <math>N_\text{max}</math>. Si le résultat d'un calcul sort de cet intervalle, il ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math> , alors que la valeur basse vaut <math>N_\text{min} - 1</math> (avec <math>N_\text{max}</math> et <math>N_\text{min}</math> respectivement la plus grande et la plus petite valeur codable par l'ordinateur).
===La correction des débordements d'entier : l'arithmétique saturée===
Quand un débordement d'entier survient, tous les circuits de calcul ne procèdent pas de la même manière. Dans les grandes lignes, il y a deux réactions possibles : soit on corrige automatiquement le résultat du débordement, soit on ne fait rien et on se contente de détecter le débordement.
Si le débordement n'est pas corrigé automatiquement, le circuit ne conserve que les bits de poids faibles du résultat. Les bits en trop sont simplement ignorés. On dit qu'on utilise l''''arithmétique modulaire'''. Le problème avec ce genre d'arithmétique, c'est qu'une opération entre deux grands nombres peut donner un résultat très petit. Par exemple, si je dispose de registres 4 bits et que je souhaite faire l'addition 1111 + 0010 (ce qui donne 15 + 2), le résultat est censé être 10001 (17), ce qui est un résultat plus grand que la taille d'un registre. En conservant les 4 bits de poids faible, j’obtiens 0001 (1). En clair, un résultat très grand est transformé en un résultat très petit. Cela peut poser problèmes si on travaille uniquement avec des nombres positifs, mais c'est aussi utilisé pour coder des nombres en complément à deux.
D'autres circuits utilisent ce qu'on appelle l''''arithmétique saturée''' : si un calcul génère un débordement, on arrondi le résultat au plus grand entier supporté par le circuit. Les circuits capables de calculer en arithmétique saturée sont un peu plus complexes, vu qu'il faut rajouter des circuits pour corriger le résultat en cas de débordement. Il suffit généralement de rajouter un ''circuit de saturation'', qui prend en entrée le résultat et le corrige en cas de débordement. Ce circuit de saturation met la valeur maximale en sortie si un débordement survient, mais se contente de recopier le résultat du calcul sur sa sortie s'il n'y a pas de débordement. Typiquement, il est composé d'une couche de multiplexeurs, qui sélectionnent quelle valeur mettre sur la sortie : soit le résultat du calcul, soit le plus grand nombre entier géré par le processeur, soit le plus petit (pour les nombres négatifs/soustractions).
L'arithmétique saturée est utilisée pour les additions et soustractions, mais c'est plus rare pour les multiplications/divisions. Une des raisons est que le résultat d'une addition/soustraction prend un bit de plus que le résultat, là où les multiplications doublent le nombre de bits. Quand une addition déborde, le résultat réel est proche de la valeur maximale codable. mais quand une multiplication déborde, le résultat peut parfois valoir 200 à 60000 fois plus que la valeur maximale codable. Les calculs avec une valeur saturée/corrigée sont donc crédibles pour une suite d'additions, mais pas pour une suite de multiplications.
===La détection des débordements entiers===
Quand un débordement d'entier a eu lieu, il vaut mieux que l'additionneur prévienne ! Pour cela, l'additionneur a une '''sortie de débordement''', parfois nommée ''Overflow'', dont la valeur indique si l'addition a généré un débordement d'entier ou non. Reste que détecter un débordement ne se fait pas de la même manière selon que l'on parle d'un additionneur non-signé ou signé.
Pour les additionneur non-signés, l'additionneur calcule un bit de plus que ce qui est supporté par l'ordinateur. Par exemple, un additionneur 32 bits fournit un résultat sur 33 bits, un débordement d'entier a lieu quand le 33ème bit est à 1. Précisément, la sortie de débordement n'est autre que la retenue finale, celle fournie par le dernier additionneur complet. Le seul type de débordement possible est un débordement par le haut, où le résultat dépasse la valeur maximale. Avec l'arithmétique saturée, le circuit de saturation consiste en une seule couche de multiplexeurs, voire en un circuit de mise à la valeur maximale tel que vu dans le chapitre sur les opérations bits à bits.
[[File:Gestion des débordements d'entiers.png|centre|vignette|upright=2.5|Gestion des débordements d'entiers lors d'une addition non-signée.]]
Pour les additionneurs non-signés, la gestion des débordements d'entiers dépend fortement de la représentation signée. Nous allons étudier le cas du complément à deux. Si vous vous rappelez le chapitre 1, les calculs sur des nombres en complètement à deux utilisent les règles de l'arithmétique modulaire, c'est une condition nécessaire. À priori, on peut penser que dans ces conditions, les débordements d'entiers sont une chose parfaitement normale, qui nous permet d'avoir des résultats corrects. Néanmoins, certains débordements d'entiers peuvent survenir malgré tout et produire des bugs assez ennuyeux.
Si l'on tient en compte les règles du complément à deux, on sait que le bit de poids fort (le plus à gauche) permet de déterminer si le nombre est positif ou négatif : il indique le signe du nombre. Tout se passe comme si les entiers en complément à deux étaient codés sur un bit de moins, et avaient leur longueur amputé du bit de poids fort. Si le résultat d'un calcul écrase le bit de poids fort, il y a un débordement d'entiers. Il existe une règle simple qui permet de détecter ces débordements d'entiers. L'addition de deux nombres positifs ne peut pas être un nombre négatif. Si on additionne deux nombres dont le bit de signe est à 0 et que le bit de signe du résultat est à 1, on est en face d'un débordement d'entiers. Même chose pour deux nombres négatifs : le résultat de l'addition ne peut pas être positif. On peut résumer cela en une phrase : si deux nombres de même signe sont ajoutés, un débordement a lieu quand le bit du signe du résultat a le signe opposé.
Modifier les circuits d'au-dessus pour qu'ils détectent les débordements en complément à deux est simple comme bonjour : il suffit créer un petit circuit combinatoire qui prenne en entrée les bits de signe des opérandes et du résultat, et qui fasse le calcul de l'indicateur de débordements. Si l'on rédige sa table de vérité, on doit se retrouver avec la table suivante :
{|class="wikitable"
|-
!Entrées
!Sortie
|-
|000||0
|-
|001||1
|-
|010||0
|-
|011||0
|-
|100||0
|-
|101||0
|-
|110||1
|-
|111||0
|}
L'équation de ce circuit est la suivante, avec <math>S_a</math> et <math>S_b</math> les signes des deux opérandes, et <math>C_i</math> la retenue de la colonne précédente :
: <math> ( S_a . S_b . \overline{C_i} ) + ( \overline{S_a} . \overline{S_b} . C_i )</math>
En simplifiant, on obtient alors :
: <math>( S_a . S_b ) \oplus C_i</math>
Or, il se trouve que <math>S_a . S_b</math> est tout simplement la retenue en sortie du dernier additionneur, que nous noterons <math>C_o</math>. On trouve donc :
: <math>C_o \oplus C_i</math>
Il suffit donc de faire un XOR entre la dernière retenue et la précédente pour obtenir le bit de débordement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de décalage et de rotation
| prevText=Les circuits de décalage et de rotation
| next=Les circuits de comparaison
| nextText=Les circuits de comparaison
}}
</noinclude>
53f181ewoqc31u4a8j0vtnrholfm5nb
768658
768657
2026-06-25T19:44:17Z
Mewtow
31375
/* Les additionneurs à anticipation de retenue multi-niveau */
768658
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir les circuits capables de faire une addition ou une soustraction, ainsi que quelques circuits spécialisés. Précisons cependant que les fabricants de processeurs travaillent d'arrache-pied pour trouver des moyens de rendre ces circuits de calcul plus rapides et plus économes en énergie. Autant vous dire que les circuits que vous allez voir sont vraiment des circuits qui font pâle figure comparé à ce que l'on peut trouver dans un vrai processeur commercial !
==Les circuits pour additionner 2 ou 3 bits==
L'addition se fait en binaire de la même manière qu'en décimal. On additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. La soustraction fonctionne sur le même principe, sur le même modèle qu'en décimal.
[[File:Binary Addition Demonstration.svg|centre|vignette|Exemple d'addition en binaire.]]
En clair, additionner deux nombres demande d'additionner 2 bits et une retenue sur chaque colonne, et de propager les retenues d'une colonne à l'autre. La propagation des retenues est quelque chose de simple en apparence, mais qui est sujet à des optimisations extraordinairement nombreuses. Aussi, pour simplifier l'exposition, nous allons voir comment gérer une colonne avant de voir comment sont propagées les retenues.
[[File:Full Adder Block.svg|vignette|upright=1|class=transparent|Additionneur complet.]]
Si on effectue une addition en colonne, on doit additionner les deux bits sur la colonne, mais aussi additionner une éventuelle retenue. Il faut donc créer un circuit qui additionne trois bits : deux bits de données, plus une retenue. Ce circuit qui additionne trois bits est appelé un '''additionneur complet'''. Il fournit en sortie deux bits : un bit de somme et une retenue sortante. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|-
||0||0||0|| ||0||0
|-
||0||0||1|| ||0||1
|-
||0||1||0|| ||0||1
|-
||0||1||1|| ||1||0
|-
||1||0||0|| ||0||1
|-
||1||0||1|| ||1||0
|-
||1||1||0|| ||1||0
|-
||1||1||1|| ||1||1
|}
Il est possible d'utiliser un tableau de Karnaugh pour traduire la table de vérité, mais elle donne un résultat assez compliqué. La retenue et le bit de somme sont calculés à part. La retenue est calculée avec trois portes ET et une porte OU. Le bit de somme est calculé lui avec 4 portes ET, une porte OU, et trois portes NON.
[[File:Full Adler with 3 entries.png|centre|vignette|upright=2|Additionneur complet, fabriqué à partir de la table de vérité.]]
D'autres méthodes donnent des résultats plus compréhensibles. Nous allons les voir dans la suite de cette section.
===L'additionneur complet basé sur des demi-additionneurs===
[[File:1-bit half-adder.svg|class=transparent|vignette|Demi-addtionneur.]]
Nous avons déjà vu comment additionner deux bits dans le chapitre sur les incrémenteurs, mais quelques rappels ne seront pas de trop. Le '''demi-additionneur''' est un circuit qui additionne deux bits. Il implémente la table d'addition, qui est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Mais un demi-additionneur n'additionne que deux bits, il manque de quoi additionner la retenue. Une solution pour cela est d'enchaîner deux demi-additionneurs : un qui additionne les deux bits de données, et un second qui additionne la retenue au résultat du précédent. La retenue finale se calcule en combinant les sorties de retenue des deux demi-additionneurs, avec une porte OU. Le résultat est un additionneur complet fabriqué avec deux demi-additionneurs. Pour vous en convaincre, établissez la table de vérité de ce circuit, vous verrez que ça marche.
[[File:Full Adder Blocks.svg|centre|vignette|upright=2|Additionneur complet fabriqué avec deux demi-additionneurs.]]
[[File:Half-adder.svg|vignette|upright=1|Circuit d'un demi-additionneur naïf.]]
L'implémentation naïve d'un demi-additionneur utilise une porte XOR et une porte ET, comme illustré ci-contre. Le circuit s'obtient en faisant uen traduction littérale de la table de vérité du circuit. Si je dis qu'elle est naïve, c'est car il est possible de l'optimiser, de manière à éliminer des redondances cachées. Mais nous avions déjà vu cela dans le chapitre sur les incrémenteurs, aussi laissons cela pour plus tard.
En combinant deux demi-additionneurs de ce type, on obtient l'additionneur complet suivant. Les deux sorties de retenue des demi-additionneurs sont combinées avec une porte OU, pour calculer la retenue finale.
{| class="flexible"
|[[File:Full Adder Modules.svg|class=transparent|Composition d'un additionneur complet. On voit bien que celui-ci est composé de deux demi-additionneurs, en rouge et en bleu, auxquels on a ajouté une porte OU pour calculer la retenue finale.]]
|[[File:Full-adder.svg|300px|class=transparent|Circuit d'un additionneur complet.]]
|}
Le fait de combiner les deux retenues avec une porte OU n'est pas ce qu'il y a de plus intuitif. La table de vérité nous dit que ça fonctionne, mais on comprend mal pourquoi. Mais tout devient plus clair quand on sait que les deux retenues ne peuvent pas être à 1 en même temps. Même dans l'addition 1 + 1 + 1, seule une retenue est à 1. Soit l'addition des deux bits d'opérande donne naissance à une retenue, soit c'est l'addition ''résultat + retenue entrante''. La retenue sortant est donc à 1 quand une des deux addition donne une retenue. D'où l'usage d'une porte OU. Remarquez qu'une porte XOR donne le même résultat, vu que le cas où les deux retenues sont à 1 n'est jamais rencontré.
===La propagation et la génération des retenues===
L'additionneur complet que nous allons voir dans cette section sert à introduire les concepts de propagation, génération et absorption de retenue. Il s'agit de concepts très importants quand on étudie les additionneurs. l'idée part d'un principe très simple : la retenue sortante dépend de la retenue d'entrée. La relation entre les deux se résume à trois cas, qui dépendent de la valeur des deux bits additionnés, nommés A et B.
* Dans le premier cas, la retenue entrante est égale à la retenue sortante. On dit que la retenue entrante est propagée par l'additionneur.
* Dans le second cas, la retenue est forcée à 0 : la retenue sortante vaut 0, peu importe la valeur de la retenue entrante.
* Dans le troisième cas, la retenue est forcée à 1 : la retenue entrante vaut 1, peu importe la valeur de la retenue entrante.
[[File:Signaux P et G fournis par un demi-additionneur.png|vignette|Signaux P et G fournis par un demi-additionneur]]
Maintenant, créons un circuit qui nous dise si une retenue est propagée ou générée. Il a deux bits de sortie, nommés P et G : P pour ''Propagate'', G pour ''Generate''. Le bit P est à 1 si la retenue entrante est propagée, il est à 0 sinon. Le bit G est à 1 si une retenue est générée, à 0 sinon. Une retenue est considérée comme absorbée si elle n'est pas ni propagée ni générée, pas besoin d'un troisième bit pour gérer ce cas.
Il se trouve que ce circuit n'est autre qu'un demi-additionneur ! Pour vous en rendre compte, regardez la table de vérité d'un additionneur complet, illustrée ci-dessous. Lorsque les deux bits d'opérande sont à 0, la retenue sortante vaut toujours 0. Si ils sont tous deux à 1, alors la retenue sortante vaut 1. S'ils sont différents, alors retenues sortante et entrante sont égales. Le bit P est donc généré par une simple porte XOR. Quant au bit G, il est à 1 si les deux bits d'opérandes sont à 1, ce qui correspond à une porte ET. Il se trouve que ces deux portes forment un demi-additionneur !
{|class="wikitable"
|-
! Opérande 1 !! Opérande 2 !! Retenue entrante !! !! Retenue sortante
|- class="f_rouge"
||0||0||0|| ||0
|- class="f_rouge"
||0||0||1|| ||0
|- class="f_bleu"
||0||1||0|| ||0
|- class="f_bleu"
||0||1||1|| ||1
|- class="f_bleu"
||1||0||0|| ||0
|- class="f_bleu"
||1||0||1|| ||1
|- class="f_vert"
||1||1||0|| ||1
|- class="f_vert"
||1||1||1|| ||1
|}
Ensuite, créons un circuit qui prend ces deux sorties P et G, et calcule la retenue sortante en fonction. Le circuit en question a trois entrées : la retenue entrante, les deux bits P et G. La retenue sortante vaut 1 soit si une retenue est générée, soit si la retenue entrante est propagée et qu'elle vaut 1. En notant <math>{C_{in}}</math> et <math>{C_{out}}</math> les retenues entrantes et sortante, on a :
: <math>{C_{out}} = G + \left( P . C_{in} \right)</math>
Le circuit est donc composé d'une porte OU et d'une porte ET. En combinant un demi-additionneur avec le circuit de calcul de retenue sortante vu plus haut, on a :
[[File:Additionneur complet avec propagation et génération de retenue.png|class=transparent|centre|vignette|upright=2|Additionneur complet avec propagation et génération de retenue.]]
Certes, on retombe sur le circuit vu plus haut. Mais il est possible de remplacer le circuit en orange par un autre. Par exemple, il est possible de le remplacer par multiplexeur, qui choisit entre la retenue générée et la retenue entrante (propagée). Le choix se fait selon la valeur du bit P, qui chosiit entre propager la retenue et la générer.
[[File:Additionneur crée avec un multiplexeur.png|centre|vignette|upright=1.5|Additionneur crée avec un multiplexeur]]
Le circuit exact est illustré ci-dessous. Il semble utiliser plus de portes logiques que l'additionneur complet précédent. Cependant, nous verrons dans quelques chapitres qu'il est possible d'implémenter un multiplexeur avec seulement 6 transistors, voire moins ! L'implémentation utilise des portes à transmission, mais nous en reparlerons dans le chapitre sur les transistors, quand nous verrons les additionneurs à ''Manchester Carry Chain''. Au passage, une variante de ce circuit a été utilisée dans le processeur processeur 8086 d'Intel, comme on le verra dans le chapitre suivant.
[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
[[File:FulladderPG.png|vignette|upright=1|Additionneur complet avec deux sorties P et G.]]
Pour finir, sachez qu'il existe des additionneurs qui fournissent : le bit de résultat, deux sorties P et G qui indiquent si l'addition propage ou génère une retenue. Il ne s'agit pas d'additionneurs complets, car il manque de quoi calculer la retenue sortante. De tels additionneurs seront appelés des '''additionneurs partiels'''. De tels additionneurs sont utilisés dans certains additionneurs pour gagner en performance.
Enfin, il existe aussi ce que j'ai décidé d'appeler des '''additionneurs P/G''', qui sont des additionneurs complets auxquels on a ajouté deux sorties P et G, en plus de la sortie de retenue. Leur circuit est le même que celui d'un additionneur complet, auquel on a ajouté deux fils. Vu que ces deux sorties sont fournies par le premier demi-additionneur, ajouter ces deux sorties demande d'ajouter des fils, pas de portes logiques. De tels additionneurs sont aussi utilisés dans certains additionneurs pour gagner en performance.
===L'additionneur complet basé sur une porte à majorité===
Maintenant, voyons une dernière implémentation possible de l'additionneur complet. Mais avant de voir comment l'additionneur est implémenté, nous allons devoir faire un petit focus sur la retenue sortante. Reprenons la table de vérité, et regardons ce qu'il en est pour la retenue sortante uniquement.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante
|-
||0||0||0|| ||0
|-
||0||0||1|| ||0
|-
||0||1||0|| ||0
|-
||0||1||1|| ||1
|-
||1||0||0|| ||0
|-
||1||0||1|| ||1
|-
||1||1||0|| ||1
|-
||1||1||1|| ||1
|}
Vous remarquerez que la retenue sortante est égale au bit majoritaire, parmi les trois bits d'opérande. Si deux ou trois bits sont à 1, la retenue sortante vaut 1. Et réciproquement avec 0. Le circuit de calcul de la retenue peut donc être remplacé par une '''porte à majorité'''.
[[File:Additionneur crée avec une porte à majorité.png|centre|vignette|upright=2|Additionneur crée avec une porte à majorité]]
Mais une porte à majorité est assez gourmande en circuit, ce qui fait qu'un additionneur pareil ne serait pas pratique, ni utile. Du moins, ce serait le cas s'il n'y avait pas une possibilité d'optimisation extrêmement intéressante. Il est possible de calculer le 'bit de somme' à partir de la retenue sortante ! En effet, le bit de somme est l'inverse de la retenue sortante, sauf dans deux cas : les trois bits d'entrée sont à 0, où ils sont tous à 1. Le bit de somme vaut 0 dans le premier cas, 1 dans le second cas.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|- class="f_bleu"
||0||0||0|| ||0||0
|- class="f_vert"
||0||0||1|| ||0||1
|- class="f_vert"
||0||1||0|| ||0||1
|- class="f_vert"
||0||1||1|| ||1||0
|- class="f_vert"
||1||0||0|| ||0||1
|- class="f_vert"
||1||0||1|| ||1||0
|- class="f_vert"
||1||1||0|| ||1||0
|- class="f_rouge"
||1||1||1|| ||1||1
|}
En clair, 6 lignes de la table de vérité sur 8 peuvent se calculer avec une porte à majorité, le reste demandant quelques portes logiques pour faire la correction. L'implémentation en circuit demande donc trois choses :
* de calculer la retenue sortante et de quoi l'inverser ;
* un circuit qui vérifie si tous les bits opérande valent 0 : une porte NOR ou OU fait l'affaire ;
* un circuit qui vérifie s'ils valent tous 1, à savoir une porte ET.
Ensuite, on combine le résultat des trois circuits précédents pour obtenir le résultat final. La combinaison est le fait du circuit en jaune dans le schéma ci-dessous.
[[File:Full adder basé sur une modification de la retenue.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue]]
Le circuit en jaune est assez simple. Quand la porte ET sort un 1, la sortie doit être forcée à 1. Par contre, quand la porte OU sort un zéro, la sortie doit être forcée à 0. Il suffit donc d'enchainer un circuit de mise à 1 et un circuit de mise à 0. Nous avons vu dans le chapitre sur les opérations de masquage qu'il s'agit respectivement d'une porte OU et d'une porte ET.
[[File:Full adder basé sur une modification de la retenue interne, au complet.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue interne, au complet]]
Divers processeurs utilisaient des additionneurs complets de ce type : le fameux Z80, l'Intel 4004 et de l'Intel 8008 sont les exemples les plus notables. Pourtant, on pourrait s'étonner qu'un tel circuit ait existé. Il utilise beaucoup de portes logiques, a une profondeur logique supérieure : il n'a rien d'avantageux. Sauf qu'il était utilisé sur d'anciens processeurs, qui utilisaient la technologie dite TTL, différente de la technologie CMOS des transistors modernes. Et avec la technologie TTL, il est possible de fusionner plusieurs portes logiques ET et NOR en une seule porte logique ET/OU/NON ! Un additionneur complet construit ainsi ne prenait que deux portes logiques : une pour le calcul de la retenue sortante, une autre pour le reste du circuit.
==L'addition non signée==
Voyons maintenant un circuit capable d'additionner deux nombres entiers: l''''additionneur'''. Dans la version qu'on va voir, ce circuit manipulera des nombres strictement positifs. L'addition des nombres codés en complètement à deux sera vu dans une section ultérieure.
===L'additionneur série===
Il est possible d'additionner deux nombres bit par bit, gr$ace à un additionneur complet associé à plusieurs registres à décalages. Les opérandes sont placées chacune dans un registre à décalage, et l'additionneur complet utilise les bits sortants de ces registres à décalage. Le bit du résultat est envoyé au registre à décalage pour le résultat. La retenue de l'addition est stockée dans une bascule de 1 bit, en attente du prochain cycle d'horloge. Un tel additionneur est appelé un '''additionneur série'''. Il a été utilisé sur d'anciens ordinateurs dans les années 50-60, aussi bien des prototypes que des ordinateurs commerciaux.
[[File:Additionneur série.jpg|centre|vignette|upright=2.5|Additionneur série.]]
===L'additionneur à propagation de retenue===
L''''additionneur à propagation de retenue''' est aussi appelé l'additionneur ''ripple carry'', c'est son nom anglais. J'utiliserais occasionnellement le nom anglais dans ce qui suit, car celui-ci est plus court. Ilutilise un additionneur complet pour chaque colonne de l'addition. Pour gérer les retenues, il suffit ainsi de câbler des additionneurs complets les uns à la suite des autres, chacun envoyant sa retenue à la colonne suivante.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Notons la présence de la retenue sortante, qui est utilisée pour détecter les débordements d'entier, ainsi que pour d'autres opérations. Le bit de retenue final est souvent stocké dans un registre spécial du processeur (généralement appelé ''carry flag'').
Notez aussi, à droite du schéma précédent, l'entrée de retenue <math>\text{R}_\text{Entrée}</math>. Elle est très utile pour l'implémentation de certaines opérations comme l'inversion de signe, la soustraction, l'incrémentation, etc. Certains processeurs sont capables de faire une opération appelée ADC, ADDC ou autre nom signifiant ''Addition with Carry'', qui permet de faire le calcul A + B + Retenue (la retenue en question est la retenue sortante de l'addition précédente, stockée dans le registre ''carry flag''). Son utilité principale est de permettre des additions d'entiers plus grands que ceux supportés par le processeur. Par exemple, cela permet de faire des additions d'entiers 32 bits sur un processeur 16 bits.
[[File:Adder Network 3 Bit Sum Delay 003.svg|vignette|upright=0.5|Propagation de retenue dans l'additionneur.]]
Cet additionneur utilise très peu de portes logiques, ce qui fait qu'il était utilisé sur certains processeurs 8 et 16 bits, dont le budget en portes logiques était limité. Bien que très simple, cet additionneur est cependant peu performant. Son temps de calcul est proportionnel à la taille des opérandes. Par exemple, additionner deux opérandes de 32 bits prendra deux fois plus de temps que pour des opérandes de 16 bits. La raison est que les retenues doivent se propager à travers le circuit, du premier additionneur jusqu'au dernier.
L'addition étant une opération fréquente, d'autres additionneurs ont été inventés. Ils utilisent des optimisations qui utilisent plus de circuits pour gagner en rapidité. Mais avant de voir ces optimisations, nous allons voir s'il est possible d'optimiser les additionneurs à propagation de retenue.
===L'additionneur à saut de retenue===
L'additionneur à propagation de retenue peut être optimisé de deux manières. La première accélère la propagation de la retenue, en agissant au niveau des additionneurs complets. La ''Manchester carry chain'' est une optimisation de ce type, mais on ne peut pas encore expliquer à ce stade du cours, car elle optimisait les additionneurs complets au niveau des transistors eux-mêmes. Elle a été utilisée sur de nombreux processeurs connus, comme le 8086 d'Intel ou le Z80.
La seconde manière rend l'addition plus rapide dans certains cas. Le résultat est un additionneur dont le temps de calcul est variable. Le calcul prendra quelques cycles d'horloges avec certains opérandes, mais d'autres opérandes ne verront aucune amélioration. Il n'améliore pas le pire des cas, dans lequel la retenue doit être propagée du début à la fin, du bit de poids faible au bit de poids fort. Mais dans d'autres cas, il permet d'avoir le résultat en avance. L'optimisation en question donne l''''additionneur à saut de retenue''' (''carry-skip adder'').
Il est composé de blocs qui additionnent 4/5 bits, rarement plus. La taille d'un bloc varie d'un additionneur à l'autre, elle peut même varier dans l'additionneur, mais nous allons prendre un exemple où les blocs font 4 bits. L'idée est de détecter précocement si la retenue entrante est propagée à travers tout le bloc. Si la retenue est propagée dans tous le bloc, on peut directement l'envoyer sur la sortie de retenue, la retenue saute le bloc entier. Dans le cas contraire, on doit calculer la retenue normalement. Le choix entre les deux est le fait d'un multiplexeur. Toute la difficulté est de savoir comment commander le multiplexeur.
[[File:Carry skip adder, principe de base.png|centre|vignette|upright=2.5|Carry skip adder : principe de base]]
Pour savoir si une retenue est propagée dans un bloc entier, il faut utiliser des additionneurs complets, qui ont en plus une sortie P indiquant que l'additionneur propage la retenue. Le bloc propage une retenue si chaque additionneur complet propage la retenue, donc s'ils ont tous leur sortie P à 1. Un simple ET logique suffit à déterminer si c'est le cas. Le signal de commande du multiplexeur est donc un vulgaire ET entre toutes les sorties P des additionneurs complets du bloc.
[[File:Additionneur à saut de retenue.png|centre|vignette|upright=2.5|Calcul de la commande du MUX.]]
Voici ce que ça donne pour un bloc de 4 bits :
[[File:CSAdder4Bit.svg|centre|vignette|upright=1.5|Additionneur ''carry skip'' : bloc de 4 bits.]]
L'additionneur à saut de retenue est construit en assemblant plusieurs blocs de ce type.
[[File:BCSAdder16Bit.svg|centre|vignette|upright=3|Additionneur à saut de retenue.]]
===L'additionneur à sélection de retenue===
L''''additionneur à sélection de retenue''' utilise aussi des blocs, comme les additionneurs précédents. L'addition se fait en deux versions : une avec la retenue du bloc précédent valant zéro, et une autre version avec la retenue du bloc précédent valant 1. Il suffira alors de choisir le bon résultat avec un multiplexeur, une fois cette retenue connue. On gagne ainsi du temps en calculant à l'avance les valeurs de certains bits du résultat, sans connaître la valeur de la retenue. Petit détail : sur certains additionneurs à sélection de retenue, les blocs de base n'ont pas la même taille. Cela permet de tenir compte des temps de propagation des retenues entre les blocs.
[[File:Additionneur à sélection de retenue.png|centre|vignette|upright=1.5|Additionneur à sélection de retenue avec seulement deux blocs.]]
Dans les exemples du dessus, chaque sous-additionneur étaient des additionneurs à propagation de retenue. Mais ce n'est pas une obligation, et tout autre type d’additionneur peut être utilisé. Par exemple, on peut faire en sorte que les sous-additionneurs soient eux-mêmes des additionneurs à sélection de retenue, et poursuivre ainsi de suite, récursivement. On obtient alors un '''additionneur à somme conditionnelle''', plus rapide que l'additionneur à sélection de retenue, mais qui utilise beaucoup plus de portes logiques.
===Les additionneurs à anticipation de retenue===
Les '''additionneurs à anticipation de retenue''' calculent chaque retenue sans avoir à propager les retenues précédentes. Au lieu de calculer les retenues une par une, ils calculent toutes les retenues en parallèle, à partir des bits des opérandes. Une fois les retenues pré-calculées, il suffit de les additionner avec les deux bits adéquats, pour obtenir le résultat. Ces additionneurs sont composés de deux parties :
* un circuit qui pré-calcule les retenues, sans les propager, directement à partir des opérandes ;
* d'une couche d'additionneurs complets, qui additionnent chacun deux bits d'opérande et la retenue pré-calculée.
[[File:Additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Additionneur à anticipation de retenue.]]
Le calcul des retenues se fait en deux étapes. La première détermine si les retenues sont propagées ou générées, pour chaque colonne. Elle détermine, pour chaque colonne, les bits P et G qui indiquent respectivement : qu'une retenue a été générée sur cette colonne, que la colonne propage la retenue précédente. La seconde étape utilise ces signaux P et G pour déterminer la retenue sur chaque colonne. Le circuit de calcul des retenues est donc composé de deux parties : une couche de demi-additionneurs pour générer les signaux P et G, un circuit de calcul des retenues proprement dit.
[[File:Circuit complet d'un additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Circuit complet d'un additionneur à anticipation de retenue.]]
Pour rappel, la retenue de la colonne i, notée <math>C_i</math> est égale à :
: <math>G_i + (P_i . C_{i-1})</math>, avec G le signal de génération de retenue, P le signal de propagation de retenue.
Si on utilisait cette formule sans trop réfléchir, on retomberait sur un additionneur à propagation de retenue inutilement compliqué. L'astuce des additionneurs à anticipation de retenue consiste à remplacer le terme Ci−1 par sa valeur calculée avant. Par exemple, je prends un additionneur 4 bits. Je dispose de deux nombres A et B, contenant chacun 4 bits : A3, A2, A1, et A0 pour le nombre A, et B3, B2, B1, et B0 pour le nombre B. Si j'effectue les remplacements, j'obtiens les formules suivantes :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Ces formules nous permettent de déduire la valeur d'une retenue directement : il reste alors à créer un circuit qui implémente ces formules, et le tour est joué.
[[File:Four bit adder with carry lookahead.svg|centre|vignette|upright=1.5|Additionneur à anticipation de retenue de 4 bits.]]
Plus haut, j'ai dit que le circuit était composé de trois couches : une couche de demi-additionneur pour les signaux P et G ; un circuit d'anticipation de retenue, puis l'addition finale. Mais les deux bits d'opérandes sont déjà additionnés dans la couche de demi-additionneur, autant réutiliser le résultat pour l'additionner à la retenue. Pour éliminer toute redondance de ce type, il est possible d'utiliser des additionneurs partiels (des additionneurs où la sortie de retenue a été remplacée par deux sorties P et G). L'additionneur final est alors composé de plusieurs additionneurs complets de ce type, couplé à un circuit d'anticipation de retenue.
[[File:Cla4bits.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue de 4 bits.]]
Les additionneurs à anticipation de retenue sont plus rapides que les additionneurs à propagation de retenue. La raison est que les retenues sont calculées rapidement, dans un temps plus petit qu'en propageant la retenue. Pour rentrer dans les détails mathématiques, le temps de calcul n'est pas proportionnel au nombre de bits, mais proportionnel au logarithme de ce nombre de bits. Par contre, le gain en temps de calcul s'accompagne d'un cout en prote logique conséquent. Le circuit qui calcule les retenues anticipées utilise beaucoup de portes logiques.
La conséquence est qu'utiliser un additionneur à anticipation de retenue est une solution pratique pour additionner 4 ou 8 bits. Mais pour des opérandes plus grandes, de 16 ou 32 bits, cela utiliserait trop de portes logiques. Une partie de ce cout en portes logiques est cependant lié à des redondances. Si on analyse deux retenues différentes, à des colonnes différentes. Les circuits qui calculent ces deux retenues seront différents, mais ils contiendront des sous-circuits identiques. Pour vous en rendre compte, reprenons les quatre équations obtenues précédemment et essayez de trouver des redondances :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Vous devriez trouver :
* le terme ''P0 · C0'' dans toutes les lignes ;
* les termes ''P1 · P0 · C0'' et ''P1 · P0'' dans les trois dernières lignes ;
* le terme ''P2 · P1 · P0 · C0'' ; ''P2 · P1 · G0'' et ''P2 · G1'' dans les deux dernières lignes.
===Les additionneurs à calcul parallèle de préfixe===
Il existe plusieurs manières d'exploiter de telles redondances, exploitées dans les '''additionneurs à calcul parallèle de préfixe'''. L'optimisation apportée est de générer des signaux ''propagate'' et ''generate'' pour des groupes de 2, 3, 4, ..., N colonnes. Par exemple, il est possible de générer un signal ''P 0 vers 3'', qui indique si la retenue de la première colonne est propagée jusqu'à la 4ème colonne. Un autre exemple est un signal qui indique qu'une retenue a été générée entre les colonnes 4 à 7 génèrent une retenue ou non. En clair, les signaux P et G ont maintenant un intervalle, qui précise de quelle colonne vers quelle colonne se fait la propagation, ou entre quelles colonnes se fait la génération.
Il est possible de combiner les signaux P et G de deux groupes de colonne, s'ils sont contiguës (ils peuvent aussi se recouvrir). Par exemple, il est possible de calculer les bits P et G pour les colonnes 0 à 10, à partir des deux signaux P/G des colonnes 0-4 et 5-10. Pour cela, les équations sont assez simples. Si je prends deux groupes nommés A et B, avec A pour les colonnes de poids fort et B celles de poids faible, on a :
: <math>P_{sortie} = P_A . P_B</math>
: <math>G_{sortie} = G_A . P_A . G_B</math>
Pour les bits des colonnes 2, 4, 8, 16, 32, 64, et autres, le calcul est simple. L'idée est de grouper les colonnes par groupes de 2. Puis, on calcule chaque groupe de 4 bits à partir de deux groupes de 2. Puis les groupes de 8 bits sont calculés à partir de deux groupes de 4 bits, et ainsi de suite pour toute puissance de deux.
[[File:Squelette d'un additionneur à calcul parallèle de préfixe.jpg|centre|vignette|upright=2|Squelette d'un additionneur à calcul parallèle de préfixe]]
Les seules difficultés surviennent pour les colonnes dont le nombre n'est pas une puissance de deux. Il y a plusieurs manières pour subdiviser ces colonnes en intervalles plus petits et combiner le tout. Et elles donnent chacune des additionneurs différents. Ils portent les noms d'''additionneur de Ladner-Fisher'', d'''additionneur de Brent-Kung'', d'''additionneur de Kogge-Stone'', etc. Dans le cas général, l'additionneur de Kogge-Stone est un des plus rapide, mais aussi un des plus gourmand en circuits, alors que c'est l'inverse pour l'additionneur de Brent-Kung. Les deux additionneurs sont illustrés ci-dessous, pour des opérandes de 8 bits. Dans ceux-ci, chaque losange calculent les bits P et G, à partir des signaux P et G de deux groupes précédents.
{|
|[[File:Brent-kung-8-bit.png|vignette|Brent-kung-8-bit]]
|[[File:Kogge-stone-8-bit.png|vignette|upright=1.2|Kogge-stone-8-bit]]
|}
===Les additionneurs à anticipation de retenue multi-niveau===
Dans cette section, nous allons voir des additionneurs conçus en assemblant des additionneurs plus simples, qui additionnent environ 4 à 5 bits, parfois plus, parfois moins. Ces additionneurs simples seront nommés ''blocs'' dans ce qui suit, et l'un d'entre eux est illustré ci-contre. Chaque bloc prend en entrée un morceau des deux opérandes à additionner, mais aussi une retenue d'entrée.
Les blocs sont tous identiques dans le cas le plus simple, mais il est possible d'utiliser des blocs de taille variable. Par exemple, le premier bloc peut avoir des opérandes de 6 bits, le second des opérandes de 7 bits, etc. Faire ainsi permet de gagner un petit peu en performances, si la taille de chaque bloc est bien choisie. La raison est une question de temps de propagation des retenues. La retenue met plus de temps à se propager à travers 8 blocs qu'à travers 4, ce qui prend plus de temps qu'à travers 2 blocs, etc. En tenir compte fait que la taille des blocs tend à augmenter ou diminuer quand on se rapproche des bits de poids fort.
Il est possible d'utiliser l'anticipation de retenue entre deux blocs. Par exemple, il est possible de créer un additionneur 16 bits en utilisant 4 additionneurs/blocs de 4 bits. L'additionneur utilisé peut être un additionneur à propagation de retenue, ou à anticipation de retenue, peu importe.
Pour cela, les additionneurs de 4 bits doivent fournir deux sorties P et G. Les deux sorties indiquent que le bloc soit a généré une retenue, soit a propagé la retenue entrante. Par propagé, on veut dire que la retenue entrante du bloc s'est retrouvée sur la sortie de retenue du bloc. C'est le cas si les 4 colonnes propagent la retenue, ce qui signifie que :
: <math>P_\text{4 bits} = P_0 \cdot P_1 \cdot P_2 \cdot P_3</math>
Pour la génération, cela veut dire qu'une colonne a générée une retenue, qui a été propagée jusqu'à la sortie. En clair, pour chaque colonne, il faut que le G = 1, et que les signaux P des colonnes soient à 1. Pour les 4 colonnes, cela donne :
: <math>G_\text{4 bits} = G_3 + G_2 \cdot P_3 + G_1 \cdot P_3 \cdot P_2 + G_0 \cdot P_3 \cdot P_2 \cdot P_1</math>
[[File:Cla4bitsPG.png|centre|vignette|upright=2|Additionneur à anticipation de retenue de 4 bits.]]
Pour combiner 4 blocs de 4 bits, on devra donc combiner 4 bits P et G, avec un circuit d'anticipation de retenue.
[[File:16-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 16 bits.]]
Il est même possible d'aller plus loin et de combiner 4 additionneurs précédents pour obtenir un additionneur 64 bits.
[[File:64-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 64 bits.]]
Le résultat est un additionneur à plusieurs niveaux d’anticipation de retenue. Une anticipation de retenue dans un blocs de 4 bits (facultative), entre blocs de 4 bits, entre blocs de 16 bits. La même logique peut être utilisée avec des blocs de taille différente de 4, 16 et 64 bits.
===Le calcul parallèle de la retenue===
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Plus haut, nous avons vu l'anticipation de retenue. Il est possible de l'adapter pour fonctionner avec des blocs. Une première idée est d'utiliser plusieurs additionneurs à anticipation de retenue, chacun étant un bloc. Vu qu'il s'agit d'additionneurs, ils disposent d'une sortie de retenue pour la retenue sortante, et d'une entrée de retenue pour la retenue entrante. Par exemple, le schéma ci-contre illustre un additionneur 4 bits de ce type.
De ce fait, il est possible de propager les retenues d'un bloc à l'autre. par exemple, on peut utiliser 4 additionneurs à anticipation de retenue de 4 bits, et propager les retenues entre eux. Le premier bloc calcule une retenue, qui est propagée au second bloc. Le second bloc calcule lui aussi une retenue, qui est propagée au troisième bloc, etc.
[[File:4008 Functional Diagram.svg|vignette|upright=1|4008 Functional Diagram]]
Le gain en performance est significatif, mais il est possible de faire presque aussi rapide, tout en économisant beaucoup de circuits. L'idée est de simplifier les additionneurs 4 bits, afin de ne pas utiliser une anticipation de retenue complète. L'anticipation de retenue n'est utilisée que pour la retenue sortante, alors que les bits de somme sont calculées avec propagation de retenue. La retenue sortante est donc calculée en parallèle de l'addition. Chaque bloc contient, à côté d'un additionneur proprement dit, un circuit qui calcule la retenue sortante. Il existe de nombreuses manières de calculer la retenue sortante.
Le gain en portes logiques est assez significatif. En effet, on se passe de circuits d'anticipation de retenue pour les 4 bits de somme, on ne garde que celui pour la retenue sortante. Le fait d'utiliser la propagation de retenue pour calculer les 4 bits de somme est censé avoir un léger cout en performance, mais il se trouve que ce cout est très faible. En effet, le résultat n'est connu qu'une fois les retenues propagées jusqu'au dernier bloc. Et malgré l'anticipation des retenues, la propagation est assez lente. les additionneurs à propagation de retenue auront terminé leur travail bien avant.
Un exemple est celui de l'additionneur CMOS 4008, un additionneur de 4 bit. Il est composé en trois sections. Une première couche de demi-additionneurs calcule les signaux P et G utilisés à la fois pour l'anticipation de la retenue sortante, que pour calculer les bits de somme. Le circuit de calcul de la retenue sortante utilise l'anticipation de retenue. Le reste du circuit fait l'addition en propageant les retenues.
[[File:CMOS 4008, circuit découpé en sections.png|centre|vignette|upright=1.5|CMOS 4008, circuit découpé en sections]]
==L'addition signée et la soustraction==
Après avoir vu l'addition, il est logique de passer à la soustraction, les deux opérations étant très proches. Si on sait câbler une addition entre entiers positifs, câbler une soustraction n'est pas très compliqué. De plus, la soustraction permet de faire des additions de nombres signés.
===Le soustracteur pour opérandes entiers===
Pour soustraire deux nombres entiers, on peut adapter l'algorithme de soustraction utilisé en décimal, celui que vous avez appris à l'école. Celui-ci ressemble fortement à l'algorithme d'addition : on soustrait les bits de même poids, et on propage éventuellement une retenue sur la colonne suivante. À la différence de l'addition, la retenue est soustraite, et non ajoutée. La table de soustraction nous dit quel est le résultat de la soustraction de deux bits. La voici :
* 0 - 0 = 0 ;
* 0 - 1 = 1 et une retenue ;
* 1 - 0 = 1 ;
* 1 - 1 = 0.
[[File:Algebra1 05 fig014.svg|centre|vignette|Soustraction en binaire, avec les retenues en rouge.]]
La table de soustraction peut servir de table de vérité pour construire un circuit qui soustrait deux bits. Celui-ci est appelé un '''demi-soustracteur'''. Il ressemble beaucoup à un demi-additionneur, les différences se résumant à une porte NON ajoutée pour le calcul de la retenue.
[[File:Half Subtractor Vektor.svg|centre|vignette|Demi-soustracteur.]]
Comme pour l'additionneur, deux demi-soustracteurs peuvent être combinés pour donner un '''soustracteur complet'''. Le calcul de la retenue se fait en combinant les deux retenues des demi-soustracteurs avec une porte OU. Les soustracteurs complets sont utilisés pour créer des soustracteurs à propagation de retenue ou tout autre circuit soustracteur, sur le même modèle que les additionneurs.
[[File:FullSubtractor.svg|centre|vignette|upright=2|Soustracteur complet.]]
Vous l'aurez sans doute remarqué, mais le demi-soustracteur ressemble beaucoup au demi-additionneur. La seule différence est une porte NON ajoutée au bon endroit dans le demi-soustracteur.
{|
|[[File:Half-adder.svg|vignette|Demi-additionneur]]
|[[File:Half Subtractor Vektor.svg|vignette|upright=1.3|Demi-soustracteur]]
|}
La conséquence est qu'il est possible de créer un circuit capable de faire à la fois des additions et des soustractions. L'idée est de remplacer la porte NON du demi-soustracteur par un inverseur commandable. Si on envoie un zéro sur l'entrée de commande, l'inverseur commandable se comportera comme une porte OUI. Le circuit sera alors identique à un demi-additionneur. Si on envoie un 1 sur l'entrée de commande, l'inverseur commandable se comportera comme une porte NON. le circuit sera alors un demi-soustracteur. Il faut faire cela pour chaque demi-soustracteur, ce qui donne ceci pour un soustracteur complet. le résultat est alors appelé un '''additionneur-soustracteur complet'''.
[[File:Additionneur-soustracteur complet.png|centre|vignette|upright=2|Additionneur-soustracteur complet]]
===L'additionneur-soustracteur pour opérandes codées en complément à deux===
Étudions le cas de la soustraction en complément à deux, dans l'objectif de créer un circuit soustracteur. Vous savez sûrement que a−b et a+(−b) sont deux expressions équivalentes. Et en complément à deux, − b = not(b) + 1. Dit autrement, a − b = a + not(b) + 1. On pourrait se dire qu'il faut deux additionneurs pour faire le calcul, mais la majorité des additionneurs possède une entrée de retenue pour incrémenter le résultat de l'addition. Un soustracteur en complément à deux est donc simplement composé d'un additionneur et d'un inverseur.
[[File:Additionneur soustracteur.png|centre|vignette|upright=2|Soustracteur en complément à deux.]]
Il est possible de créer un circuit capable d'effectuer soit une addition, soit une soustraction : il suffit de remplacer l'inverseur par un inverseur commandable, qui peut être désactivé. On a vu comment créer un tel inverseur commandable dans le chapitre sur les circuits combinatoires. On peut remarquer que l'entrée de retenue et l'entrée de commande de l'inverseur sont activées en même temps : on peut fusionner les deux signaux en un seul.
[[File:Additionneur-soustracteur en complément à deux.png|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux.]]
Une implémentation alternative est la suivante. Elle remplace l'inverseur commandable par un multiplexeur.
[[File:4-bit ripple carry adder-subtracter.svg|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux, version alternative.]]
===L'additionneur-soustracteur pour opérandes codées en signe-magnitude===
Passons maintenant aux nombres codés en signe-valeur absolue, les deux opérandes étant notées A et B. Suivant les signes des deux opérandes, on a quatre cas possibles : A + B, A − B (B négatif), −A + B (A négatif) et −A − B (A et B négatifs). Une astuce est que le circuit n'a besoin que de calculer A + B et A − B : il peut les inverser pour obtenir − A − B ou B − A. A + B et A − B peuvent se calculer avec un additionneur-soustracteur, reste à corriger le résultat. Il suffit de lui ajouter un inverseur commandable pour obtenir le circuit d'addition finale.
[[File:Additionneur en signe-valeur absolue.png|centre|vignette|upright=2|Additionneur en signe-valeur absolue.]]
Toute la difficulté tient dans le calcul du bit de signe du résultat, quand interviennent des soustractions. Autant l'addition de deux nombres de même signe (A + B et −A − B) ne pose aucun problème, autant les soustractions posent problème (A − B et −A + B). Suivant que <math>A<B</math> ou que <math>A>B</math>, le signe du résultat ne sera pas le même. Déterminer le signe du résultat se fait en regardant les bits de débordement d'entier, comme on le verra plus bas.
===L'additionneur-soustracteur pour opérandes codées en représentation par excès===
Passons maintenant aux nombres codés en représentation par excès. On pourrait croire que ces nombres s'additionnent comme des nombres non-signés, mais ce serait oublier la présence du biais, qui pose problème. Dans les cas de nombres signés gérés avec un biais, voyons ce que donne l'addition de deux nombres :
: <math>( n_1 + biais ) + ( n_2 + biais ) = n_1 + n_2 + 2 \times biais</math>
Or, le résultat correct serait :
: <math>n_1 + n_2 + biais</math>
En effectuant l'addition telle quelle, le biais est compté deux fois. On doit donc le soustraire après l'addition pour obtenir le résultat correct.
Même chose pour la soustraction qui donne ceci :
: <math>(n_1 + biais) - (n_2 + biais) = n_1 - n_2</math>
Or, le résultat correct serait :
: <math> ( n_1 - n_2 ) + biais </math>
Il faut rajouter le biais pour obtenir l'exposant correct.
On a donc besoin de deux additionneurs/soustracteurs : un pour additionner/soustraire les représentations binaires des opérandes, et un autre pour ajouter/retirer le biais en trop/manquant.
==L'additionneur BCD==
Maintenant, voyons un additionneur qui additionne deux entiers au format BCD. Pour cela, nous allons devoir passer par deux étapes. La première est de créer un circuit capable d'additionneur deux chiffres BCD. Ensuite, nous allons voir comment enchaîner ces circuits pour créer un additionneur BCD complet.
===L'additionneur BCD qui fait l'opération chiffre par chiffre===
Nous allons commencer par voir un additionneur qui additionne deux chiffres en BCD, une sorte d'équivalent BCD de l'additionneur complet. Il fournit un résultat sur 4 bits et une retenue qui est mise à 1 si le résultat dépasse 10 (la limite d'un chiffre BCD). Les deux opérandes sont des chiffres BCD codés sur 4 bits et sont additionnés en binaire par un additionneur des plus normaux, similaire à ceux vus plus haut. Le résultat est alors un entier codé en binaire, sur 5 bits, qu'on corrige/convertit pour obtenir un chiffre BCD et une retenue sortante.
Pour corriger le résultat, une idée intuitive serait de prendre le résultat et de faire une division par 10. Le quotient donne la retenue, alors que le reste est le résultat, le chiffre BCD. Mais un circuit diviseur par 10 utilise beaucoup de portes logiques, ce qui ne vaut pas le coup. Une autre méthode détecte si le résultat est égal ou supérieur à 10, ce qui correspond à un "débordement" (on dépasse les limites d'un chiffre BCD). Si le résultat est plus petit que 10, il n'y a rien à faire : le résultat est bon et la retenue est de zéro. Par contre, si le résultat vaut 10 ou plus, il faut corriger le résultat et générer une retenue à 1.
Il faut donc ajouter un circuit qui détecte si le résultat est supérieur à 9, qui calcule directement la retenue. Ce circuit peut se fabriquer simplement à partir de sa table de vérité, ou en utilisant les techniques que nous verrons dans un chapitre ultérieur sur les comparateurs. La solution la plus simple est clairement d'utiliser la table de vérité, ce qui est très simple, assez pour être laissé en exercice au lecteur. Pour comprendre comment corriger le résultat, établissons une table de vérité qui associe le résultat et le résultat corrigé. L'entrée vaut au minimum 10 et au maximum 9 + 9 = 18. On considère la sortie comme un tout, la retenue étant un 5ème bit, le bit de poids fort.
{|class="wikitable"
|-
! colspan="5" | Entrée
!
! rowspan="10" |
! Retenue
! Résultat corrigé (sans retenue)
! interprétation de la sortie en binaire (retenue inclue)
|-
| 0 || 1 || 0 || 1 || 0
| (10)
| 1 || 0000
| (16)
|-
| 0 || 1 || 0 || 1 || 1
| (11)
| 1 || 0001
| (17)
|-
| 0 || 1 || 1 || 0 || 0
| (12)
| 1 || 0010
| (18)
|-
| 0 || 1 || 1 || 0 || 1
| (13)
| 1 || 0011
| (19)
|-
| 0 || 1 || 1 || 1 || 0
| (14)
| 1 || 0100
| (20)
|-
| 0 || 1 || 1 || 1 || 1
| (15)
| 1 || 0101
| (21)
|-
| 1 || 0 || 0 || 0 || 0
| (16)
| 1 || 0110
| (22)
|-
| 1 || 0 || 0 || 0 || 1
| (17)
| 1 || 0111
| (23)
|-
| 1 || 0 || 0 || 1 || 0
| (18)
| 1 || 1000
| (24)
|}
En analysant le tableau, on voit que pour corriger le résultat, il suffit d'ajouter 6. La raison est que le résultat déborde d'un nibble à 16 en binaire, mais à 10 en décimal : il suffit d'ajouter la différence entre les deux, à savoir 6, et le débordement binaire fait son travail. Donc, la correction après une addition est très simple : si le résultat dépasse 9, on ajoute 6.
On peut maintenant implémenter l'additionneur BCD, en combinant le comparateur avec 10, le circuit de correction, et l'additionneur. La première solution calcule deux versions du résultat : la version corrigée, la version normale. Le choix entre les deux est réalisée par un multiplexeur, commandé par le comparateur.
[[File:Additionneur BCD.png|centre|vignette|upright=1.5|Additionneur BCD]]
L'autre solution utilise un circuit commandable qui soit additionne 6, soit ne fait rien. Le choix entre les deux est commandé par le bit calculé par le comparateur.
[[File:Additionneur BCD, seconde version.png|centre|vignette|upright=1.5|Additionneur BCD, seconde version.]]
Une version alternative du circuit précédent est la suivante. Il contient deux additionneurs : un pour additionner les deux chiffres BCD, un autre pour additionner 6 si besoin. Le résultat du comparateur est directement utilisé pour générer l'opérande du second additionneur : 0 ou 6. Le circuit est simple à concevoir, mais gaspille beaucoup de circuit. Idéalement, il vaudrait mieux utiliser un circuit combinatoire d'addition avec une constante.
[[File:Adder 4 Bit BCD.svg|centre|vignette|upright=2|Additionneur BCD, circuit complet.]]
Pour obtenir un additionneur BCD complet, il suffit d’enchaîner les additionneurs précédents, comme on le ferait avec les additionneurs complets dans un additionneur à propagation de retenue. Au final, l'additionneur BCD est beaucoup plus compliqué qu'un additionneur normal, car il rajoute un comparateur ">9", un petit additionneur pour ajouter 6 et éventuellement d'autres circuits. De plus, il est difficile d'appliquer les optimisations disponibles sur les additionneurs non-BCD. Notamment, les circuits d'anticipation de retenue sont totalement à refaire et le résultat est relativement compliqué. C'est ce qui explique pourquoi le BCD a progressivement été abandonné au profit du binaire simple.
La soustraction en BCD se fait comme en binaire : le nombre à soustraire est remplacé par son complément, le circuit additionne le complément et l'autre opérande, le débordement d'entier fait que le résultat marche. Sauf qu'ici, le complément est un complément à 9. Il se calcule chiffre par chiffre : chaque chiffre est remplacé par (9 - le chiffre en question).
===L'additionneur BCD par ajustement décimal===
L'additionneur BCD précédent effectuait son travail chiffre BCD par chiffre BCD, mais il existe des additionneurs BCD qui font autrement. Sur les premiers processeurs x86, il n'y avait pas d'opération d'addition BCD proprement dit, seulement une addition binaire normale de 8, 16 ou 32 bits. Par contre, elle était secondée par une opération dite d''''ajustement décimal''' qui transformait un nombre binaire en nombre codé en BCD. L'opération d'ajustement décimal prenait un opérande de 8 bits codé en binaire et fournissait un résultat de la même taille, c'est à dire deux chiffres BCD. Effectuer une addition BCD demandait donc de faire deux opérations à la suite : une addition binaire simple, suivie par l'opération d'ajustement décimal. Cela permettait de gérer des nombres entiers en binaire usuel et des entiers BCD sans avoir deux instructions d'addition séparées pour les deux, sans compter que cela simplifiait aussi les circuits d'addition.
L'ajustement décimal s'effectue en ajoutant une constante bien précise à l'opérande à convertir en BCD. L'idée est que la constante est découpée en morceaux de 4 bits, correspondant chacun à un chiffre BCD de l'opérande, chaque morceau contenant soit un 0, soit 6. Cela permet d'ajouter soit 0, soit 6, à chaque chiffre BCD, et donc de le corriger. La propagation des retenues d'un chiffre à l'autre est effectuée automatiquement par l'addition binaire de la constante. L'opération d'ajustement décimal calcule automatiquement la constante. Elle découpe l'opérande en ''nibbles'', vérifie si chaque ''nibble'' est supérieur ou égal à 10, puis détermine la valeur de chaque ''nibble'' de la constante finale. Par exemple, si je prends l'opérande 1001 1110, le ''nibble'' de poids faible déborde, alors que celui de poids fort non. La constante sera donc 0000 0110 : 0x06. Inversement, si le ''nibble'' de poids fort déborde et pas celui de poids faible, la constante sera alors 0x60. Et la constante est de 0x66 si les deux ''nibbles'' débordent, de 0x00 si aucun ne déborde.
Le circuit d’ajustement décimal est donc composé de trois étapes : deux étapes pour calculer la constante, et un circuit d'addition pour additionner cette constante au nombre de départ. La première étape découpe l'opérande en morceaux de 4 bits, en chiffres BCD, et vérifie si chacun d'entre eux vaut 10 ou plus. La seconde étape prend les résultats de la première étape, et les combine pour calculer la constante. Enfin, on trouve l'addition finale, qui était réalisée par un circuit d'addition utilisé à la fois pour l'ajustement décimal et l'addition binaire. La différence entre une addition normale et une opération d'ajustement décimal tient dans le fait que les deux premières étapes sont désactivées dans une addition normale.
[[File:Additionneur BCD parallèle.png|centre|vignette|upright=1.5|Additionneur BCD parallèle]]
===L'additionneur biquinaire===
Les entiers BCD ne sont qu'un des encodages hybrides entre décimal et binaire. L'encodage biquinaire est l'un d'entre eux et nous allons faire un rappel rapide à ce sujet. Pour simplifier, un chiffre encodé en biquinaire est composé de deux parties : un bit, couplé à une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits (0, 1, 2, 3 et 4). Le bit indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9.
Additionner deux nombres de biquinaire demande donc d'additionner deux parties quinaires encodées en ''one-hot'' et d'additionner deux bits. Mais attention : il faut tenir compte de la retenue de l'addition des parties quinaires. Et idéalement, il faut aussi tenir compte d'une retenue entrante, provenant de l'addition de la colonne de chiffres précédente. Toute la difficulté vient de la création de l'additionneur ''one-hot''. Heureusement, vu qu'il n'y a que 4-5 bits à additionner, il est souvent fabriqué à partir de sa table de vérité.
[[File:Additionneur bi-quinaire.png|centre|vignette|upright=2|Additionneur bi-quinaire]]
Un avantage du biquinaire est que le calcul du complément à 9 est très simple. Il faut pour cela : inverser la partie binaire avec une porte NON, puis inverser l'ordre des bits de la partie quinaire. Concrètement, le bit de poids faible devient le bit de poids fort, et ainsi de suite. Par exemple, une partie quinaire 01000 devient 00010, 10000 devient 00001, 00100 ne change pas, etc. Le tout peut se calculer avec une porte NON et 5 multiplexeurs.
===L'additionneur BCD avec calculs intermédiaires en biquinaire===
L'ordinateur IBM 1401, un ancien ''mainframe'' des années 60, utilisait un additionneur BCD un peu particulier. Les nombres étaient encodés en BCD dans la mémoire de l'ordinateur, mais les circuits de calcul utilisaient la représentation biquinaire. Lors d'un calcul, le processeur de l'ordinateur traduisait les chiffres BCD en représentation biquinaire, faisait une addition en biquinaire, avant de traduire le résultat en BCD normal.
Pour être précis, l'IBM 1401 utilisait une variante du biquinaire. L'encodage biquinaire de l'IBM 1401 est le suivant : la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre pair encodé par la partie quinaire. Si l'IBM 1401 utilisait cette variante du biquinaire, c'est car elle donnait des circuits de conversion BCD-biquinaire plus économes en portes logiques et plus rapides.
La partie binaire est le bit de poids faible du chiffre BCD, la partie biquinaire est calculée par un simple décodeur qui prend en entrée le chiffre BCD, amputé de son bit de poids faible. La traduction inverse demande d'utiliser un encodeur, à la place du décodeur. Par contre, le circuit d'addition biquinaire était plus compliqué du fait de la gestion des retenues. L'addition des parties binaires et quinaires se faisait en parallèle, dans deux additionneurs séparés. Cependant, l'addition des parties binaire fournit une retenue, qu'il faut prendre en compte. Pour cela, l'IBM 1401 disposait d'un troisième additionneur qui fournissait le résultat final, encodé en biquinaire.
[[File:Additionneur bi-quinaire de l'IBM 1401.png|centre|vignette|upright=2|Additionneur biquinaire de l'IBM 1401]]
Une implémentation moderne demanderait d'utiliser des portes ET combinées à des portes OU, le circuit pouvant être construit simplement à partir de sa table de vérité. Sur l'IBM 1401, le circuit était cependant différent, en raison de l'utilisation de OU câblés, des croisements de fils qui fonctionnent comme des portes OU, que nous n'avons pas encore vu pour le moment, mais qui seront détaillés dans quelques chapitres. Les OU câblés étaient utilisés pour simplifier le design du circuit, mais demandaient des portes logiques spécifiques, ce qui collait avec le fait que ce ''mainframe'' utilisait des transistors en Germanium. L'implémentation exacte est décrite dans cet article de blog, mais je ne recommande sa lecture qu'à ceux qui savent ce qu'est un OU câblé :
* [https://www.righto.com/2015/10/qui-binary-arithmetic-how-1960s-ibm.html Qui-binary arithmetic: how a 1960s IBM mainframe does math].
==Les débordements d'entier lors d'une addition/soustraction==
Les instructions arithmétiques manipulent des entiers codés sur un nombre fixe de bits, qui ne peuvent prendre leurs valeurs que dans un intervalle. Pour les nombres positifs, un ordinateur qui code ses entiers sur n bits pourra coder tous les entiers allant de 0 à <math>2^n -1</math>. Pour les nombres négatifs, l'intervalle est différent et dépend de la représentation utilisée. Dans le cas général, l'ordinateur peut coder les valeurs comprises de <math>N_\text{min}</math> à <math>N_\text{max}</math>. Si le résultat d'un calcul sort de cet intervalle, il ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math> , alors que la valeur basse vaut <math>N_\text{min} - 1</math> (avec <math>N_\text{max}</math> et <math>N_\text{min}</math> respectivement la plus grande et la plus petite valeur codable par l'ordinateur).
===La correction des débordements d'entier : l'arithmétique saturée===
Quand un débordement d'entier survient, tous les circuits de calcul ne procèdent pas de la même manière. Dans les grandes lignes, il y a deux réactions possibles : soit on corrige automatiquement le résultat du débordement, soit on ne fait rien et on se contente de détecter le débordement.
Si le débordement n'est pas corrigé automatiquement, le circuit ne conserve que les bits de poids faibles du résultat. Les bits en trop sont simplement ignorés. On dit qu'on utilise l''''arithmétique modulaire'''. Le problème avec ce genre d'arithmétique, c'est qu'une opération entre deux grands nombres peut donner un résultat très petit. Par exemple, si je dispose de registres 4 bits et que je souhaite faire l'addition 1111 + 0010 (ce qui donne 15 + 2), le résultat est censé être 10001 (17), ce qui est un résultat plus grand que la taille d'un registre. En conservant les 4 bits de poids faible, j’obtiens 0001 (1). En clair, un résultat très grand est transformé en un résultat très petit. Cela peut poser problèmes si on travaille uniquement avec des nombres positifs, mais c'est aussi utilisé pour coder des nombres en complément à deux.
D'autres circuits utilisent ce qu'on appelle l''''arithmétique saturée''' : si un calcul génère un débordement, on arrondi le résultat au plus grand entier supporté par le circuit. Les circuits capables de calculer en arithmétique saturée sont un peu plus complexes, vu qu'il faut rajouter des circuits pour corriger le résultat en cas de débordement. Il suffit généralement de rajouter un ''circuit de saturation'', qui prend en entrée le résultat et le corrige en cas de débordement. Ce circuit de saturation met la valeur maximale en sortie si un débordement survient, mais se contente de recopier le résultat du calcul sur sa sortie s'il n'y a pas de débordement. Typiquement, il est composé d'une couche de multiplexeurs, qui sélectionnent quelle valeur mettre sur la sortie : soit le résultat du calcul, soit le plus grand nombre entier géré par le processeur, soit le plus petit (pour les nombres négatifs/soustractions).
L'arithmétique saturée est utilisée pour les additions et soustractions, mais c'est plus rare pour les multiplications/divisions. Une des raisons est que le résultat d'une addition/soustraction prend un bit de plus que le résultat, là où les multiplications doublent le nombre de bits. Quand une addition déborde, le résultat réel est proche de la valeur maximale codable. mais quand une multiplication déborde, le résultat peut parfois valoir 200 à 60000 fois plus que la valeur maximale codable. Les calculs avec une valeur saturée/corrigée sont donc crédibles pour une suite d'additions, mais pas pour une suite de multiplications.
===La détection des débordements entiers===
Quand un débordement d'entier a eu lieu, il vaut mieux que l'additionneur prévienne ! Pour cela, l'additionneur a une '''sortie de débordement''', parfois nommée ''Overflow'', dont la valeur indique si l'addition a généré un débordement d'entier ou non. Reste que détecter un débordement ne se fait pas de la même manière selon que l'on parle d'un additionneur non-signé ou signé.
Pour les additionneur non-signés, l'additionneur calcule un bit de plus que ce qui est supporté par l'ordinateur. Par exemple, un additionneur 32 bits fournit un résultat sur 33 bits, un débordement d'entier a lieu quand le 33ème bit est à 1. Précisément, la sortie de débordement n'est autre que la retenue finale, celle fournie par le dernier additionneur complet. Le seul type de débordement possible est un débordement par le haut, où le résultat dépasse la valeur maximale. Avec l'arithmétique saturée, le circuit de saturation consiste en une seule couche de multiplexeurs, voire en un circuit de mise à la valeur maximale tel que vu dans le chapitre sur les opérations bits à bits.
[[File:Gestion des débordements d'entiers.png|centre|vignette|upright=2.5|Gestion des débordements d'entiers lors d'une addition non-signée.]]
Pour les additionneurs non-signés, la gestion des débordements d'entiers dépend fortement de la représentation signée. Nous allons étudier le cas du complément à deux. Si vous vous rappelez le chapitre 1, les calculs sur des nombres en complètement à deux utilisent les règles de l'arithmétique modulaire, c'est une condition nécessaire. À priori, on peut penser que dans ces conditions, les débordements d'entiers sont une chose parfaitement normale, qui nous permet d'avoir des résultats corrects. Néanmoins, certains débordements d'entiers peuvent survenir malgré tout et produire des bugs assez ennuyeux.
Si l'on tient en compte les règles du complément à deux, on sait que le bit de poids fort (le plus à gauche) permet de déterminer si le nombre est positif ou négatif : il indique le signe du nombre. Tout se passe comme si les entiers en complément à deux étaient codés sur un bit de moins, et avaient leur longueur amputé du bit de poids fort. Si le résultat d'un calcul écrase le bit de poids fort, il y a un débordement d'entiers. Il existe une règle simple qui permet de détecter ces débordements d'entiers. L'addition de deux nombres positifs ne peut pas être un nombre négatif. Si on additionne deux nombres dont le bit de signe est à 0 et que le bit de signe du résultat est à 1, on est en face d'un débordement d'entiers. Même chose pour deux nombres négatifs : le résultat de l'addition ne peut pas être positif. On peut résumer cela en une phrase : si deux nombres de même signe sont ajoutés, un débordement a lieu quand le bit du signe du résultat a le signe opposé.
Modifier les circuits d'au-dessus pour qu'ils détectent les débordements en complément à deux est simple comme bonjour : il suffit créer un petit circuit combinatoire qui prenne en entrée les bits de signe des opérandes et du résultat, et qui fasse le calcul de l'indicateur de débordements. Si l'on rédige sa table de vérité, on doit se retrouver avec la table suivante :
{|class="wikitable"
|-
!Entrées
!Sortie
|-
|000||0
|-
|001||1
|-
|010||0
|-
|011||0
|-
|100||0
|-
|101||0
|-
|110||1
|-
|111||0
|}
L'équation de ce circuit est la suivante, avec <math>S_a</math> et <math>S_b</math> les signes des deux opérandes, et <math>C_i</math> la retenue de la colonne précédente :
: <math> ( S_a . S_b . \overline{C_i} ) + ( \overline{S_a} . \overline{S_b} . C_i )</math>
En simplifiant, on obtient alors :
: <math>( S_a . S_b ) \oplus C_i</math>
Or, il se trouve que <math>S_a . S_b</math> est tout simplement la retenue en sortie du dernier additionneur, que nous noterons <math>C_o</math>. On trouve donc :
: <math>C_o \oplus C_i</math>
Il suffit donc de faire un XOR entre la dernière retenue et la précédente pour obtenir le bit de débordement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de décalage et de rotation
| prevText=Les circuits de décalage et de rotation
| next=Les circuits de comparaison
| nextText=Les circuits de comparaison
}}
</noinclude>
dlcmdbfcuh4xrex4z8s6lr3cmpckerh
768659
768658
2026-06-25T19:45:08Z
Mewtow
31375
/* Les additionneurs à anticipation de retenue multi-niveau */
768659
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir les circuits capables de faire une addition ou une soustraction, ainsi que quelques circuits spécialisés. Précisons cependant que les fabricants de processeurs travaillent d'arrache-pied pour trouver des moyens de rendre ces circuits de calcul plus rapides et plus économes en énergie. Autant vous dire que les circuits que vous allez voir sont vraiment des circuits qui font pâle figure comparé à ce que l'on peut trouver dans un vrai processeur commercial !
==Les circuits pour additionner 2 ou 3 bits==
L'addition se fait en binaire de la même manière qu'en décimal. On additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. La soustraction fonctionne sur le même principe, sur le même modèle qu'en décimal.
[[File:Binary Addition Demonstration.svg|centre|vignette|Exemple d'addition en binaire.]]
En clair, additionner deux nombres demande d'additionner 2 bits et une retenue sur chaque colonne, et de propager les retenues d'une colonne à l'autre. La propagation des retenues est quelque chose de simple en apparence, mais qui est sujet à des optimisations extraordinairement nombreuses. Aussi, pour simplifier l'exposition, nous allons voir comment gérer une colonne avant de voir comment sont propagées les retenues.
[[File:Full Adder Block.svg|vignette|upright=1|class=transparent|Additionneur complet.]]
Si on effectue une addition en colonne, on doit additionner les deux bits sur la colonne, mais aussi additionner une éventuelle retenue. Il faut donc créer un circuit qui additionne trois bits : deux bits de données, plus une retenue. Ce circuit qui additionne trois bits est appelé un '''additionneur complet'''. Il fournit en sortie deux bits : un bit de somme et une retenue sortante. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|-
||0||0||0|| ||0||0
|-
||0||0||1|| ||0||1
|-
||0||1||0|| ||0||1
|-
||0||1||1|| ||1||0
|-
||1||0||0|| ||0||1
|-
||1||0||1|| ||1||0
|-
||1||1||0|| ||1||0
|-
||1||1||1|| ||1||1
|}
Il est possible d'utiliser un tableau de Karnaugh pour traduire la table de vérité, mais elle donne un résultat assez compliqué. La retenue et le bit de somme sont calculés à part. La retenue est calculée avec trois portes ET et une porte OU. Le bit de somme est calculé lui avec 4 portes ET, une porte OU, et trois portes NON.
[[File:Full Adler with 3 entries.png|centre|vignette|upright=2|Additionneur complet, fabriqué à partir de la table de vérité.]]
D'autres méthodes donnent des résultats plus compréhensibles. Nous allons les voir dans la suite de cette section.
===L'additionneur complet basé sur des demi-additionneurs===
[[File:1-bit half-adder.svg|class=transparent|vignette|Demi-addtionneur.]]
Nous avons déjà vu comment additionner deux bits dans le chapitre sur les incrémenteurs, mais quelques rappels ne seront pas de trop. Le '''demi-additionneur''' est un circuit qui additionne deux bits. Il implémente la table d'addition, qui est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Mais un demi-additionneur n'additionne que deux bits, il manque de quoi additionner la retenue. Une solution pour cela est d'enchaîner deux demi-additionneurs : un qui additionne les deux bits de données, et un second qui additionne la retenue au résultat du précédent. La retenue finale se calcule en combinant les sorties de retenue des deux demi-additionneurs, avec une porte OU. Le résultat est un additionneur complet fabriqué avec deux demi-additionneurs. Pour vous en convaincre, établissez la table de vérité de ce circuit, vous verrez que ça marche.
[[File:Full Adder Blocks.svg|centre|vignette|upright=2|Additionneur complet fabriqué avec deux demi-additionneurs.]]
[[File:Half-adder.svg|vignette|upright=1|Circuit d'un demi-additionneur naïf.]]
L'implémentation naïve d'un demi-additionneur utilise une porte XOR et une porte ET, comme illustré ci-contre. Le circuit s'obtient en faisant uen traduction littérale de la table de vérité du circuit. Si je dis qu'elle est naïve, c'est car il est possible de l'optimiser, de manière à éliminer des redondances cachées. Mais nous avions déjà vu cela dans le chapitre sur les incrémenteurs, aussi laissons cela pour plus tard.
En combinant deux demi-additionneurs de ce type, on obtient l'additionneur complet suivant. Les deux sorties de retenue des demi-additionneurs sont combinées avec une porte OU, pour calculer la retenue finale.
{| class="flexible"
|[[File:Full Adder Modules.svg|class=transparent|Composition d'un additionneur complet. On voit bien que celui-ci est composé de deux demi-additionneurs, en rouge et en bleu, auxquels on a ajouté une porte OU pour calculer la retenue finale.]]
|[[File:Full-adder.svg|300px|class=transparent|Circuit d'un additionneur complet.]]
|}
Le fait de combiner les deux retenues avec une porte OU n'est pas ce qu'il y a de plus intuitif. La table de vérité nous dit que ça fonctionne, mais on comprend mal pourquoi. Mais tout devient plus clair quand on sait que les deux retenues ne peuvent pas être à 1 en même temps. Même dans l'addition 1 + 1 + 1, seule une retenue est à 1. Soit l'addition des deux bits d'opérande donne naissance à une retenue, soit c'est l'addition ''résultat + retenue entrante''. La retenue sortant est donc à 1 quand une des deux addition donne une retenue. D'où l'usage d'une porte OU. Remarquez qu'une porte XOR donne le même résultat, vu que le cas où les deux retenues sont à 1 n'est jamais rencontré.
===La propagation et la génération des retenues===
L'additionneur complet que nous allons voir dans cette section sert à introduire les concepts de propagation, génération et absorption de retenue. Il s'agit de concepts très importants quand on étudie les additionneurs. l'idée part d'un principe très simple : la retenue sortante dépend de la retenue d'entrée. La relation entre les deux se résume à trois cas, qui dépendent de la valeur des deux bits additionnés, nommés A et B.
* Dans le premier cas, la retenue entrante est égale à la retenue sortante. On dit que la retenue entrante est propagée par l'additionneur.
* Dans le second cas, la retenue est forcée à 0 : la retenue sortante vaut 0, peu importe la valeur de la retenue entrante.
* Dans le troisième cas, la retenue est forcée à 1 : la retenue entrante vaut 1, peu importe la valeur de la retenue entrante.
[[File:Signaux P et G fournis par un demi-additionneur.png|vignette|Signaux P et G fournis par un demi-additionneur]]
Maintenant, créons un circuit qui nous dise si une retenue est propagée ou générée. Il a deux bits de sortie, nommés P et G : P pour ''Propagate'', G pour ''Generate''. Le bit P est à 1 si la retenue entrante est propagée, il est à 0 sinon. Le bit G est à 1 si une retenue est générée, à 0 sinon. Une retenue est considérée comme absorbée si elle n'est pas ni propagée ni générée, pas besoin d'un troisième bit pour gérer ce cas.
Il se trouve que ce circuit n'est autre qu'un demi-additionneur ! Pour vous en rendre compte, regardez la table de vérité d'un additionneur complet, illustrée ci-dessous. Lorsque les deux bits d'opérande sont à 0, la retenue sortante vaut toujours 0. Si ils sont tous deux à 1, alors la retenue sortante vaut 1. S'ils sont différents, alors retenues sortante et entrante sont égales. Le bit P est donc généré par une simple porte XOR. Quant au bit G, il est à 1 si les deux bits d'opérandes sont à 1, ce qui correspond à une porte ET. Il se trouve que ces deux portes forment un demi-additionneur !
{|class="wikitable"
|-
! Opérande 1 !! Opérande 2 !! Retenue entrante !! !! Retenue sortante
|- class="f_rouge"
||0||0||0|| ||0
|- class="f_rouge"
||0||0||1|| ||0
|- class="f_bleu"
||0||1||0|| ||0
|- class="f_bleu"
||0||1||1|| ||1
|- class="f_bleu"
||1||0||0|| ||0
|- class="f_bleu"
||1||0||1|| ||1
|- class="f_vert"
||1||1||0|| ||1
|- class="f_vert"
||1||1||1|| ||1
|}
Ensuite, créons un circuit qui prend ces deux sorties P et G, et calcule la retenue sortante en fonction. Le circuit en question a trois entrées : la retenue entrante, les deux bits P et G. La retenue sortante vaut 1 soit si une retenue est générée, soit si la retenue entrante est propagée et qu'elle vaut 1. En notant <math>{C_{in}}</math> et <math>{C_{out}}</math> les retenues entrantes et sortante, on a :
: <math>{C_{out}} = G + \left( P . C_{in} \right)</math>
Le circuit est donc composé d'une porte OU et d'une porte ET. En combinant un demi-additionneur avec le circuit de calcul de retenue sortante vu plus haut, on a :
[[File:Additionneur complet avec propagation et génération de retenue.png|class=transparent|centre|vignette|upright=2|Additionneur complet avec propagation et génération de retenue.]]
Certes, on retombe sur le circuit vu plus haut. Mais il est possible de remplacer le circuit en orange par un autre. Par exemple, il est possible de le remplacer par multiplexeur, qui choisit entre la retenue générée et la retenue entrante (propagée). Le choix se fait selon la valeur du bit P, qui chosiit entre propager la retenue et la générer.
[[File:Additionneur crée avec un multiplexeur.png|centre|vignette|upright=1.5|Additionneur crée avec un multiplexeur]]
Le circuit exact est illustré ci-dessous. Il semble utiliser plus de portes logiques que l'additionneur complet précédent. Cependant, nous verrons dans quelques chapitres qu'il est possible d'implémenter un multiplexeur avec seulement 6 transistors, voire moins ! L'implémentation utilise des portes à transmission, mais nous en reparlerons dans le chapitre sur les transistors, quand nous verrons les additionneurs à ''Manchester Carry Chain''. Au passage, une variante de ce circuit a été utilisée dans le processeur processeur 8086 d'Intel, comme on le verra dans le chapitre suivant.
[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
[[File:FulladderPG.png|vignette|upright=1|Additionneur complet avec deux sorties P et G.]]
Pour finir, sachez qu'il existe des additionneurs qui fournissent : le bit de résultat, deux sorties P et G qui indiquent si l'addition propage ou génère une retenue. Il ne s'agit pas d'additionneurs complets, car il manque de quoi calculer la retenue sortante. De tels additionneurs seront appelés des '''additionneurs partiels'''. De tels additionneurs sont utilisés dans certains additionneurs pour gagner en performance.
Enfin, il existe aussi ce que j'ai décidé d'appeler des '''additionneurs P/G''', qui sont des additionneurs complets auxquels on a ajouté deux sorties P et G, en plus de la sortie de retenue. Leur circuit est le même que celui d'un additionneur complet, auquel on a ajouté deux fils. Vu que ces deux sorties sont fournies par le premier demi-additionneur, ajouter ces deux sorties demande d'ajouter des fils, pas de portes logiques. De tels additionneurs sont aussi utilisés dans certains additionneurs pour gagner en performance.
===L'additionneur complet basé sur une porte à majorité===
Maintenant, voyons une dernière implémentation possible de l'additionneur complet. Mais avant de voir comment l'additionneur est implémenté, nous allons devoir faire un petit focus sur la retenue sortante. Reprenons la table de vérité, et regardons ce qu'il en est pour la retenue sortante uniquement.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante
|-
||0||0||0|| ||0
|-
||0||0||1|| ||0
|-
||0||1||0|| ||0
|-
||0||1||1|| ||1
|-
||1||0||0|| ||0
|-
||1||0||1|| ||1
|-
||1||1||0|| ||1
|-
||1||1||1|| ||1
|}
Vous remarquerez que la retenue sortante est égale au bit majoritaire, parmi les trois bits d'opérande. Si deux ou trois bits sont à 1, la retenue sortante vaut 1. Et réciproquement avec 0. Le circuit de calcul de la retenue peut donc être remplacé par une '''porte à majorité'''.
[[File:Additionneur crée avec une porte à majorité.png|centre|vignette|upright=2|Additionneur crée avec une porte à majorité]]
Mais une porte à majorité est assez gourmande en circuit, ce qui fait qu'un additionneur pareil ne serait pas pratique, ni utile. Du moins, ce serait le cas s'il n'y avait pas une possibilité d'optimisation extrêmement intéressante. Il est possible de calculer le 'bit de somme' à partir de la retenue sortante ! En effet, le bit de somme est l'inverse de la retenue sortante, sauf dans deux cas : les trois bits d'entrée sont à 0, où ils sont tous à 1. Le bit de somme vaut 0 dans le premier cas, 1 dans le second cas.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|- class="f_bleu"
||0||0||0|| ||0||0
|- class="f_vert"
||0||0||1|| ||0||1
|- class="f_vert"
||0||1||0|| ||0||1
|- class="f_vert"
||0||1||1|| ||1||0
|- class="f_vert"
||1||0||0|| ||0||1
|- class="f_vert"
||1||0||1|| ||1||0
|- class="f_vert"
||1||1||0|| ||1||0
|- class="f_rouge"
||1||1||1|| ||1||1
|}
En clair, 6 lignes de la table de vérité sur 8 peuvent se calculer avec une porte à majorité, le reste demandant quelques portes logiques pour faire la correction. L'implémentation en circuit demande donc trois choses :
* de calculer la retenue sortante et de quoi l'inverser ;
* un circuit qui vérifie si tous les bits opérande valent 0 : une porte NOR ou OU fait l'affaire ;
* un circuit qui vérifie s'ils valent tous 1, à savoir une porte ET.
Ensuite, on combine le résultat des trois circuits précédents pour obtenir le résultat final. La combinaison est le fait du circuit en jaune dans le schéma ci-dessous.
[[File:Full adder basé sur une modification de la retenue.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue]]
Le circuit en jaune est assez simple. Quand la porte ET sort un 1, la sortie doit être forcée à 1. Par contre, quand la porte OU sort un zéro, la sortie doit être forcée à 0. Il suffit donc d'enchainer un circuit de mise à 1 et un circuit de mise à 0. Nous avons vu dans le chapitre sur les opérations de masquage qu'il s'agit respectivement d'une porte OU et d'une porte ET.
[[File:Full adder basé sur une modification de la retenue interne, au complet.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue interne, au complet]]
Divers processeurs utilisaient des additionneurs complets de ce type : le fameux Z80, l'Intel 4004 et de l'Intel 8008 sont les exemples les plus notables. Pourtant, on pourrait s'étonner qu'un tel circuit ait existé. Il utilise beaucoup de portes logiques, a une profondeur logique supérieure : il n'a rien d'avantageux. Sauf qu'il était utilisé sur d'anciens processeurs, qui utilisaient la technologie dite TTL, différente de la technologie CMOS des transistors modernes. Et avec la technologie TTL, il est possible de fusionner plusieurs portes logiques ET et NOR en une seule porte logique ET/OU/NON ! Un additionneur complet construit ainsi ne prenait que deux portes logiques : une pour le calcul de la retenue sortante, une autre pour le reste du circuit.
==L'addition non signée==
Voyons maintenant un circuit capable d'additionner deux nombres entiers: l''''additionneur'''. Dans la version qu'on va voir, ce circuit manipulera des nombres strictement positifs. L'addition des nombres codés en complètement à deux sera vu dans une section ultérieure.
===L'additionneur série===
Il est possible d'additionner deux nombres bit par bit, gr$ace à un additionneur complet associé à plusieurs registres à décalages. Les opérandes sont placées chacune dans un registre à décalage, et l'additionneur complet utilise les bits sortants de ces registres à décalage. Le bit du résultat est envoyé au registre à décalage pour le résultat. La retenue de l'addition est stockée dans une bascule de 1 bit, en attente du prochain cycle d'horloge. Un tel additionneur est appelé un '''additionneur série'''. Il a été utilisé sur d'anciens ordinateurs dans les années 50-60, aussi bien des prototypes que des ordinateurs commerciaux.
[[File:Additionneur série.jpg|centre|vignette|upright=2.5|Additionneur série.]]
===L'additionneur à propagation de retenue===
L''''additionneur à propagation de retenue''' est aussi appelé l'additionneur ''ripple carry'', c'est son nom anglais. J'utiliserais occasionnellement le nom anglais dans ce qui suit, car celui-ci est plus court. Ilutilise un additionneur complet pour chaque colonne de l'addition. Pour gérer les retenues, il suffit ainsi de câbler des additionneurs complets les uns à la suite des autres, chacun envoyant sa retenue à la colonne suivante.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Notons la présence de la retenue sortante, qui est utilisée pour détecter les débordements d'entier, ainsi que pour d'autres opérations. Le bit de retenue final est souvent stocké dans un registre spécial du processeur (généralement appelé ''carry flag'').
Notez aussi, à droite du schéma précédent, l'entrée de retenue <math>\text{R}_\text{Entrée}</math>. Elle est très utile pour l'implémentation de certaines opérations comme l'inversion de signe, la soustraction, l'incrémentation, etc. Certains processeurs sont capables de faire une opération appelée ADC, ADDC ou autre nom signifiant ''Addition with Carry'', qui permet de faire le calcul A + B + Retenue (la retenue en question est la retenue sortante de l'addition précédente, stockée dans le registre ''carry flag''). Son utilité principale est de permettre des additions d'entiers plus grands que ceux supportés par le processeur. Par exemple, cela permet de faire des additions d'entiers 32 bits sur un processeur 16 bits.
[[File:Adder Network 3 Bit Sum Delay 003.svg|vignette|upright=0.5|Propagation de retenue dans l'additionneur.]]
Cet additionneur utilise très peu de portes logiques, ce qui fait qu'il était utilisé sur certains processeurs 8 et 16 bits, dont le budget en portes logiques était limité. Bien que très simple, cet additionneur est cependant peu performant. Son temps de calcul est proportionnel à la taille des opérandes. Par exemple, additionner deux opérandes de 32 bits prendra deux fois plus de temps que pour des opérandes de 16 bits. La raison est que les retenues doivent se propager à travers le circuit, du premier additionneur jusqu'au dernier.
L'addition étant une opération fréquente, d'autres additionneurs ont été inventés. Ils utilisent des optimisations qui utilisent plus de circuits pour gagner en rapidité. Mais avant de voir ces optimisations, nous allons voir s'il est possible d'optimiser les additionneurs à propagation de retenue.
===L'additionneur à saut de retenue===
L'additionneur à propagation de retenue peut être optimisé de deux manières. La première accélère la propagation de la retenue, en agissant au niveau des additionneurs complets. La ''Manchester carry chain'' est une optimisation de ce type, mais on ne peut pas encore expliquer à ce stade du cours, car elle optimisait les additionneurs complets au niveau des transistors eux-mêmes. Elle a été utilisée sur de nombreux processeurs connus, comme le 8086 d'Intel ou le Z80.
La seconde manière rend l'addition plus rapide dans certains cas. Le résultat est un additionneur dont le temps de calcul est variable. Le calcul prendra quelques cycles d'horloges avec certains opérandes, mais d'autres opérandes ne verront aucune amélioration. Il n'améliore pas le pire des cas, dans lequel la retenue doit être propagée du début à la fin, du bit de poids faible au bit de poids fort. Mais dans d'autres cas, il permet d'avoir le résultat en avance. L'optimisation en question donne l''''additionneur à saut de retenue''' (''carry-skip adder'').
Il est composé de blocs qui additionnent 4/5 bits, rarement plus. La taille d'un bloc varie d'un additionneur à l'autre, elle peut même varier dans l'additionneur, mais nous allons prendre un exemple où les blocs font 4 bits. L'idée est de détecter précocement si la retenue entrante est propagée à travers tout le bloc. Si la retenue est propagée dans tous le bloc, on peut directement l'envoyer sur la sortie de retenue, la retenue saute le bloc entier. Dans le cas contraire, on doit calculer la retenue normalement. Le choix entre les deux est le fait d'un multiplexeur. Toute la difficulté est de savoir comment commander le multiplexeur.
[[File:Carry skip adder, principe de base.png|centre|vignette|upright=2.5|Carry skip adder : principe de base]]
Pour savoir si une retenue est propagée dans un bloc entier, il faut utiliser des additionneurs complets, qui ont en plus une sortie P indiquant que l'additionneur propage la retenue. Le bloc propage une retenue si chaque additionneur complet propage la retenue, donc s'ils ont tous leur sortie P à 1. Un simple ET logique suffit à déterminer si c'est le cas. Le signal de commande du multiplexeur est donc un vulgaire ET entre toutes les sorties P des additionneurs complets du bloc.
[[File:Additionneur à saut de retenue.png|centre|vignette|upright=2.5|Calcul de la commande du MUX.]]
Voici ce que ça donne pour un bloc de 4 bits :
[[File:CSAdder4Bit.svg|centre|vignette|upright=1.5|Additionneur ''carry skip'' : bloc de 4 bits.]]
L'additionneur à saut de retenue est construit en assemblant plusieurs blocs de ce type.
[[File:BCSAdder16Bit.svg|centre|vignette|upright=3|Additionneur à saut de retenue.]]
===L'additionneur à sélection de retenue===
L''''additionneur à sélection de retenue''' utilise aussi des blocs, comme les additionneurs précédents. L'addition se fait en deux versions : une avec la retenue du bloc précédent valant zéro, et une autre version avec la retenue du bloc précédent valant 1. Il suffira alors de choisir le bon résultat avec un multiplexeur, une fois cette retenue connue. On gagne ainsi du temps en calculant à l'avance les valeurs de certains bits du résultat, sans connaître la valeur de la retenue. Petit détail : sur certains additionneurs à sélection de retenue, les blocs de base n'ont pas la même taille. Cela permet de tenir compte des temps de propagation des retenues entre les blocs.
[[File:Additionneur à sélection de retenue.png|centre|vignette|upright=1.5|Additionneur à sélection de retenue avec seulement deux blocs.]]
Dans les exemples du dessus, chaque sous-additionneur étaient des additionneurs à propagation de retenue. Mais ce n'est pas une obligation, et tout autre type d’additionneur peut être utilisé. Par exemple, on peut faire en sorte que les sous-additionneurs soient eux-mêmes des additionneurs à sélection de retenue, et poursuivre ainsi de suite, récursivement. On obtient alors un '''additionneur à somme conditionnelle''', plus rapide que l'additionneur à sélection de retenue, mais qui utilise beaucoup plus de portes logiques.
===Les additionneurs à anticipation de retenue===
Les '''additionneurs à anticipation de retenue''' calculent chaque retenue sans avoir à propager les retenues précédentes. Au lieu de calculer les retenues une par une, ils calculent toutes les retenues en parallèle, à partir des bits des opérandes. Une fois les retenues pré-calculées, il suffit de les additionner avec les deux bits adéquats, pour obtenir le résultat. Ces additionneurs sont composés de deux parties :
* un circuit qui pré-calcule les retenues, sans les propager, directement à partir des opérandes ;
* d'une couche d'additionneurs complets, qui additionnent chacun deux bits d'opérande et la retenue pré-calculée.
[[File:Additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Additionneur à anticipation de retenue.]]
Le calcul des retenues se fait en deux étapes. La première détermine si les retenues sont propagées ou générées, pour chaque colonne. Elle détermine, pour chaque colonne, les bits P et G qui indiquent respectivement : qu'une retenue a été générée sur cette colonne, que la colonne propage la retenue précédente. La seconde étape utilise ces signaux P et G pour déterminer la retenue sur chaque colonne. Le circuit de calcul des retenues est donc composé de deux parties : une couche de demi-additionneurs pour générer les signaux P et G, un circuit de calcul des retenues proprement dit.
[[File:Circuit complet d'un additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Circuit complet d'un additionneur à anticipation de retenue.]]
Pour rappel, la retenue de la colonne i, notée <math>C_i</math> est égale à :
: <math>G_i + (P_i . C_{i-1})</math>, avec G le signal de génération de retenue, P le signal de propagation de retenue.
Si on utilisait cette formule sans trop réfléchir, on retomberait sur un additionneur à propagation de retenue inutilement compliqué. L'astuce des additionneurs à anticipation de retenue consiste à remplacer le terme Ci−1 par sa valeur calculée avant. Par exemple, je prends un additionneur 4 bits. Je dispose de deux nombres A et B, contenant chacun 4 bits : A3, A2, A1, et A0 pour le nombre A, et B3, B2, B1, et B0 pour le nombre B. Si j'effectue les remplacements, j'obtiens les formules suivantes :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Ces formules nous permettent de déduire la valeur d'une retenue directement : il reste alors à créer un circuit qui implémente ces formules, et le tour est joué.
[[File:Four bit adder with carry lookahead.svg|centre|vignette|upright=1.5|Additionneur à anticipation de retenue de 4 bits.]]
Plus haut, j'ai dit que le circuit était composé de trois couches : une couche de demi-additionneur pour les signaux P et G ; un circuit d'anticipation de retenue, puis l'addition finale. Mais les deux bits d'opérandes sont déjà additionnés dans la couche de demi-additionneur, autant réutiliser le résultat pour l'additionner à la retenue. Pour éliminer toute redondance de ce type, il est possible d'utiliser des additionneurs partiels (des additionneurs où la sortie de retenue a été remplacée par deux sorties P et G). L'additionneur final est alors composé de plusieurs additionneurs complets de ce type, couplé à un circuit d'anticipation de retenue.
[[File:Cla4bits.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue de 4 bits.]]
Les additionneurs à anticipation de retenue sont plus rapides que les additionneurs à propagation de retenue. La raison est que les retenues sont calculées rapidement, dans un temps plus petit qu'en propageant la retenue. Pour rentrer dans les détails mathématiques, le temps de calcul n'est pas proportionnel au nombre de bits, mais proportionnel au logarithme de ce nombre de bits. Par contre, le gain en temps de calcul s'accompagne d'un cout en prote logique conséquent. Le circuit qui calcule les retenues anticipées utilise beaucoup de portes logiques.
La conséquence est qu'utiliser un additionneur à anticipation de retenue est une solution pratique pour additionner 4 ou 8 bits. Mais pour des opérandes plus grandes, de 16 ou 32 bits, cela utiliserait trop de portes logiques. Une partie de ce cout en portes logiques est cependant lié à des redondances. Si on analyse deux retenues différentes, à des colonnes différentes. Les circuits qui calculent ces deux retenues seront différents, mais ils contiendront des sous-circuits identiques. Pour vous en rendre compte, reprenons les quatre équations obtenues précédemment et essayez de trouver des redondances :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Vous devriez trouver :
* le terme ''P0 · C0'' dans toutes les lignes ;
* les termes ''P1 · P0 · C0'' et ''P1 · P0'' dans les trois dernières lignes ;
* le terme ''P2 · P1 · P0 · C0'' ; ''P2 · P1 · G0'' et ''P2 · G1'' dans les deux dernières lignes.
===Les additionneurs à calcul parallèle de préfixe===
Il existe plusieurs manières d'exploiter de telles redondances, exploitées dans les '''additionneurs à calcul parallèle de préfixe'''. L'optimisation apportée est de générer des signaux ''propagate'' et ''generate'' pour des groupes de 2, 3, 4, ..., N colonnes. Par exemple, il est possible de générer un signal ''P 0 vers 3'', qui indique si la retenue de la première colonne est propagée jusqu'à la 4ème colonne. Un autre exemple est un signal qui indique qu'une retenue a été générée entre les colonnes 4 à 7 génèrent une retenue ou non. En clair, les signaux P et G ont maintenant un intervalle, qui précise de quelle colonne vers quelle colonne se fait la propagation, ou entre quelles colonnes se fait la génération.
Il est possible de combiner les signaux P et G de deux groupes de colonne, s'ils sont contiguës (ils peuvent aussi se recouvrir). Par exemple, il est possible de calculer les bits P et G pour les colonnes 0 à 10, à partir des deux signaux P/G des colonnes 0-4 et 5-10. Pour cela, les équations sont assez simples. Si je prends deux groupes nommés A et B, avec A pour les colonnes de poids fort et B celles de poids faible, on a :
: <math>P_{sortie} = P_A . P_B</math>
: <math>G_{sortie} = G_A . P_A . G_B</math>
Pour les bits des colonnes 2, 4, 8, 16, 32, 64, et autres, le calcul est simple. L'idée est de grouper les colonnes par groupes de 2. Puis, on calcule chaque groupe de 4 bits à partir de deux groupes de 2. Puis les groupes de 8 bits sont calculés à partir de deux groupes de 4 bits, et ainsi de suite pour toute puissance de deux.
[[File:Squelette d'un additionneur à calcul parallèle de préfixe.jpg|centre|vignette|upright=2|Squelette d'un additionneur à calcul parallèle de préfixe]]
Les seules difficultés surviennent pour les colonnes dont le nombre n'est pas une puissance de deux. Il y a plusieurs manières pour subdiviser ces colonnes en intervalles plus petits et combiner le tout. Et elles donnent chacune des additionneurs différents. Ils portent les noms d'''additionneur de Ladner-Fisher'', d'''additionneur de Brent-Kung'', d'''additionneur de Kogge-Stone'', etc. Dans le cas général, l'additionneur de Kogge-Stone est un des plus rapide, mais aussi un des plus gourmand en circuits, alors que c'est l'inverse pour l'additionneur de Brent-Kung. Les deux additionneurs sont illustrés ci-dessous, pour des opérandes de 8 bits. Dans ceux-ci, chaque losange calculent les bits P et G, à partir des signaux P et G de deux groupes précédents.
{|
|[[File:Brent-kung-8-bit.png|vignette|Brent-kung-8-bit]]
|[[File:Kogge-stone-8-bit.png|vignette|upright=1.2|Kogge-stone-8-bit]]
|}
===Les additionneurs à anticipation de retenue multi-niveau===
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Dans cette section, nous allons voir des additionneurs conçus en assemblant des additionneurs plus simples, qui additionnent environ 4 à 5 bits, parfois plus, parfois moins. Ces additionneurs simples seront nommés ''blocs'' dans ce qui suit, et l'un d'entre eux est illustré ci-contre. Chaque bloc prend en entrée un morceau des deux opérandes à additionner, mais aussi une retenue d'entrée.
Les blocs sont tous identiques dans le cas le plus simple, mais il est possible d'utiliser des blocs de taille variable. Par exemple, le premier bloc peut avoir des opérandes de 6 bits, le second des opérandes de 7 bits, etc. Faire ainsi permet de gagner un petit peu en performances, si la taille de chaque bloc est bien choisie. La raison est une question de temps de propagation des retenues. La retenue met plus de temps à se propager à travers 8 blocs qu'à travers 4, ce qui prend plus de temps qu'à travers 2 blocs, etc. En tenir compte fait que la taille des blocs tend à augmenter ou diminuer quand on se rapproche des bits de poids fort.
Il est possible d'utiliser l'anticipation de retenue entre deux blocs. Par exemple, il est possible de créer un additionneur 16 bits en utilisant 4 additionneurs/blocs de 4 bits. L'additionneur utilisé peut être un additionneur à propagation de retenue, ou à anticipation de retenue, peu importe.
Pour cela, les additionneurs de 4 bits doivent fournir deux sorties P et G. Les deux sorties indiquent que le bloc soit a généré une retenue, soit a propagé la retenue entrante. Par propagé, on veut dire que la retenue entrante du bloc s'est retrouvée sur la sortie de retenue du bloc. C'est le cas si les 4 colonnes propagent la retenue, ce qui signifie que :
: <math>P_\text{4 bits} = P_0 \cdot P_1 \cdot P_2 \cdot P_3</math>
Pour la génération, cela veut dire qu'une colonne a générée une retenue, qui a été propagée jusqu'à la sortie. En clair, pour chaque colonne, il faut que le G = 1, et que les signaux P des colonnes soient à 1. Pour les 4 colonnes, cela donne :
: <math>G_\text{4 bits} = G_3 + G_2 \cdot P_3 + G_1 \cdot P_3 \cdot P_2 + G_0 \cdot P_3 \cdot P_2 \cdot P_1</math>
[[File:Cla4bitsPG.png|centre|vignette|upright=2|Additionneur à anticipation de retenue de 4 bits.]]
Pour combiner 4 blocs de 4 bits, on devra donc combiner 4 bits P et G, avec un circuit d'anticipation de retenue.
[[File:16-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 16 bits.]]
Il est même possible d'aller plus loin et de combiner 4 additionneurs précédents pour obtenir un additionneur 64 bits.
[[File:64-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 64 bits.]]
Le résultat est un additionneur à plusieurs niveaux d’anticipation de retenue. Une anticipation de retenue dans un blocs de 4 bits (facultative), entre blocs de 4 bits, entre blocs de 16 bits. La même logique peut être utilisée avec des blocs de taille différente de 4, 16 et 64 bits.
===Le calcul parallèle de la retenue===
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Plus haut, nous avons vu l'anticipation de retenue. Il est possible de l'adapter pour fonctionner avec des blocs. Une première idée est d'utiliser plusieurs additionneurs à anticipation de retenue, chacun étant un bloc. Vu qu'il s'agit d'additionneurs, ils disposent d'une sortie de retenue pour la retenue sortante, et d'une entrée de retenue pour la retenue entrante. Par exemple, le schéma ci-contre illustre un additionneur 4 bits de ce type.
De ce fait, il est possible de propager les retenues d'un bloc à l'autre. par exemple, on peut utiliser 4 additionneurs à anticipation de retenue de 4 bits, et propager les retenues entre eux. Le premier bloc calcule une retenue, qui est propagée au second bloc. Le second bloc calcule lui aussi une retenue, qui est propagée au troisième bloc, etc.
[[File:4008 Functional Diagram.svg|vignette|upright=1|4008 Functional Diagram]]
Le gain en performance est significatif, mais il est possible de faire presque aussi rapide, tout en économisant beaucoup de circuits. L'idée est de simplifier les additionneurs 4 bits, afin de ne pas utiliser une anticipation de retenue complète. L'anticipation de retenue n'est utilisée que pour la retenue sortante, alors que les bits de somme sont calculées avec propagation de retenue. La retenue sortante est donc calculée en parallèle de l'addition. Chaque bloc contient, à côté d'un additionneur proprement dit, un circuit qui calcule la retenue sortante. Il existe de nombreuses manières de calculer la retenue sortante.
Le gain en portes logiques est assez significatif. En effet, on se passe de circuits d'anticipation de retenue pour les 4 bits de somme, on ne garde que celui pour la retenue sortante. Le fait d'utiliser la propagation de retenue pour calculer les 4 bits de somme est censé avoir un léger cout en performance, mais il se trouve que ce cout est très faible. En effet, le résultat n'est connu qu'une fois les retenues propagées jusqu'au dernier bloc. Et malgré l'anticipation des retenues, la propagation est assez lente. les additionneurs à propagation de retenue auront terminé leur travail bien avant.
Un exemple est celui de l'additionneur CMOS 4008, un additionneur de 4 bit. Il est composé en trois sections. Une première couche de demi-additionneurs calcule les signaux P et G utilisés à la fois pour l'anticipation de la retenue sortante, que pour calculer les bits de somme. Le circuit de calcul de la retenue sortante utilise l'anticipation de retenue. Le reste du circuit fait l'addition en propageant les retenues.
[[File:CMOS 4008, circuit découpé en sections.png|centre|vignette|upright=1.5|CMOS 4008, circuit découpé en sections]]
==L'addition signée et la soustraction==
Après avoir vu l'addition, il est logique de passer à la soustraction, les deux opérations étant très proches. Si on sait câbler une addition entre entiers positifs, câbler une soustraction n'est pas très compliqué. De plus, la soustraction permet de faire des additions de nombres signés.
===Le soustracteur pour opérandes entiers===
Pour soustraire deux nombres entiers, on peut adapter l'algorithme de soustraction utilisé en décimal, celui que vous avez appris à l'école. Celui-ci ressemble fortement à l'algorithme d'addition : on soustrait les bits de même poids, et on propage éventuellement une retenue sur la colonne suivante. À la différence de l'addition, la retenue est soustraite, et non ajoutée. La table de soustraction nous dit quel est le résultat de la soustraction de deux bits. La voici :
* 0 - 0 = 0 ;
* 0 - 1 = 1 et une retenue ;
* 1 - 0 = 1 ;
* 1 - 1 = 0.
[[File:Algebra1 05 fig014.svg|centre|vignette|Soustraction en binaire, avec les retenues en rouge.]]
La table de soustraction peut servir de table de vérité pour construire un circuit qui soustrait deux bits. Celui-ci est appelé un '''demi-soustracteur'''. Il ressemble beaucoup à un demi-additionneur, les différences se résumant à une porte NON ajoutée pour le calcul de la retenue.
[[File:Half Subtractor Vektor.svg|centre|vignette|Demi-soustracteur.]]
Comme pour l'additionneur, deux demi-soustracteurs peuvent être combinés pour donner un '''soustracteur complet'''. Le calcul de la retenue se fait en combinant les deux retenues des demi-soustracteurs avec une porte OU. Les soustracteurs complets sont utilisés pour créer des soustracteurs à propagation de retenue ou tout autre circuit soustracteur, sur le même modèle que les additionneurs.
[[File:FullSubtractor.svg|centre|vignette|upright=2|Soustracteur complet.]]
Vous l'aurez sans doute remarqué, mais le demi-soustracteur ressemble beaucoup au demi-additionneur. La seule différence est une porte NON ajoutée au bon endroit dans le demi-soustracteur.
{|
|[[File:Half-adder.svg|vignette|Demi-additionneur]]
|[[File:Half Subtractor Vektor.svg|vignette|upright=1.3|Demi-soustracteur]]
|}
La conséquence est qu'il est possible de créer un circuit capable de faire à la fois des additions et des soustractions. L'idée est de remplacer la porte NON du demi-soustracteur par un inverseur commandable. Si on envoie un zéro sur l'entrée de commande, l'inverseur commandable se comportera comme une porte OUI. Le circuit sera alors identique à un demi-additionneur. Si on envoie un 1 sur l'entrée de commande, l'inverseur commandable se comportera comme une porte NON. le circuit sera alors un demi-soustracteur. Il faut faire cela pour chaque demi-soustracteur, ce qui donne ceci pour un soustracteur complet. le résultat est alors appelé un '''additionneur-soustracteur complet'''.
[[File:Additionneur-soustracteur complet.png|centre|vignette|upright=2|Additionneur-soustracteur complet]]
===L'additionneur-soustracteur pour opérandes codées en complément à deux===
Étudions le cas de la soustraction en complément à deux, dans l'objectif de créer un circuit soustracteur. Vous savez sûrement que a−b et a+(−b) sont deux expressions équivalentes. Et en complément à deux, − b = not(b) + 1. Dit autrement, a − b = a + not(b) + 1. On pourrait se dire qu'il faut deux additionneurs pour faire le calcul, mais la majorité des additionneurs possède une entrée de retenue pour incrémenter le résultat de l'addition. Un soustracteur en complément à deux est donc simplement composé d'un additionneur et d'un inverseur.
[[File:Additionneur soustracteur.png|centre|vignette|upright=2|Soustracteur en complément à deux.]]
Il est possible de créer un circuit capable d'effectuer soit une addition, soit une soustraction : il suffit de remplacer l'inverseur par un inverseur commandable, qui peut être désactivé. On a vu comment créer un tel inverseur commandable dans le chapitre sur les circuits combinatoires. On peut remarquer que l'entrée de retenue et l'entrée de commande de l'inverseur sont activées en même temps : on peut fusionner les deux signaux en un seul.
[[File:Additionneur-soustracteur en complément à deux.png|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux.]]
Une implémentation alternative est la suivante. Elle remplace l'inverseur commandable par un multiplexeur.
[[File:4-bit ripple carry adder-subtracter.svg|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux, version alternative.]]
===L'additionneur-soustracteur pour opérandes codées en signe-magnitude===
Passons maintenant aux nombres codés en signe-valeur absolue, les deux opérandes étant notées A et B. Suivant les signes des deux opérandes, on a quatre cas possibles : A + B, A − B (B négatif), −A + B (A négatif) et −A − B (A et B négatifs). Une astuce est que le circuit n'a besoin que de calculer A + B et A − B : il peut les inverser pour obtenir − A − B ou B − A. A + B et A − B peuvent se calculer avec un additionneur-soustracteur, reste à corriger le résultat. Il suffit de lui ajouter un inverseur commandable pour obtenir le circuit d'addition finale.
[[File:Additionneur en signe-valeur absolue.png|centre|vignette|upright=2|Additionneur en signe-valeur absolue.]]
Toute la difficulté tient dans le calcul du bit de signe du résultat, quand interviennent des soustractions. Autant l'addition de deux nombres de même signe (A + B et −A − B) ne pose aucun problème, autant les soustractions posent problème (A − B et −A + B). Suivant que <math>A<B</math> ou que <math>A>B</math>, le signe du résultat ne sera pas le même. Déterminer le signe du résultat se fait en regardant les bits de débordement d'entier, comme on le verra plus bas.
===L'additionneur-soustracteur pour opérandes codées en représentation par excès===
Passons maintenant aux nombres codés en représentation par excès. On pourrait croire que ces nombres s'additionnent comme des nombres non-signés, mais ce serait oublier la présence du biais, qui pose problème. Dans les cas de nombres signés gérés avec un biais, voyons ce que donne l'addition de deux nombres :
: <math>( n_1 + biais ) + ( n_2 + biais ) = n_1 + n_2 + 2 \times biais</math>
Or, le résultat correct serait :
: <math>n_1 + n_2 + biais</math>
En effectuant l'addition telle quelle, le biais est compté deux fois. On doit donc le soustraire après l'addition pour obtenir le résultat correct.
Même chose pour la soustraction qui donne ceci :
: <math>(n_1 + biais) - (n_2 + biais) = n_1 - n_2</math>
Or, le résultat correct serait :
: <math> ( n_1 - n_2 ) + biais </math>
Il faut rajouter le biais pour obtenir l'exposant correct.
On a donc besoin de deux additionneurs/soustracteurs : un pour additionner/soustraire les représentations binaires des opérandes, et un autre pour ajouter/retirer le biais en trop/manquant.
==L'additionneur BCD==
Maintenant, voyons un additionneur qui additionne deux entiers au format BCD. Pour cela, nous allons devoir passer par deux étapes. La première est de créer un circuit capable d'additionneur deux chiffres BCD. Ensuite, nous allons voir comment enchaîner ces circuits pour créer un additionneur BCD complet.
===L'additionneur BCD qui fait l'opération chiffre par chiffre===
Nous allons commencer par voir un additionneur qui additionne deux chiffres en BCD, une sorte d'équivalent BCD de l'additionneur complet. Il fournit un résultat sur 4 bits et une retenue qui est mise à 1 si le résultat dépasse 10 (la limite d'un chiffre BCD). Les deux opérandes sont des chiffres BCD codés sur 4 bits et sont additionnés en binaire par un additionneur des plus normaux, similaire à ceux vus plus haut. Le résultat est alors un entier codé en binaire, sur 5 bits, qu'on corrige/convertit pour obtenir un chiffre BCD et une retenue sortante.
Pour corriger le résultat, une idée intuitive serait de prendre le résultat et de faire une division par 10. Le quotient donne la retenue, alors que le reste est le résultat, le chiffre BCD. Mais un circuit diviseur par 10 utilise beaucoup de portes logiques, ce qui ne vaut pas le coup. Une autre méthode détecte si le résultat est égal ou supérieur à 10, ce qui correspond à un "débordement" (on dépasse les limites d'un chiffre BCD). Si le résultat est plus petit que 10, il n'y a rien à faire : le résultat est bon et la retenue est de zéro. Par contre, si le résultat vaut 10 ou plus, il faut corriger le résultat et générer une retenue à 1.
Il faut donc ajouter un circuit qui détecte si le résultat est supérieur à 9, qui calcule directement la retenue. Ce circuit peut se fabriquer simplement à partir de sa table de vérité, ou en utilisant les techniques que nous verrons dans un chapitre ultérieur sur les comparateurs. La solution la plus simple est clairement d'utiliser la table de vérité, ce qui est très simple, assez pour être laissé en exercice au lecteur. Pour comprendre comment corriger le résultat, établissons une table de vérité qui associe le résultat et le résultat corrigé. L'entrée vaut au minimum 10 et au maximum 9 + 9 = 18. On considère la sortie comme un tout, la retenue étant un 5ème bit, le bit de poids fort.
{|class="wikitable"
|-
! colspan="5" | Entrée
!
! rowspan="10" |
! Retenue
! Résultat corrigé (sans retenue)
! interprétation de la sortie en binaire (retenue inclue)
|-
| 0 || 1 || 0 || 1 || 0
| (10)
| 1 || 0000
| (16)
|-
| 0 || 1 || 0 || 1 || 1
| (11)
| 1 || 0001
| (17)
|-
| 0 || 1 || 1 || 0 || 0
| (12)
| 1 || 0010
| (18)
|-
| 0 || 1 || 1 || 0 || 1
| (13)
| 1 || 0011
| (19)
|-
| 0 || 1 || 1 || 1 || 0
| (14)
| 1 || 0100
| (20)
|-
| 0 || 1 || 1 || 1 || 1
| (15)
| 1 || 0101
| (21)
|-
| 1 || 0 || 0 || 0 || 0
| (16)
| 1 || 0110
| (22)
|-
| 1 || 0 || 0 || 0 || 1
| (17)
| 1 || 0111
| (23)
|-
| 1 || 0 || 0 || 1 || 0
| (18)
| 1 || 1000
| (24)
|}
En analysant le tableau, on voit que pour corriger le résultat, il suffit d'ajouter 6. La raison est que le résultat déborde d'un nibble à 16 en binaire, mais à 10 en décimal : il suffit d'ajouter la différence entre les deux, à savoir 6, et le débordement binaire fait son travail. Donc, la correction après une addition est très simple : si le résultat dépasse 9, on ajoute 6.
On peut maintenant implémenter l'additionneur BCD, en combinant le comparateur avec 10, le circuit de correction, et l'additionneur. La première solution calcule deux versions du résultat : la version corrigée, la version normale. Le choix entre les deux est réalisée par un multiplexeur, commandé par le comparateur.
[[File:Additionneur BCD.png|centre|vignette|upright=1.5|Additionneur BCD]]
L'autre solution utilise un circuit commandable qui soit additionne 6, soit ne fait rien. Le choix entre les deux est commandé par le bit calculé par le comparateur.
[[File:Additionneur BCD, seconde version.png|centre|vignette|upright=1.5|Additionneur BCD, seconde version.]]
Une version alternative du circuit précédent est la suivante. Il contient deux additionneurs : un pour additionner les deux chiffres BCD, un autre pour additionner 6 si besoin. Le résultat du comparateur est directement utilisé pour générer l'opérande du second additionneur : 0 ou 6. Le circuit est simple à concevoir, mais gaspille beaucoup de circuit. Idéalement, il vaudrait mieux utiliser un circuit combinatoire d'addition avec une constante.
[[File:Adder 4 Bit BCD.svg|centre|vignette|upright=2|Additionneur BCD, circuit complet.]]
Pour obtenir un additionneur BCD complet, il suffit d’enchaîner les additionneurs précédents, comme on le ferait avec les additionneurs complets dans un additionneur à propagation de retenue. Au final, l'additionneur BCD est beaucoup plus compliqué qu'un additionneur normal, car il rajoute un comparateur ">9", un petit additionneur pour ajouter 6 et éventuellement d'autres circuits. De plus, il est difficile d'appliquer les optimisations disponibles sur les additionneurs non-BCD. Notamment, les circuits d'anticipation de retenue sont totalement à refaire et le résultat est relativement compliqué. C'est ce qui explique pourquoi le BCD a progressivement été abandonné au profit du binaire simple.
La soustraction en BCD se fait comme en binaire : le nombre à soustraire est remplacé par son complément, le circuit additionne le complément et l'autre opérande, le débordement d'entier fait que le résultat marche. Sauf qu'ici, le complément est un complément à 9. Il se calcule chiffre par chiffre : chaque chiffre est remplacé par (9 - le chiffre en question).
===L'additionneur BCD par ajustement décimal===
L'additionneur BCD précédent effectuait son travail chiffre BCD par chiffre BCD, mais il existe des additionneurs BCD qui font autrement. Sur les premiers processeurs x86, il n'y avait pas d'opération d'addition BCD proprement dit, seulement une addition binaire normale de 8, 16 ou 32 bits. Par contre, elle était secondée par une opération dite d''''ajustement décimal''' qui transformait un nombre binaire en nombre codé en BCD. L'opération d'ajustement décimal prenait un opérande de 8 bits codé en binaire et fournissait un résultat de la même taille, c'est à dire deux chiffres BCD. Effectuer une addition BCD demandait donc de faire deux opérations à la suite : une addition binaire simple, suivie par l'opération d'ajustement décimal. Cela permettait de gérer des nombres entiers en binaire usuel et des entiers BCD sans avoir deux instructions d'addition séparées pour les deux, sans compter que cela simplifiait aussi les circuits d'addition.
L'ajustement décimal s'effectue en ajoutant une constante bien précise à l'opérande à convertir en BCD. L'idée est que la constante est découpée en morceaux de 4 bits, correspondant chacun à un chiffre BCD de l'opérande, chaque morceau contenant soit un 0, soit 6. Cela permet d'ajouter soit 0, soit 6, à chaque chiffre BCD, et donc de le corriger. La propagation des retenues d'un chiffre à l'autre est effectuée automatiquement par l'addition binaire de la constante. L'opération d'ajustement décimal calcule automatiquement la constante. Elle découpe l'opérande en ''nibbles'', vérifie si chaque ''nibble'' est supérieur ou égal à 10, puis détermine la valeur de chaque ''nibble'' de la constante finale. Par exemple, si je prends l'opérande 1001 1110, le ''nibble'' de poids faible déborde, alors que celui de poids fort non. La constante sera donc 0000 0110 : 0x06. Inversement, si le ''nibble'' de poids fort déborde et pas celui de poids faible, la constante sera alors 0x60. Et la constante est de 0x66 si les deux ''nibbles'' débordent, de 0x00 si aucun ne déborde.
Le circuit d’ajustement décimal est donc composé de trois étapes : deux étapes pour calculer la constante, et un circuit d'addition pour additionner cette constante au nombre de départ. La première étape découpe l'opérande en morceaux de 4 bits, en chiffres BCD, et vérifie si chacun d'entre eux vaut 10 ou plus. La seconde étape prend les résultats de la première étape, et les combine pour calculer la constante. Enfin, on trouve l'addition finale, qui était réalisée par un circuit d'addition utilisé à la fois pour l'ajustement décimal et l'addition binaire. La différence entre une addition normale et une opération d'ajustement décimal tient dans le fait que les deux premières étapes sont désactivées dans une addition normale.
[[File:Additionneur BCD parallèle.png|centre|vignette|upright=1.5|Additionneur BCD parallèle]]
===L'additionneur biquinaire===
Les entiers BCD ne sont qu'un des encodages hybrides entre décimal et binaire. L'encodage biquinaire est l'un d'entre eux et nous allons faire un rappel rapide à ce sujet. Pour simplifier, un chiffre encodé en biquinaire est composé de deux parties : un bit, couplé à une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits (0, 1, 2, 3 et 4). Le bit indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9.
Additionner deux nombres de biquinaire demande donc d'additionner deux parties quinaires encodées en ''one-hot'' et d'additionner deux bits. Mais attention : il faut tenir compte de la retenue de l'addition des parties quinaires. Et idéalement, il faut aussi tenir compte d'une retenue entrante, provenant de l'addition de la colonne de chiffres précédente. Toute la difficulté vient de la création de l'additionneur ''one-hot''. Heureusement, vu qu'il n'y a que 4-5 bits à additionner, il est souvent fabriqué à partir de sa table de vérité.
[[File:Additionneur bi-quinaire.png|centre|vignette|upright=2|Additionneur bi-quinaire]]
Un avantage du biquinaire est que le calcul du complément à 9 est très simple. Il faut pour cela : inverser la partie binaire avec une porte NON, puis inverser l'ordre des bits de la partie quinaire. Concrètement, le bit de poids faible devient le bit de poids fort, et ainsi de suite. Par exemple, une partie quinaire 01000 devient 00010, 10000 devient 00001, 00100 ne change pas, etc. Le tout peut se calculer avec une porte NON et 5 multiplexeurs.
===L'additionneur BCD avec calculs intermédiaires en biquinaire===
L'ordinateur IBM 1401, un ancien ''mainframe'' des années 60, utilisait un additionneur BCD un peu particulier. Les nombres étaient encodés en BCD dans la mémoire de l'ordinateur, mais les circuits de calcul utilisaient la représentation biquinaire. Lors d'un calcul, le processeur de l'ordinateur traduisait les chiffres BCD en représentation biquinaire, faisait une addition en biquinaire, avant de traduire le résultat en BCD normal.
Pour être précis, l'IBM 1401 utilisait une variante du biquinaire. L'encodage biquinaire de l'IBM 1401 est le suivant : la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre pair encodé par la partie quinaire. Si l'IBM 1401 utilisait cette variante du biquinaire, c'est car elle donnait des circuits de conversion BCD-biquinaire plus économes en portes logiques et plus rapides.
La partie binaire est le bit de poids faible du chiffre BCD, la partie biquinaire est calculée par un simple décodeur qui prend en entrée le chiffre BCD, amputé de son bit de poids faible. La traduction inverse demande d'utiliser un encodeur, à la place du décodeur. Par contre, le circuit d'addition biquinaire était plus compliqué du fait de la gestion des retenues. L'addition des parties binaires et quinaires se faisait en parallèle, dans deux additionneurs séparés. Cependant, l'addition des parties binaire fournit une retenue, qu'il faut prendre en compte. Pour cela, l'IBM 1401 disposait d'un troisième additionneur qui fournissait le résultat final, encodé en biquinaire.
[[File:Additionneur bi-quinaire de l'IBM 1401.png|centre|vignette|upright=2|Additionneur biquinaire de l'IBM 1401]]
Une implémentation moderne demanderait d'utiliser des portes ET combinées à des portes OU, le circuit pouvant être construit simplement à partir de sa table de vérité. Sur l'IBM 1401, le circuit était cependant différent, en raison de l'utilisation de OU câblés, des croisements de fils qui fonctionnent comme des portes OU, que nous n'avons pas encore vu pour le moment, mais qui seront détaillés dans quelques chapitres. Les OU câblés étaient utilisés pour simplifier le design du circuit, mais demandaient des portes logiques spécifiques, ce qui collait avec le fait que ce ''mainframe'' utilisait des transistors en Germanium. L'implémentation exacte est décrite dans cet article de blog, mais je ne recommande sa lecture qu'à ceux qui savent ce qu'est un OU câblé :
* [https://www.righto.com/2015/10/qui-binary-arithmetic-how-1960s-ibm.html Qui-binary arithmetic: how a 1960s IBM mainframe does math].
==Les débordements d'entier lors d'une addition/soustraction==
Les instructions arithmétiques manipulent des entiers codés sur un nombre fixe de bits, qui ne peuvent prendre leurs valeurs que dans un intervalle. Pour les nombres positifs, un ordinateur qui code ses entiers sur n bits pourra coder tous les entiers allant de 0 à <math>2^n -1</math>. Pour les nombres négatifs, l'intervalle est différent et dépend de la représentation utilisée. Dans le cas général, l'ordinateur peut coder les valeurs comprises de <math>N_\text{min}</math> à <math>N_\text{max}</math>. Si le résultat d'un calcul sort de cet intervalle, il ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math> , alors que la valeur basse vaut <math>N_\text{min} - 1</math> (avec <math>N_\text{max}</math> et <math>N_\text{min}</math> respectivement la plus grande et la plus petite valeur codable par l'ordinateur).
===La correction des débordements d'entier : l'arithmétique saturée===
Quand un débordement d'entier survient, tous les circuits de calcul ne procèdent pas de la même manière. Dans les grandes lignes, il y a deux réactions possibles : soit on corrige automatiquement le résultat du débordement, soit on ne fait rien et on se contente de détecter le débordement.
Si le débordement n'est pas corrigé automatiquement, le circuit ne conserve que les bits de poids faibles du résultat. Les bits en trop sont simplement ignorés. On dit qu'on utilise l''''arithmétique modulaire'''. Le problème avec ce genre d'arithmétique, c'est qu'une opération entre deux grands nombres peut donner un résultat très petit. Par exemple, si je dispose de registres 4 bits et que je souhaite faire l'addition 1111 + 0010 (ce qui donne 15 + 2), le résultat est censé être 10001 (17), ce qui est un résultat plus grand que la taille d'un registre. En conservant les 4 bits de poids faible, j’obtiens 0001 (1). En clair, un résultat très grand est transformé en un résultat très petit. Cela peut poser problèmes si on travaille uniquement avec des nombres positifs, mais c'est aussi utilisé pour coder des nombres en complément à deux.
D'autres circuits utilisent ce qu'on appelle l''''arithmétique saturée''' : si un calcul génère un débordement, on arrondi le résultat au plus grand entier supporté par le circuit. Les circuits capables de calculer en arithmétique saturée sont un peu plus complexes, vu qu'il faut rajouter des circuits pour corriger le résultat en cas de débordement. Il suffit généralement de rajouter un ''circuit de saturation'', qui prend en entrée le résultat et le corrige en cas de débordement. Ce circuit de saturation met la valeur maximale en sortie si un débordement survient, mais se contente de recopier le résultat du calcul sur sa sortie s'il n'y a pas de débordement. Typiquement, il est composé d'une couche de multiplexeurs, qui sélectionnent quelle valeur mettre sur la sortie : soit le résultat du calcul, soit le plus grand nombre entier géré par le processeur, soit le plus petit (pour les nombres négatifs/soustractions).
L'arithmétique saturée est utilisée pour les additions et soustractions, mais c'est plus rare pour les multiplications/divisions. Une des raisons est que le résultat d'une addition/soustraction prend un bit de plus que le résultat, là où les multiplications doublent le nombre de bits. Quand une addition déborde, le résultat réel est proche de la valeur maximale codable. mais quand une multiplication déborde, le résultat peut parfois valoir 200 à 60000 fois plus que la valeur maximale codable. Les calculs avec une valeur saturée/corrigée sont donc crédibles pour une suite d'additions, mais pas pour une suite de multiplications.
===La détection des débordements entiers===
Quand un débordement d'entier a eu lieu, il vaut mieux que l'additionneur prévienne ! Pour cela, l'additionneur a une '''sortie de débordement''', parfois nommée ''Overflow'', dont la valeur indique si l'addition a généré un débordement d'entier ou non. Reste que détecter un débordement ne se fait pas de la même manière selon que l'on parle d'un additionneur non-signé ou signé.
Pour les additionneur non-signés, l'additionneur calcule un bit de plus que ce qui est supporté par l'ordinateur. Par exemple, un additionneur 32 bits fournit un résultat sur 33 bits, un débordement d'entier a lieu quand le 33ème bit est à 1. Précisément, la sortie de débordement n'est autre que la retenue finale, celle fournie par le dernier additionneur complet. Le seul type de débordement possible est un débordement par le haut, où le résultat dépasse la valeur maximale. Avec l'arithmétique saturée, le circuit de saturation consiste en une seule couche de multiplexeurs, voire en un circuit de mise à la valeur maximale tel que vu dans le chapitre sur les opérations bits à bits.
[[File:Gestion des débordements d'entiers.png|centre|vignette|upright=2.5|Gestion des débordements d'entiers lors d'une addition non-signée.]]
Pour les additionneurs non-signés, la gestion des débordements d'entiers dépend fortement de la représentation signée. Nous allons étudier le cas du complément à deux. Si vous vous rappelez le chapitre 1, les calculs sur des nombres en complètement à deux utilisent les règles de l'arithmétique modulaire, c'est une condition nécessaire. À priori, on peut penser que dans ces conditions, les débordements d'entiers sont une chose parfaitement normale, qui nous permet d'avoir des résultats corrects. Néanmoins, certains débordements d'entiers peuvent survenir malgré tout et produire des bugs assez ennuyeux.
Si l'on tient en compte les règles du complément à deux, on sait que le bit de poids fort (le plus à gauche) permet de déterminer si le nombre est positif ou négatif : il indique le signe du nombre. Tout se passe comme si les entiers en complément à deux étaient codés sur un bit de moins, et avaient leur longueur amputé du bit de poids fort. Si le résultat d'un calcul écrase le bit de poids fort, il y a un débordement d'entiers. Il existe une règle simple qui permet de détecter ces débordements d'entiers. L'addition de deux nombres positifs ne peut pas être un nombre négatif. Si on additionne deux nombres dont le bit de signe est à 0 et que le bit de signe du résultat est à 1, on est en face d'un débordement d'entiers. Même chose pour deux nombres négatifs : le résultat de l'addition ne peut pas être positif. On peut résumer cela en une phrase : si deux nombres de même signe sont ajoutés, un débordement a lieu quand le bit du signe du résultat a le signe opposé.
Modifier les circuits d'au-dessus pour qu'ils détectent les débordements en complément à deux est simple comme bonjour : il suffit créer un petit circuit combinatoire qui prenne en entrée les bits de signe des opérandes et du résultat, et qui fasse le calcul de l'indicateur de débordements. Si l'on rédige sa table de vérité, on doit se retrouver avec la table suivante :
{|class="wikitable"
|-
!Entrées
!Sortie
|-
|000||0
|-
|001||1
|-
|010||0
|-
|011||0
|-
|100||0
|-
|101||0
|-
|110||1
|-
|111||0
|}
L'équation de ce circuit est la suivante, avec <math>S_a</math> et <math>S_b</math> les signes des deux opérandes, et <math>C_i</math> la retenue de la colonne précédente :
: <math> ( S_a . S_b . \overline{C_i} ) + ( \overline{S_a} . \overline{S_b} . C_i )</math>
En simplifiant, on obtient alors :
: <math>( S_a . S_b ) \oplus C_i</math>
Or, il se trouve que <math>S_a . S_b</math> est tout simplement la retenue en sortie du dernier additionneur, que nous noterons <math>C_o</math>. On trouve donc :
: <math>C_o \oplus C_i</math>
Il suffit donc de faire un XOR entre la dernière retenue et la précédente pour obtenir le bit de débordement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de décalage et de rotation
| prevText=Les circuits de décalage et de rotation
| next=Les circuits de comparaison
| nextText=Les circuits de comparaison
}}
</noinclude>
hnnf7n2708tqdlviaa0ksqp4uk2u4pk
768660
768659
2026-06-25T19:46:31Z
Mewtow
31375
/* Le calcul parallèle de la retenue */
768660
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir les circuits capables de faire une addition ou une soustraction, ainsi que quelques circuits spécialisés. Précisons cependant que les fabricants de processeurs travaillent d'arrache-pied pour trouver des moyens de rendre ces circuits de calcul plus rapides et plus économes en énergie. Autant vous dire que les circuits que vous allez voir sont vraiment des circuits qui font pâle figure comparé à ce que l'on peut trouver dans un vrai processeur commercial !
==Les circuits pour additionner 2 ou 3 bits==
L'addition se fait en binaire de la même manière qu'en décimal. On additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. La soustraction fonctionne sur le même principe, sur le même modèle qu'en décimal.
[[File:Binary Addition Demonstration.svg|centre|vignette|Exemple d'addition en binaire.]]
En clair, additionner deux nombres demande d'additionner 2 bits et une retenue sur chaque colonne, et de propager les retenues d'une colonne à l'autre. La propagation des retenues est quelque chose de simple en apparence, mais qui est sujet à des optimisations extraordinairement nombreuses. Aussi, pour simplifier l'exposition, nous allons voir comment gérer une colonne avant de voir comment sont propagées les retenues.
[[File:Full Adder Block.svg|vignette|upright=1|class=transparent|Additionneur complet.]]
Si on effectue une addition en colonne, on doit additionner les deux bits sur la colonne, mais aussi additionner une éventuelle retenue. Il faut donc créer un circuit qui additionne trois bits : deux bits de données, plus une retenue. Ce circuit qui additionne trois bits est appelé un '''additionneur complet'''. Il fournit en sortie deux bits : un bit de somme et une retenue sortante. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|-
||0||0||0|| ||0||0
|-
||0||0||1|| ||0||1
|-
||0||1||0|| ||0||1
|-
||0||1||1|| ||1||0
|-
||1||0||0|| ||0||1
|-
||1||0||1|| ||1||0
|-
||1||1||0|| ||1||0
|-
||1||1||1|| ||1||1
|}
Il est possible d'utiliser un tableau de Karnaugh pour traduire la table de vérité, mais elle donne un résultat assez compliqué. La retenue et le bit de somme sont calculés à part. La retenue est calculée avec trois portes ET et une porte OU. Le bit de somme est calculé lui avec 4 portes ET, une porte OU, et trois portes NON.
[[File:Full Adler with 3 entries.png|centre|vignette|upright=2|Additionneur complet, fabriqué à partir de la table de vérité.]]
D'autres méthodes donnent des résultats plus compréhensibles. Nous allons les voir dans la suite de cette section.
===L'additionneur complet basé sur des demi-additionneurs===
[[File:1-bit half-adder.svg|class=transparent|vignette|Demi-addtionneur.]]
Nous avons déjà vu comment additionner deux bits dans le chapitre sur les incrémenteurs, mais quelques rappels ne seront pas de trop. Le '''demi-additionneur''' est un circuit qui additionne deux bits. Il implémente la table d'addition, qui est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Mais un demi-additionneur n'additionne que deux bits, il manque de quoi additionner la retenue. Une solution pour cela est d'enchaîner deux demi-additionneurs : un qui additionne les deux bits de données, et un second qui additionne la retenue au résultat du précédent. La retenue finale se calcule en combinant les sorties de retenue des deux demi-additionneurs, avec une porte OU. Le résultat est un additionneur complet fabriqué avec deux demi-additionneurs. Pour vous en convaincre, établissez la table de vérité de ce circuit, vous verrez que ça marche.
[[File:Full Adder Blocks.svg|centre|vignette|upright=2|Additionneur complet fabriqué avec deux demi-additionneurs.]]
[[File:Half-adder.svg|vignette|upright=1|Circuit d'un demi-additionneur naïf.]]
L'implémentation naïve d'un demi-additionneur utilise une porte XOR et une porte ET, comme illustré ci-contre. Le circuit s'obtient en faisant uen traduction littérale de la table de vérité du circuit. Si je dis qu'elle est naïve, c'est car il est possible de l'optimiser, de manière à éliminer des redondances cachées. Mais nous avions déjà vu cela dans le chapitre sur les incrémenteurs, aussi laissons cela pour plus tard.
En combinant deux demi-additionneurs de ce type, on obtient l'additionneur complet suivant. Les deux sorties de retenue des demi-additionneurs sont combinées avec une porte OU, pour calculer la retenue finale.
{| class="flexible"
|[[File:Full Adder Modules.svg|class=transparent|Composition d'un additionneur complet. On voit bien que celui-ci est composé de deux demi-additionneurs, en rouge et en bleu, auxquels on a ajouté une porte OU pour calculer la retenue finale.]]
|[[File:Full-adder.svg|300px|class=transparent|Circuit d'un additionneur complet.]]
|}
Le fait de combiner les deux retenues avec une porte OU n'est pas ce qu'il y a de plus intuitif. La table de vérité nous dit que ça fonctionne, mais on comprend mal pourquoi. Mais tout devient plus clair quand on sait que les deux retenues ne peuvent pas être à 1 en même temps. Même dans l'addition 1 + 1 + 1, seule une retenue est à 1. Soit l'addition des deux bits d'opérande donne naissance à une retenue, soit c'est l'addition ''résultat + retenue entrante''. La retenue sortant est donc à 1 quand une des deux addition donne une retenue. D'où l'usage d'une porte OU. Remarquez qu'une porte XOR donne le même résultat, vu que le cas où les deux retenues sont à 1 n'est jamais rencontré.
===La propagation et la génération des retenues===
L'additionneur complet que nous allons voir dans cette section sert à introduire les concepts de propagation, génération et absorption de retenue. Il s'agit de concepts très importants quand on étudie les additionneurs. l'idée part d'un principe très simple : la retenue sortante dépend de la retenue d'entrée. La relation entre les deux se résume à trois cas, qui dépendent de la valeur des deux bits additionnés, nommés A et B.
* Dans le premier cas, la retenue entrante est égale à la retenue sortante. On dit que la retenue entrante est propagée par l'additionneur.
* Dans le second cas, la retenue est forcée à 0 : la retenue sortante vaut 0, peu importe la valeur de la retenue entrante.
* Dans le troisième cas, la retenue est forcée à 1 : la retenue entrante vaut 1, peu importe la valeur de la retenue entrante.
[[File:Signaux P et G fournis par un demi-additionneur.png|vignette|Signaux P et G fournis par un demi-additionneur]]
Maintenant, créons un circuit qui nous dise si une retenue est propagée ou générée. Il a deux bits de sortie, nommés P et G : P pour ''Propagate'', G pour ''Generate''. Le bit P est à 1 si la retenue entrante est propagée, il est à 0 sinon. Le bit G est à 1 si une retenue est générée, à 0 sinon. Une retenue est considérée comme absorbée si elle n'est pas ni propagée ni générée, pas besoin d'un troisième bit pour gérer ce cas.
Il se trouve que ce circuit n'est autre qu'un demi-additionneur ! Pour vous en rendre compte, regardez la table de vérité d'un additionneur complet, illustrée ci-dessous. Lorsque les deux bits d'opérande sont à 0, la retenue sortante vaut toujours 0. Si ils sont tous deux à 1, alors la retenue sortante vaut 1. S'ils sont différents, alors retenues sortante et entrante sont égales. Le bit P est donc généré par une simple porte XOR. Quant au bit G, il est à 1 si les deux bits d'opérandes sont à 1, ce qui correspond à une porte ET. Il se trouve que ces deux portes forment un demi-additionneur !
{|class="wikitable"
|-
! Opérande 1 !! Opérande 2 !! Retenue entrante !! !! Retenue sortante
|- class="f_rouge"
||0||0||0|| ||0
|- class="f_rouge"
||0||0||1|| ||0
|- class="f_bleu"
||0||1||0|| ||0
|- class="f_bleu"
||0||1||1|| ||1
|- class="f_bleu"
||1||0||0|| ||0
|- class="f_bleu"
||1||0||1|| ||1
|- class="f_vert"
||1||1||0|| ||1
|- class="f_vert"
||1||1||1|| ||1
|}
Ensuite, créons un circuit qui prend ces deux sorties P et G, et calcule la retenue sortante en fonction. Le circuit en question a trois entrées : la retenue entrante, les deux bits P et G. La retenue sortante vaut 1 soit si une retenue est générée, soit si la retenue entrante est propagée et qu'elle vaut 1. En notant <math>{C_{in}}</math> et <math>{C_{out}}</math> les retenues entrantes et sortante, on a :
: <math>{C_{out}} = G + \left( P . C_{in} \right)</math>
Le circuit est donc composé d'une porte OU et d'une porte ET. En combinant un demi-additionneur avec le circuit de calcul de retenue sortante vu plus haut, on a :
[[File:Additionneur complet avec propagation et génération de retenue.png|class=transparent|centre|vignette|upright=2|Additionneur complet avec propagation et génération de retenue.]]
Certes, on retombe sur le circuit vu plus haut. Mais il est possible de remplacer le circuit en orange par un autre. Par exemple, il est possible de le remplacer par multiplexeur, qui choisit entre la retenue générée et la retenue entrante (propagée). Le choix se fait selon la valeur du bit P, qui chosiit entre propager la retenue et la générer.
[[File:Additionneur crée avec un multiplexeur.png|centre|vignette|upright=1.5|Additionneur crée avec un multiplexeur]]
Le circuit exact est illustré ci-dessous. Il semble utiliser plus de portes logiques que l'additionneur complet précédent. Cependant, nous verrons dans quelques chapitres qu'il est possible d'implémenter un multiplexeur avec seulement 6 transistors, voire moins ! L'implémentation utilise des portes à transmission, mais nous en reparlerons dans le chapitre sur les transistors, quand nous verrons les additionneurs à ''Manchester Carry Chain''. Au passage, une variante de ce circuit a été utilisée dans le processeur processeur 8086 d'Intel, comme on le verra dans le chapitre suivant.
[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
[[File:FulladderPG.png|vignette|upright=1|Additionneur complet avec deux sorties P et G.]]
Pour finir, sachez qu'il existe des additionneurs qui fournissent : le bit de résultat, deux sorties P et G qui indiquent si l'addition propage ou génère une retenue. Il ne s'agit pas d'additionneurs complets, car il manque de quoi calculer la retenue sortante. De tels additionneurs seront appelés des '''additionneurs partiels'''. De tels additionneurs sont utilisés dans certains additionneurs pour gagner en performance.
Enfin, il existe aussi ce que j'ai décidé d'appeler des '''additionneurs P/G''', qui sont des additionneurs complets auxquels on a ajouté deux sorties P et G, en plus de la sortie de retenue. Leur circuit est le même que celui d'un additionneur complet, auquel on a ajouté deux fils. Vu que ces deux sorties sont fournies par le premier demi-additionneur, ajouter ces deux sorties demande d'ajouter des fils, pas de portes logiques. De tels additionneurs sont aussi utilisés dans certains additionneurs pour gagner en performance.
===L'additionneur complet basé sur une porte à majorité===
Maintenant, voyons une dernière implémentation possible de l'additionneur complet. Mais avant de voir comment l'additionneur est implémenté, nous allons devoir faire un petit focus sur la retenue sortante. Reprenons la table de vérité, et regardons ce qu'il en est pour la retenue sortante uniquement.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante
|-
||0||0||0|| ||0
|-
||0||0||1|| ||0
|-
||0||1||0|| ||0
|-
||0||1||1|| ||1
|-
||1||0||0|| ||0
|-
||1||0||1|| ||1
|-
||1||1||0|| ||1
|-
||1||1||1|| ||1
|}
Vous remarquerez que la retenue sortante est égale au bit majoritaire, parmi les trois bits d'opérande. Si deux ou trois bits sont à 1, la retenue sortante vaut 1. Et réciproquement avec 0. Le circuit de calcul de la retenue peut donc être remplacé par une '''porte à majorité'''.
[[File:Additionneur crée avec une porte à majorité.png|centre|vignette|upright=2|Additionneur crée avec une porte à majorité]]
Mais une porte à majorité est assez gourmande en circuit, ce qui fait qu'un additionneur pareil ne serait pas pratique, ni utile. Du moins, ce serait le cas s'il n'y avait pas une possibilité d'optimisation extrêmement intéressante. Il est possible de calculer le 'bit de somme' à partir de la retenue sortante ! En effet, le bit de somme est l'inverse de la retenue sortante, sauf dans deux cas : les trois bits d'entrée sont à 0, où ils sont tous à 1. Le bit de somme vaut 0 dans le premier cas, 1 dans le second cas.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|- class="f_bleu"
||0||0||0|| ||0||0
|- class="f_vert"
||0||0||1|| ||0||1
|- class="f_vert"
||0||1||0|| ||0||1
|- class="f_vert"
||0||1||1|| ||1||0
|- class="f_vert"
||1||0||0|| ||0||1
|- class="f_vert"
||1||0||1|| ||1||0
|- class="f_vert"
||1||1||0|| ||1||0
|- class="f_rouge"
||1||1||1|| ||1||1
|}
En clair, 6 lignes de la table de vérité sur 8 peuvent se calculer avec une porte à majorité, le reste demandant quelques portes logiques pour faire la correction. L'implémentation en circuit demande donc trois choses :
* de calculer la retenue sortante et de quoi l'inverser ;
* un circuit qui vérifie si tous les bits opérande valent 0 : une porte NOR ou OU fait l'affaire ;
* un circuit qui vérifie s'ils valent tous 1, à savoir une porte ET.
Ensuite, on combine le résultat des trois circuits précédents pour obtenir le résultat final. La combinaison est le fait du circuit en jaune dans le schéma ci-dessous.
[[File:Full adder basé sur une modification de la retenue.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue]]
Le circuit en jaune est assez simple. Quand la porte ET sort un 1, la sortie doit être forcée à 1. Par contre, quand la porte OU sort un zéro, la sortie doit être forcée à 0. Il suffit donc d'enchainer un circuit de mise à 1 et un circuit de mise à 0. Nous avons vu dans le chapitre sur les opérations de masquage qu'il s'agit respectivement d'une porte OU et d'une porte ET.
[[File:Full adder basé sur une modification de la retenue interne, au complet.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue interne, au complet]]
Divers processeurs utilisaient des additionneurs complets de ce type : le fameux Z80, l'Intel 4004 et de l'Intel 8008 sont les exemples les plus notables. Pourtant, on pourrait s'étonner qu'un tel circuit ait existé. Il utilise beaucoup de portes logiques, a une profondeur logique supérieure : il n'a rien d'avantageux. Sauf qu'il était utilisé sur d'anciens processeurs, qui utilisaient la technologie dite TTL, différente de la technologie CMOS des transistors modernes. Et avec la technologie TTL, il est possible de fusionner plusieurs portes logiques ET et NOR en une seule porte logique ET/OU/NON ! Un additionneur complet construit ainsi ne prenait que deux portes logiques : une pour le calcul de la retenue sortante, une autre pour le reste du circuit.
==L'addition non signée==
Voyons maintenant un circuit capable d'additionner deux nombres entiers: l''''additionneur'''. Dans la version qu'on va voir, ce circuit manipulera des nombres strictement positifs. L'addition des nombres codés en complètement à deux sera vu dans une section ultérieure.
===L'additionneur série===
Il est possible d'additionner deux nombres bit par bit, gr$ace à un additionneur complet associé à plusieurs registres à décalages. Les opérandes sont placées chacune dans un registre à décalage, et l'additionneur complet utilise les bits sortants de ces registres à décalage. Le bit du résultat est envoyé au registre à décalage pour le résultat. La retenue de l'addition est stockée dans une bascule de 1 bit, en attente du prochain cycle d'horloge. Un tel additionneur est appelé un '''additionneur série'''. Il a été utilisé sur d'anciens ordinateurs dans les années 50-60, aussi bien des prototypes que des ordinateurs commerciaux.
[[File:Additionneur série.jpg|centre|vignette|upright=2.5|Additionneur série.]]
===L'additionneur à propagation de retenue===
L''''additionneur à propagation de retenue''' est aussi appelé l'additionneur ''ripple carry'', c'est son nom anglais. J'utiliserais occasionnellement le nom anglais dans ce qui suit, car celui-ci est plus court. Ilutilise un additionneur complet pour chaque colonne de l'addition. Pour gérer les retenues, il suffit ainsi de câbler des additionneurs complets les uns à la suite des autres, chacun envoyant sa retenue à la colonne suivante.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Notons la présence de la retenue sortante, qui est utilisée pour détecter les débordements d'entier, ainsi que pour d'autres opérations. Le bit de retenue final est souvent stocké dans un registre spécial du processeur (généralement appelé ''carry flag'').
Notez aussi, à droite du schéma précédent, l'entrée de retenue <math>\text{R}_\text{Entrée}</math>. Elle est très utile pour l'implémentation de certaines opérations comme l'inversion de signe, la soustraction, l'incrémentation, etc. Certains processeurs sont capables de faire une opération appelée ADC, ADDC ou autre nom signifiant ''Addition with Carry'', qui permet de faire le calcul A + B + Retenue (la retenue en question est la retenue sortante de l'addition précédente, stockée dans le registre ''carry flag''). Son utilité principale est de permettre des additions d'entiers plus grands que ceux supportés par le processeur. Par exemple, cela permet de faire des additions d'entiers 32 bits sur un processeur 16 bits.
[[File:Adder Network 3 Bit Sum Delay 003.svg|vignette|upright=0.5|Propagation de retenue dans l'additionneur.]]
Cet additionneur utilise très peu de portes logiques, ce qui fait qu'il était utilisé sur certains processeurs 8 et 16 bits, dont le budget en portes logiques était limité. Bien que très simple, cet additionneur est cependant peu performant. Son temps de calcul est proportionnel à la taille des opérandes. Par exemple, additionner deux opérandes de 32 bits prendra deux fois plus de temps que pour des opérandes de 16 bits. La raison est que les retenues doivent se propager à travers le circuit, du premier additionneur jusqu'au dernier.
L'addition étant une opération fréquente, d'autres additionneurs ont été inventés. Ils utilisent des optimisations qui utilisent plus de circuits pour gagner en rapidité. Mais avant de voir ces optimisations, nous allons voir s'il est possible d'optimiser les additionneurs à propagation de retenue.
===L'additionneur à saut de retenue===
L'additionneur à propagation de retenue peut être optimisé de deux manières. La première accélère la propagation de la retenue, en agissant au niveau des additionneurs complets. La ''Manchester carry chain'' est une optimisation de ce type, mais on ne peut pas encore expliquer à ce stade du cours, car elle optimisait les additionneurs complets au niveau des transistors eux-mêmes. Elle a été utilisée sur de nombreux processeurs connus, comme le 8086 d'Intel ou le Z80.
La seconde manière rend l'addition plus rapide dans certains cas. Le résultat est un additionneur dont le temps de calcul est variable. Le calcul prendra quelques cycles d'horloges avec certains opérandes, mais d'autres opérandes ne verront aucune amélioration. Il n'améliore pas le pire des cas, dans lequel la retenue doit être propagée du début à la fin, du bit de poids faible au bit de poids fort. Mais dans d'autres cas, il permet d'avoir le résultat en avance. L'optimisation en question donne l''''additionneur à saut de retenue''' (''carry-skip adder'').
Il est composé de blocs qui additionnent 4/5 bits, rarement plus. La taille d'un bloc varie d'un additionneur à l'autre, elle peut même varier dans l'additionneur, mais nous allons prendre un exemple où les blocs font 4 bits. L'idée est de détecter précocement si la retenue entrante est propagée à travers tout le bloc. Si la retenue est propagée dans tous le bloc, on peut directement l'envoyer sur la sortie de retenue, la retenue saute le bloc entier. Dans le cas contraire, on doit calculer la retenue normalement. Le choix entre les deux est le fait d'un multiplexeur. Toute la difficulté est de savoir comment commander le multiplexeur.
[[File:Carry skip adder, principe de base.png|centre|vignette|upright=2.5|Carry skip adder : principe de base]]
Pour savoir si une retenue est propagée dans un bloc entier, il faut utiliser des additionneurs complets, qui ont en plus une sortie P indiquant que l'additionneur propage la retenue. Le bloc propage une retenue si chaque additionneur complet propage la retenue, donc s'ils ont tous leur sortie P à 1. Un simple ET logique suffit à déterminer si c'est le cas. Le signal de commande du multiplexeur est donc un vulgaire ET entre toutes les sorties P des additionneurs complets du bloc.
[[File:Additionneur à saut de retenue.png|centre|vignette|upright=2.5|Calcul de la commande du MUX.]]
Voici ce que ça donne pour un bloc de 4 bits :
[[File:CSAdder4Bit.svg|centre|vignette|upright=1.5|Additionneur ''carry skip'' : bloc de 4 bits.]]
L'additionneur à saut de retenue est construit en assemblant plusieurs blocs de ce type.
[[File:BCSAdder16Bit.svg|centre|vignette|upright=3|Additionneur à saut de retenue.]]
===L'additionneur à sélection de retenue===
L''''additionneur à sélection de retenue''' utilise aussi des blocs, comme les additionneurs précédents. L'addition se fait en deux versions : une avec la retenue du bloc précédent valant zéro, et une autre version avec la retenue du bloc précédent valant 1. Il suffira alors de choisir le bon résultat avec un multiplexeur, une fois cette retenue connue. On gagne ainsi du temps en calculant à l'avance les valeurs de certains bits du résultat, sans connaître la valeur de la retenue. Petit détail : sur certains additionneurs à sélection de retenue, les blocs de base n'ont pas la même taille. Cela permet de tenir compte des temps de propagation des retenues entre les blocs.
[[File:Additionneur à sélection de retenue.png|centre|vignette|upright=1.5|Additionneur à sélection de retenue avec seulement deux blocs.]]
Dans les exemples du dessus, chaque sous-additionneur étaient des additionneurs à propagation de retenue. Mais ce n'est pas une obligation, et tout autre type d’additionneur peut être utilisé. Par exemple, on peut faire en sorte que les sous-additionneurs soient eux-mêmes des additionneurs à sélection de retenue, et poursuivre ainsi de suite, récursivement. On obtient alors un '''additionneur à somme conditionnelle''', plus rapide que l'additionneur à sélection de retenue, mais qui utilise beaucoup plus de portes logiques.
===Les additionneurs à anticipation de retenue===
Les '''additionneurs à anticipation de retenue''' calculent chaque retenue sans avoir à propager les retenues précédentes. Au lieu de calculer les retenues une par une, ils calculent toutes les retenues en parallèle, à partir des bits des opérandes. Une fois les retenues pré-calculées, il suffit de les additionner avec les deux bits adéquats, pour obtenir le résultat. Ces additionneurs sont composés de deux parties :
* un circuit qui pré-calcule les retenues, sans les propager, directement à partir des opérandes ;
* d'une couche d'additionneurs complets, qui additionnent chacun deux bits d'opérande et la retenue pré-calculée.
[[File:Additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Additionneur à anticipation de retenue.]]
Le calcul des retenues se fait en deux étapes. La première détermine si les retenues sont propagées ou générées, pour chaque colonne. Elle détermine, pour chaque colonne, les bits P et G qui indiquent respectivement : qu'une retenue a été générée sur cette colonne, que la colonne propage la retenue précédente. La seconde étape utilise ces signaux P et G pour déterminer la retenue sur chaque colonne. Le circuit de calcul des retenues est donc composé de deux parties : une couche de demi-additionneurs pour générer les signaux P et G, un circuit de calcul des retenues proprement dit.
[[File:Circuit complet d'un additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Circuit complet d'un additionneur à anticipation de retenue.]]
Pour rappel, la retenue de la colonne i, notée <math>C_i</math> est égale à :
: <math>G_i + (P_i . C_{i-1})</math>, avec G le signal de génération de retenue, P le signal de propagation de retenue.
Si on utilisait cette formule sans trop réfléchir, on retomberait sur un additionneur à propagation de retenue inutilement compliqué. L'astuce des additionneurs à anticipation de retenue consiste à remplacer le terme Ci−1 par sa valeur calculée avant. Par exemple, je prends un additionneur 4 bits. Je dispose de deux nombres A et B, contenant chacun 4 bits : A3, A2, A1, et A0 pour le nombre A, et B3, B2, B1, et B0 pour le nombre B. Si j'effectue les remplacements, j'obtiens les formules suivantes :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Ces formules nous permettent de déduire la valeur d'une retenue directement : il reste alors à créer un circuit qui implémente ces formules, et le tour est joué.
[[File:Four bit adder with carry lookahead.svg|centre|vignette|upright=1.5|Additionneur à anticipation de retenue de 4 bits.]]
Plus haut, j'ai dit que le circuit était composé de trois couches : une couche de demi-additionneur pour les signaux P et G ; un circuit d'anticipation de retenue, puis l'addition finale. Mais les deux bits d'opérandes sont déjà additionnés dans la couche de demi-additionneur, autant réutiliser le résultat pour l'additionner à la retenue. Pour éliminer toute redondance de ce type, il est possible d'utiliser des additionneurs partiels (des additionneurs où la sortie de retenue a été remplacée par deux sorties P et G). L'additionneur final est alors composé de plusieurs additionneurs complets de ce type, couplé à un circuit d'anticipation de retenue.
[[File:Cla4bits.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue de 4 bits.]]
Les additionneurs à anticipation de retenue sont plus rapides que les additionneurs à propagation de retenue. La raison est que les retenues sont calculées rapidement, dans un temps plus petit qu'en propageant la retenue. Pour rentrer dans les détails mathématiques, le temps de calcul n'est pas proportionnel au nombre de bits, mais proportionnel au logarithme de ce nombre de bits. Par contre, le gain en temps de calcul s'accompagne d'un cout en prote logique conséquent. Le circuit qui calcule les retenues anticipées utilise beaucoup de portes logiques.
La conséquence est qu'utiliser un additionneur à anticipation de retenue est une solution pratique pour additionner 4 ou 8 bits. Mais pour des opérandes plus grandes, de 16 ou 32 bits, cela utiliserait trop de portes logiques. Une partie de ce cout en portes logiques est cependant lié à des redondances. Si on analyse deux retenues différentes, à des colonnes différentes. Les circuits qui calculent ces deux retenues seront différents, mais ils contiendront des sous-circuits identiques. Pour vous en rendre compte, reprenons les quatre équations obtenues précédemment et essayez de trouver des redondances :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Vous devriez trouver :
* le terme ''P0 · C0'' dans toutes les lignes ;
* les termes ''P1 · P0 · C0'' et ''P1 · P0'' dans les trois dernières lignes ;
* le terme ''P2 · P1 · P0 · C0'' ; ''P2 · P1 · G0'' et ''P2 · G1'' dans les deux dernières lignes.
===Les additionneurs à calcul parallèle de préfixe===
Il existe plusieurs manières d'exploiter de telles redondances, exploitées dans les '''additionneurs à calcul parallèle de préfixe'''. L'optimisation apportée est de générer des signaux ''propagate'' et ''generate'' pour des groupes de 2, 3, 4, ..., N colonnes. Par exemple, il est possible de générer un signal ''P 0 vers 3'', qui indique si la retenue de la première colonne est propagée jusqu'à la 4ème colonne. Un autre exemple est un signal qui indique qu'une retenue a été générée entre les colonnes 4 à 7 génèrent une retenue ou non. En clair, les signaux P et G ont maintenant un intervalle, qui précise de quelle colonne vers quelle colonne se fait la propagation, ou entre quelles colonnes se fait la génération.
Il est possible de combiner les signaux P et G de deux groupes de colonne, s'ils sont contiguës (ils peuvent aussi se recouvrir). Par exemple, il est possible de calculer les bits P et G pour les colonnes 0 à 10, à partir des deux signaux P/G des colonnes 0-4 et 5-10. Pour cela, les équations sont assez simples. Si je prends deux groupes nommés A et B, avec A pour les colonnes de poids fort et B celles de poids faible, on a :
: <math>P_{sortie} = P_A . P_B</math>
: <math>G_{sortie} = G_A . P_A . G_B</math>
Pour les bits des colonnes 2, 4, 8, 16, 32, 64, et autres, le calcul est simple. L'idée est de grouper les colonnes par groupes de 2. Puis, on calcule chaque groupe de 4 bits à partir de deux groupes de 2. Puis les groupes de 8 bits sont calculés à partir de deux groupes de 4 bits, et ainsi de suite pour toute puissance de deux.
[[File:Squelette d'un additionneur à calcul parallèle de préfixe.jpg|centre|vignette|upright=2|Squelette d'un additionneur à calcul parallèle de préfixe]]
Les seules difficultés surviennent pour les colonnes dont le nombre n'est pas une puissance de deux. Il y a plusieurs manières pour subdiviser ces colonnes en intervalles plus petits et combiner le tout. Et elles donnent chacune des additionneurs différents. Ils portent les noms d'''additionneur de Ladner-Fisher'', d'''additionneur de Brent-Kung'', d'''additionneur de Kogge-Stone'', etc. Dans le cas général, l'additionneur de Kogge-Stone est un des plus rapide, mais aussi un des plus gourmand en circuits, alors que c'est l'inverse pour l'additionneur de Brent-Kung. Les deux additionneurs sont illustrés ci-dessous, pour des opérandes de 8 bits. Dans ceux-ci, chaque losange calculent les bits P et G, à partir des signaux P et G de deux groupes précédents.
{|
|[[File:Brent-kung-8-bit.png|vignette|Brent-kung-8-bit]]
|[[File:Kogge-stone-8-bit.png|vignette|upright=1.2|Kogge-stone-8-bit]]
|}
===Les additionneurs à anticipation de retenue multi-niveau===
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Dans cette section, nous allons voir des additionneurs conçus en assemblant des additionneurs plus simples, qui additionnent environ 4 à 5 bits, parfois plus, parfois moins. Ces additionneurs simples seront nommés ''blocs'' dans ce qui suit, et l'un d'entre eux est illustré ci-contre. Chaque bloc prend en entrée un morceau des deux opérandes à additionner, mais aussi une retenue d'entrée.
Les blocs sont tous identiques dans le cas le plus simple, mais il est possible d'utiliser des blocs de taille variable. Par exemple, le premier bloc peut avoir des opérandes de 6 bits, le second des opérandes de 7 bits, etc. Faire ainsi permet de gagner un petit peu en performances, si la taille de chaque bloc est bien choisie. La raison est une question de temps de propagation des retenues. La retenue met plus de temps à se propager à travers 8 blocs qu'à travers 4, ce qui prend plus de temps qu'à travers 2 blocs, etc. En tenir compte fait que la taille des blocs tend à augmenter ou diminuer quand on se rapproche des bits de poids fort.
Il est possible d'utiliser l'anticipation de retenue entre deux blocs. Par exemple, il est possible de créer un additionneur 16 bits en utilisant 4 additionneurs/blocs de 4 bits. L'additionneur utilisé peut être un additionneur à propagation de retenue, ou à anticipation de retenue, peu importe.
Pour cela, les additionneurs de 4 bits doivent fournir deux sorties P et G. Les deux sorties indiquent que le bloc soit a généré une retenue, soit a propagé la retenue entrante. Par propagé, on veut dire que la retenue entrante du bloc s'est retrouvée sur la sortie de retenue du bloc. C'est le cas si les 4 colonnes propagent la retenue, ce qui signifie que :
: <math>P_\text{4 bits} = P_0 \cdot P_1 \cdot P_2 \cdot P_3</math>
Pour la génération, cela veut dire qu'une colonne a générée une retenue, qui a été propagée jusqu'à la sortie. En clair, pour chaque colonne, il faut que le G = 1, et que les signaux P des colonnes soient à 1. Pour les 4 colonnes, cela donne :
: <math>G_\text{4 bits} = G_3 + G_2 \cdot P_3 + G_1 \cdot P_3 \cdot P_2 + G_0 \cdot P_3 \cdot P_2 \cdot P_1</math>
[[File:Cla4bitsPG.png|centre|vignette|upright=2|Additionneur à anticipation de retenue de 4 bits.]]
Pour combiner 4 blocs de 4 bits, on devra donc combiner 4 bits P et G, avec un circuit d'anticipation de retenue.
[[File:16-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 16 bits.]]
Il est même possible d'aller plus loin et de combiner 4 additionneurs précédents pour obtenir un additionneur 64 bits.
[[File:64-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 64 bits.]]
Le résultat est un additionneur à plusieurs niveaux d’anticipation de retenue. Une anticipation de retenue dans un blocs de 4 bits (facultative), entre blocs de 4 bits, entre blocs de 16 bits. La même logique peut être utilisée avec des blocs de taille différente de 4, 16 et 64 bits.
===Le calcul parallèle de la retenue===
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Utiliser de l'anticipation de retenue entre blocs utilise beaucoup de circuits. Aussi, une autre option est d'utiliser la propagation de retenue entre blocs. L'anticipation de retenue est utilisée à l'intérieur des blocs, pas entre eux. Par exemple, on peut utiliser 4 additionneurs à anticipation de retenue de 4 bits, et propager les retenues entre eux. Le premier bloc calcule une retenue, qui est propagée au second bloc. Le second bloc calcule lui aussi une retenue, qui est propagée au troisième bloc, etc.
[[File:4008 Functional Diagram.svg|vignette|upright=1|4008 Functional Diagram]]
Le gain en performance est significatif, mais il est possible de faire presque aussi rapide, tout en économisant beaucoup de circuits. L'idée est de simplifier les additionneurs 4 bits, afin de ne pas utiliser une anticipation de retenue complète. L'anticipation de retenue n'est utilisée que pour la retenue sortante, alors que les bits de somme sont calculées avec propagation de retenue. La retenue sortante est donc calculée en parallèle de l'addition. Chaque bloc contient, à côté d'un additionneur proprement dit, un circuit qui calcule la retenue sortante. Il existe de nombreuses manières de calculer la retenue sortante.
Le gain en portes logiques est assez significatif. En effet, on se passe de circuits d'anticipation de retenue pour les 4 bits de somme, on ne garde que celui pour la retenue sortante. Le fait d'utiliser la propagation de retenue pour calculer les 4 bits de somme est censé avoir un léger cout en performance, mais il se trouve que ce cout est très faible. En effet, le résultat n'est connu qu'une fois les retenues propagées jusqu'au dernier bloc. Et malgré l'anticipation des retenues, la propagation est assez lente. les additionneurs à propagation de retenue auront terminé leur travail bien avant.
Un exemple est celui de l'additionneur CMOS 4008, un additionneur de 4 bit. Il est composé en trois sections. Une première couche de demi-additionneurs calcule les signaux P et G utilisés à la fois pour l'anticipation de la retenue sortante, que pour calculer les bits de somme. Le circuit de calcul de la retenue sortante utilise l'anticipation de retenue. Le reste du circuit fait l'addition en propageant les retenues.
[[File:CMOS 4008, circuit découpé en sections.png|centre|vignette|upright=1.5|CMOS 4008, circuit découpé en sections]]
==L'addition signée et la soustraction==
Après avoir vu l'addition, il est logique de passer à la soustraction, les deux opérations étant très proches. Si on sait câbler une addition entre entiers positifs, câbler une soustraction n'est pas très compliqué. De plus, la soustraction permet de faire des additions de nombres signés.
===Le soustracteur pour opérandes entiers===
Pour soustraire deux nombres entiers, on peut adapter l'algorithme de soustraction utilisé en décimal, celui que vous avez appris à l'école. Celui-ci ressemble fortement à l'algorithme d'addition : on soustrait les bits de même poids, et on propage éventuellement une retenue sur la colonne suivante. À la différence de l'addition, la retenue est soustraite, et non ajoutée. La table de soustraction nous dit quel est le résultat de la soustraction de deux bits. La voici :
* 0 - 0 = 0 ;
* 0 - 1 = 1 et une retenue ;
* 1 - 0 = 1 ;
* 1 - 1 = 0.
[[File:Algebra1 05 fig014.svg|centre|vignette|Soustraction en binaire, avec les retenues en rouge.]]
La table de soustraction peut servir de table de vérité pour construire un circuit qui soustrait deux bits. Celui-ci est appelé un '''demi-soustracteur'''. Il ressemble beaucoup à un demi-additionneur, les différences se résumant à une porte NON ajoutée pour le calcul de la retenue.
[[File:Half Subtractor Vektor.svg|centre|vignette|Demi-soustracteur.]]
Comme pour l'additionneur, deux demi-soustracteurs peuvent être combinés pour donner un '''soustracteur complet'''. Le calcul de la retenue se fait en combinant les deux retenues des demi-soustracteurs avec une porte OU. Les soustracteurs complets sont utilisés pour créer des soustracteurs à propagation de retenue ou tout autre circuit soustracteur, sur le même modèle que les additionneurs.
[[File:FullSubtractor.svg|centre|vignette|upright=2|Soustracteur complet.]]
Vous l'aurez sans doute remarqué, mais le demi-soustracteur ressemble beaucoup au demi-additionneur. La seule différence est une porte NON ajoutée au bon endroit dans le demi-soustracteur.
{|
|[[File:Half-adder.svg|vignette|Demi-additionneur]]
|[[File:Half Subtractor Vektor.svg|vignette|upright=1.3|Demi-soustracteur]]
|}
La conséquence est qu'il est possible de créer un circuit capable de faire à la fois des additions et des soustractions. L'idée est de remplacer la porte NON du demi-soustracteur par un inverseur commandable. Si on envoie un zéro sur l'entrée de commande, l'inverseur commandable se comportera comme une porte OUI. Le circuit sera alors identique à un demi-additionneur. Si on envoie un 1 sur l'entrée de commande, l'inverseur commandable se comportera comme une porte NON. le circuit sera alors un demi-soustracteur. Il faut faire cela pour chaque demi-soustracteur, ce qui donne ceci pour un soustracteur complet. le résultat est alors appelé un '''additionneur-soustracteur complet'''.
[[File:Additionneur-soustracteur complet.png|centre|vignette|upright=2|Additionneur-soustracteur complet]]
===L'additionneur-soustracteur pour opérandes codées en complément à deux===
Étudions le cas de la soustraction en complément à deux, dans l'objectif de créer un circuit soustracteur. Vous savez sûrement que a−b et a+(−b) sont deux expressions équivalentes. Et en complément à deux, − b = not(b) + 1. Dit autrement, a − b = a + not(b) + 1. On pourrait se dire qu'il faut deux additionneurs pour faire le calcul, mais la majorité des additionneurs possède une entrée de retenue pour incrémenter le résultat de l'addition. Un soustracteur en complément à deux est donc simplement composé d'un additionneur et d'un inverseur.
[[File:Additionneur soustracteur.png|centre|vignette|upright=2|Soustracteur en complément à deux.]]
Il est possible de créer un circuit capable d'effectuer soit une addition, soit une soustraction : il suffit de remplacer l'inverseur par un inverseur commandable, qui peut être désactivé. On a vu comment créer un tel inverseur commandable dans le chapitre sur les circuits combinatoires. On peut remarquer que l'entrée de retenue et l'entrée de commande de l'inverseur sont activées en même temps : on peut fusionner les deux signaux en un seul.
[[File:Additionneur-soustracteur en complément à deux.png|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux.]]
Une implémentation alternative est la suivante. Elle remplace l'inverseur commandable par un multiplexeur.
[[File:4-bit ripple carry adder-subtracter.svg|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux, version alternative.]]
===L'additionneur-soustracteur pour opérandes codées en signe-magnitude===
Passons maintenant aux nombres codés en signe-valeur absolue, les deux opérandes étant notées A et B. Suivant les signes des deux opérandes, on a quatre cas possibles : A + B, A − B (B négatif), −A + B (A négatif) et −A − B (A et B négatifs). Une astuce est que le circuit n'a besoin que de calculer A + B et A − B : il peut les inverser pour obtenir − A − B ou B − A. A + B et A − B peuvent se calculer avec un additionneur-soustracteur, reste à corriger le résultat. Il suffit de lui ajouter un inverseur commandable pour obtenir le circuit d'addition finale.
[[File:Additionneur en signe-valeur absolue.png|centre|vignette|upright=2|Additionneur en signe-valeur absolue.]]
Toute la difficulté tient dans le calcul du bit de signe du résultat, quand interviennent des soustractions. Autant l'addition de deux nombres de même signe (A + B et −A − B) ne pose aucun problème, autant les soustractions posent problème (A − B et −A + B). Suivant que <math>A<B</math> ou que <math>A>B</math>, le signe du résultat ne sera pas le même. Déterminer le signe du résultat se fait en regardant les bits de débordement d'entier, comme on le verra plus bas.
===L'additionneur-soustracteur pour opérandes codées en représentation par excès===
Passons maintenant aux nombres codés en représentation par excès. On pourrait croire que ces nombres s'additionnent comme des nombres non-signés, mais ce serait oublier la présence du biais, qui pose problème. Dans les cas de nombres signés gérés avec un biais, voyons ce que donne l'addition de deux nombres :
: <math>( n_1 + biais ) + ( n_2 + biais ) = n_1 + n_2 + 2 \times biais</math>
Or, le résultat correct serait :
: <math>n_1 + n_2 + biais</math>
En effectuant l'addition telle quelle, le biais est compté deux fois. On doit donc le soustraire après l'addition pour obtenir le résultat correct.
Même chose pour la soustraction qui donne ceci :
: <math>(n_1 + biais) - (n_2 + biais) = n_1 - n_2</math>
Or, le résultat correct serait :
: <math> ( n_1 - n_2 ) + biais </math>
Il faut rajouter le biais pour obtenir l'exposant correct.
On a donc besoin de deux additionneurs/soustracteurs : un pour additionner/soustraire les représentations binaires des opérandes, et un autre pour ajouter/retirer le biais en trop/manquant.
==L'additionneur BCD==
Maintenant, voyons un additionneur qui additionne deux entiers au format BCD. Pour cela, nous allons devoir passer par deux étapes. La première est de créer un circuit capable d'additionneur deux chiffres BCD. Ensuite, nous allons voir comment enchaîner ces circuits pour créer un additionneur BCD complet.
===L'additionneur BCD qui fait l'opération chiffre par chiffre===
Nous allons commencer par voir un additionneur qui additionne deux chiffres en BCD, une sorte d'équivalent BCD de l'additionneur complet. Il fournit un résultat sur 4 bits et une retenue qui est mise à 1 si le résultat dépasse 10 (la limite d'un chiffre BCD). Les deux opérandes sont des chiffres BCD codés sur 4 bits et sont additionnés en binaire par un additionneur des plus normaux, similaire à ceux vus plus haut. Le résultat est alors un entier codé en binaire, sur 5 bits, qu'on corrige/convertit pour obtenir un chiffre BCD et une retenue sortante.
Pour corriger le résultat, une idée intuitive serait de prendre le résultat et de faire une division par 10. Le quotient donne la retenue, alors que le reste est le résultat, le chiffre BCD. Mais un circuit diviseur par 10 utilise beaucoup de portes logiques, ce qui ne vaut pas le coup. Une autre méthode détecte si le résultat est égal ou supérieur à 10, ce qui correspond à un "débordement" (on dépasse les limites d'un chiffre BCD). Si le résultat est plus petit que 10, il n'y a rien à faire : le résultat est bon et la retenue est de zéro. Par contre, si le résultat vaut 10 ou plus, il faut corriger le résultat et générer une retenue à 1.
Il faut donc ajouter un circuit qui détecte si le résultat est supérieur à 9, qui calcule directement la retenue. Ce circuit peut se fabriquer simplement à partir de sa table de vérité, ou en utilisant les techniques que nous verrons dans un chapitre ultérieur sur les comparateurs. La solution la plus simple est clairement d'utiliser la table de vérité, ce qui est très simple, assez pour être laissé en exercice au lecteur. Pour comprendre comment corriger le résultat, établissons une table de vérité qui associe le résultat et le résultat corrigé. L'entrée vaut au minimum 10 et au maximum 9 + 9 = 18. On considère la sortie comme un tout, la retenue étant un 5ème bit, le bit de poids fort.
{|class="wikitable"
|-
! colspan="5" | Entrée
!
! rowspan="10" |
! Retenue
! Résultat corrigé (sans retenue)
! interprétation de la sortie en binaire (retenue inclue)
|-
| 0 || 1 || 0 || 1 || 0
| (10)
| 1 || 0000
| (16)
|-
| 0 || 1 || 0 || 1 || 1
| (11)
| 1 || 0001
| (17)
|-
| 0 || 1 || 1 || 0 || 0
| (12)
| 1 || 0010
| (18)
|-
| 0 || 1 || 1 || 0 || 1
| (13)
| 1 || 0011
| (19)
|-
| 0 || 1 || 1 || 1 || 0
| (14)
| 1 || 0100
| (20)
|-
| 0 || 1 || 1 || 1 || 1
| (15)
| 1 || 0101
| (21)
|-
| 1 || 0 || 0 || 0 || 0
| (16)
| 1 || 0110
| (22)
|-
| 1 || 0 || 0 || 0 || 1
| (17)
| 1 || 0111
| (23)
|-
| 1 || 0 || 0 || 1 || 0
| (18)
| 1 || 1000
| (24)
|}
En analysant le tableau, on voit que pour corriger le résultat, il suffit d'ajouter 6. La raison est que le résultat déborde d'un nibble à 16 en binaire, mais à 10 en décimal : il suffit d'ajouter la différence entre les deux, à savoir 6, et le débordement binaire fait son travail. Donc, la correction après une addition est très simple : si le résultat dépasse 9, on ajoute 6.
On peut maintenant implémenter l'additionneur BCD, en combinant le comparateur avec 10, le circuit de correction, et l'additionneur. La première solution calcule deux versions du résultat : la version corrigée, la version normale. Le choix entre les deux est réalisée par un multiplexeur, commandé par le comparateur.
[[File:Additionneur BCD.png|centre|vignette|upright=1.5|Additionneur BCD]]
L'autre solution utilise un circuit commandable qui soit additionne 6, soit ne fait rien. Le choix entre les deux est commandé par le bit calculé par le comparateur.
[[File:Additionneur BCD, seconde version.png|centre|vignette|upright=1.5|Additionneur BCD, seconde version.]]
Une version alternative du circuit précédent est la suivante. Il contient deux additionneurs : un pour additionner les deux chiffres BCD, un autre pour additionner 6 si besoin. Le résultat du comparateur est directement utilisé pour générer l'opérande du second additionneur : 0 ou 6. Le circuit est simple à concevoir, mais gaspille beaucoup de circuit. Idéalement, il vaudrait mieux utiliser un circuit combinatoire d'addition avec une constante.
[[File:Adder 4 Bit BCD.svg|centre|vignette|upright=2|Additionneur BCD, circuit complet.]]
Pour obtenir un additionneur BCD complet, il suffit d’enchaîner les additionneurs précédents, comme on le ferait avec les additionneurs complets dans un additionneur à propagation de retenue. Au final, l'additionneur BCD est beaucoup plus compliqué qu'un additionneur normal, car il rajoute un comparateur ">9", un petit additionneur pour ajouter 6 et éventuellement d'autres circuits. De plus, il est difficile d'appliquer les optimisations disponibles sur les additionneurs non-BCD. Notamment, les circuits d'anticipation de retenue sont totalement à refaire et le résultat est relativement compliqué. C'est ce qui explique pourquoi le BCD a progressivement été abandonné au profit du binaire simple.
La soustraction en BCD se fait comme en binaire : le nombre à soustraire est remplacé par son complément, le circuit additionne le complément et l'autre opérande, le débordement d'entier fait que le résultat marche. Sauf qu'ici, le complément est un complément à 9. Il se calcule chiffre par chiffre : chaque chiffre est remplacé par (9 - le chiffre en question).
===L'additionneur BCD par ajustement décimal===
L'additionneur BCD précédent effectuait son travail chiffre BCD par chiffre BCD, mais il existe des additionneurs BCD qui font autrement. Sur les premiers processeurs x86, il n'y avait pas d'opération d'addition BCD proprement dit, seulement une addition binaire normale de 8, 16 ou 32 bits. Par contre, elle était secondée par une opération dite d''''ajustement décimal''' qui transformait un nombre binaire en nombre codé en BCD. L'opération d'ajustement décimal prenait un opérande de 8 bits codé en binaire et fournissait un résultat de la même taille, c'est à dire deux chiffres BCD. Effectuer une addition BCD demandait donc de faire deux opérations à la suite : une addition binaire simple, suivie par l'opération d'ajustement décimal. Cela permettait de gérer des nombres entiers en binaire usuel et des entiers BCD sans avoir deux instructions d'addition séparées pour les deux, sans compter que cela simplifiait aussi les circuits d'addition.
L'ajustement décimal s'effectue en ajoutant une constante bien précise à l'opérande à convertir en BCD. L'idée est que la constante est découpée en morceaux de 4 bits, correspondant chacun à un chiffre BCD de l'opérande, chaque morceau contenant soit un 0, soit 6. Cela permet d'ajouter soit 0, soit 6, à chaque chiffre BCD, et donc de le corriger. La propagation des retenues d'un chiffre à l'autre est effectuée automatiquement par l'addition binaire de la constante. L'opération d'ajustement décimal calcule automatiquement la constante. Elle découpe l'opérande en ''nibbles'', vérifie si chaque ''nibble'' est supérieur ou égal à 10, puis détermine la valeur de chaque ''nibble'' de la constante finale. Par exemple, si je prends l'opérande 1001 1110, le ''nibble'' de poids faible déborde, alors que celui de poids fort non. La constante sera donc 0000 0110 : 0x06. Inversement, si le ''nibble'' de poids fort déborde et pas celui de poids faible, la constante sera alors 0x60. Et la constante est de 0x66 si les deux ''nibbles'' débordent, de 0x00 si aucun ne déborde.
Le circuit d’ajustement décimal est donc composé de trois étapes : deux étapes pour calculer la constante, et un circuit d'addition pour additionner cette constante au nombre de départ. La première étape découpe l'opérande en morceaux de 4 bits, en chiffres BCD, et vérifie si chacun d'entre eux vaut 10 ou plus. La seconde étape prend les résultats de la première étape, et les combine pour calculer la constante. Enfin, on trouve l'addition finale, qui était réalisée par un circuit d'addition utilisé à la fois pour l'ajustement décimal et l'addition binaire. La différence entre une addition normale et une opération d'ajustement décimal tient dans le fait que les deux premières étapes sont désactivées dans une addition normale.
[[File:Additionneur BCD parallèle.png|centre|vignette|upright=1.5|Additionneur BCD parallèle]]
===L'additionneur biquinaire===
Les entiers BCD ne sont qu'un des encodages hybrides entre décimal et binaire. L'encodage biquinaire est l'un d'entre eux et nous allons faire un rappel rapide à ce sujet. Pour simplifier, un chiffre encodé en biquinaire est composé de deux parties : un bit, couplé à une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits (0, 1, 2, 3 et 4). Le bit indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9.
Additionner deux nombres de biquinaire demande donc d'additionner deux parties quinaires encodées en ''one-hot'' et d'additionner deux bits. Mais attention : il faut tenir compte de la retenue de l'addition des parties quinaires. Et idéalement, il faut aussi tenir compte d'une retenue entrante, provenant de l'addition de la colonne de chiffres précédente. Toute la difficulté vient de la création de l'additionneur ''one-hot''. Heureusement, vu qu'il n'y a que 4-5 bits à additionner, il est souvent fabriqué à partir de sa table de vérité.
[[File:Additionneur bi-quinaire.png|centre|vignette|upright=2|Additionneur bi-quinaire]]
Un avantage du biquinaire est que le calcul du complément à 9 est très simple. Il faut pour cela : inverser la partie binaire avec une porte NON, puis inverser l'ordre des bits de la partie quinaire. Concrètement, le bit de poids faible devient le bit de poids fort, et ainsi de suite. Par exemple, une partie quinaire 01000 devient 00010, 10000 devient 00001, 00100 ne change pas, etc. Le tout peut se calculer avec une porte NON et 5 multiplexeurs.
===L'additionneur BCD avec calculs intermédiaires en biquinaire===
L'ordinateur IBM 1401, un ancien ''mainframe'' des années 60, utilisait un additionneur BCD un peu particulier. Les nombres étaient encodés en BCD dans la mémoire de l'ordinateur, mais les circuits de calcul utilisaient la représentation biquinaire. Lors d'un calcul, le processeur de l'ordinateur traduisait les chiffres BCD en représentation biquinaire, faisait une addition en biquinaire, avant de traduire le résultat en BCD normal.
Pour être précis, l'IBM 1401 utilisait une variante du biquinaire. L'encodage biquinaire de l'IBM 1401 est le suivant : la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre pair encodé par la partie quinaire. Si l'IBM 1401 utilisait cette variante du biquinaire, c'est car elle donnait des circuits de conversion BCD-biquinaire plus économes en portes logiques et plus rapides.
La partie binaire est le bit de poids faible du chiffre BCD, la partie biquinaire est calculée par un simple décodeur qui prend en entrée le chiffre BCD, amputé de son bit de poids faible. La traduction inverse demande d'utiliser un encodeur, à la place du décodeur. Par contre, le circuit d'addition biquinaire était plus compliqué du fait de la gestion des retenues. L'addition des parties binaires et quinaires se faisait en parallèle, dans deux additionneurs séparés. Cependant, l'addition des parties binaire fournit une retenue, qu'il faut prendre en compte. Pour cela, l'IBM 1401 disposait d'un troisième additionneur qui fournissait le résultat final, encodé en biquinaire.
[[File:Additionneur bi-quinaire de l'IBM 1401.png|centre|vignette|upright=2|Additionneur biquinaire de l'IBM 1401]]
Une implémentation moderne demanderait d'utiliser des portes ET combinées à des portes OU, le circuit pouvant être construit simplement à partir de sa table de vérité. Sur l'IBM 1401, le circuit était cependant différent, en raison de l'utilisation de OU câblés, des croisements de fils qui fonctionnent comme des portes OU, que nous n'avons pas encore vu pour le moment, mais qui seront détaillés dans quelques chapitres. Les OU câblés étaient utilisés pour simplifier le design du circuit, mais demandaient des portes logiques spécifiques, ce qui collait avec le fait que ce ''mainframe'' utilisait des transistors en Germanium. L'implémentation exacte est décrite dans cet article de blog, mais je ne recommande sa lecture qu'à ceux qui savent ce qu'est un OU câblé :
* [https://www.righto.com/2015/10/qui-binary-arithmetic-how-1960s-ibm.html Qui-binary arithmetic: how a 1960s IBM mainframe does math].
==Les débordements d'entier lors d'une addition/soustraction==
Les instructions arithmétiques manipulent des entiers codés sur un nombre fixe de bits, qui ne peuvent prendre leurs valeurs que dans un intervalle. Pour les nombres positifs, un ordinateur qui code ses entiers sur n bits pourra coder tous les entiers allant de 0 à <math>2^n -1</math>. Pour les nombres négatifs, l'intervalle est différent et dépend de la représentation utilisée. Dans le cas général, l'ordinateur peut coder les valeurs comprises de <math>N_\text{min}</math> à <math>N_\text{max}</math>. Si le résultat d'un calcul sort de cet intervalle, il ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math> , alors que la valeur basse vaut <math>N_\text{min} - 1</math> (avec <math>N_\text{max}</math> et <math>N_\text{min}</math> respectivement la plus grande et la plus petite valeur codable par l'ordinateur).
===La correction des débordements d'entier : l'arithmétique saturée===
Quand un débordement d'entier survient, tous les circuits de calcul ne procèdent pas de la même manière. Dans les grandes lignes, il y a deux réactions possibles : soit on corrige automatiquement le résultat du débordement, soit on ne fait rien et on se contente de détecter le débordement.
Si le débordement n'est pas corrigé automatiquement, le circuit ne conserve que les bits de poids faibles du résultat. Les bits en trop sont simplement ignorés. On dit qu'on utilise l''''arithmétique modulaire'''. Le problème avec ce genre d'arithmétique, c'est qu'une opération entre deux grands nombres peut donner un résultat très petit. Par exemple, si je dispose de registres 4 bits et que je souhaite faire l'addition 1111 + 0010 (ce qui donne 15 + 2), le résultat est censé être 10001 (17), ce qui est un résultat plus grand que la taille d'un registre. En conservant les 4 bits de poids faible, j’obtiens 0001 (1). En clair, un résultat très grand est transformé en un résultat très petit. Cela peut poser problèmes si on travaille uniquement avec des nombres positifs, mais c'est aussi utilisé pour coder des nombres en complément à deux.
D'autres circuits utilisent ce qu'on appelle l''''arithmétique saturée''' : si un calcul génère un débordement, on arrondi le résultat au plus grand entier supporté par le circuit. Les circuits capables de calculer en arithmétique saturée sont un peu plus complexes, vu qu'il faut rajouter des circuits pour corriger le résultat en cas de débordement. Il suffit généralement de rajouter un ''circuit de saturation'', qui prend en entrée le résultat et le corrige en cas de débordement. Ce circuit de saturation met la valeur maximale en sortie si un débordement survient, mais se contente de recopier le résultat du calcul sur sa sortie s'il n'y a pas de débordement. Typiquement, il est composé d'une couche de multiplexeurs, qui sélectionnent quelle valeur mettre sur la sortie : soit le résultat du calcul, soit le plus grand nombre entier géré par le processeur, soit le plus petit (pour les nombres négatifs/soustractions).
L'arithmétique saturée est utilisée pour les additions et soustractions, mais c'est plus rare pour les multiplications/divisions. Une des raisons est que le résultat d'une addition/soustraction prend un bit de plus que le résultat, là où les multiplications doublent le nombre de bits. Quand une addition déborde, le résultat réel est proche de la valeur maximale codable. mais quand une multiplication déborde, le résultat peut parfois valoir 200 à 60000 fois plus que la valeur maximale codable. Les calculs avec une valeur saturée/corrigée sont donc crédibles pour une suite d'additions, mais pas pour une suite de multiplications.
===La détection des débordements entiers===
Quand un débordement d'entier a eu lieu, il vaut mieux que l'additionneur prévienne ! Pour cela, l'additionneur a une '''sortie de débordement''', parfois nommée ''Overflow'', dont la valeur indique si l'addition a généré un débordement d'entier ou non. Reste que détecter un débordement ne se fait pas de la même manière selon que l'on parle d'un additionneur non-signé ou signé.
Pour les additionneur non-signés, l'additionneur calcule un bit de plus que ce qui est supporté par l'ordinateur. Par exemple, un additionneur 32 bits fournit un résultat sur 33 bits, un débordement d'entier a lieu quand le 33ème bit est à 1. Précisément, la sortie de débordement n'est autre que la retenue finale, celle fournie par le dernier additionneur complet. Le seul type de débordement possible est un débordement par le haut, où le résultat dépasse la valeur maximale. Avec l'arithmétique saturée, le circuit de saturation consiste en une seule couche de multiplexeurs, voire en un circuit de mise à la valeur maximale tel que vu dans le chapitre sur les opérations bits à bits.
[[File:Gestion des débordements d'entiers.png|centre|vignette|upright=2.5|Gestion des débordements d'entiers lors d'une addition non-signée.]]
Pour les additionneurs non-signés, la gestion des débordements d'entiers dépend fortement de la représentation signée. Nous allons étudier le cas du complément à deux. Si vous vous rappelez le chapitre 1, les calculs sur des nombres en complètement à deux utilisent les règles de l'arithmétique modulaire, c'est une condition nécessaire. À priori, on peut penser que dans ces conditions, les débordements d'entiers sont une chose parfaitement normale, qui nous permet d'avoir des résultats corrects. Néanmoins, certains débordements d'entiers peuvent survenir malgré tout et produire des bugs assez ennuyeux.
Si l'on tient en compte les règles du complément à deux, on sait que le bit de poids fort (le plus à gauche) permet de déterminer si le nombre est positif ou négatif : il indique le signe du nombre. Tout se passe comme si les entiers en complément à deux étaient codés sur un bit de moins, et avaient leur longueur amputé du bit de poids fort. Si le résultat d'un calcul écrase le bit de poids fort, il y a un débordement d'entiers. Il existe une règle simple qui permet de détecter ces débordements d'entiers. L'addition de deux nombres positifs ne peut pas être un nombre négatif. Si on additionne deux nombres dont le bit de signe est à 0 et que le bit de signe du résultat est à 1, on est en face d'un débordement d'entiers. Même chose pour deux nombres négatifs : le résultat de l'addition ne peut pas être positif. On peut résumer cela en une phrase : si deux nombres de même signe sont ajoutés, un débordement a lieu quand le bit du signe du résultat a le signe opposé.
Modifier les circuits d'au-dessus pour qu'ils détectent les débordements en complément à deux est simple comme bonjour : il suffit créer un petit circuit combinatoire qui prenne en entrée les bits de signe des opérandes et du résultat, et qui fasse le calcul de l'indicateur de débordements. Si l'on rédige sa table de vérité, on doit se retrouver avec la table suivante :
{|class="wikitable"
|-
!Entrées
!Sortie
|-
|000||0
|-
|001||1
|-
|010||0
|-
|011||0
|-
|100||0
|-
|101||0
|-
|110||1
|-
|111||0
|}
L'équation de ce circuit est la suivante, avec <math>S_a</math> et <math>S_b</math> les signes des deux opérandes, et <math>C_i</math> la retenue de la colonne précédente :
: <math> ( S_a . S_b . \overline{C_i} ) + ( \overline{S_a} . \overline{S_b} . C_i )</math>
En simplifiant, on obtient alors :
: <math>( S_a . S_b ) \oplus C_i</math>
Or, il se trouve que <math>S_a . S_b</math> est tout simplement la retenue en sortie du dernier additionneur, que nous noterons <math>C_o</math>. On trouve donc :
: <math>C_o \oplus C_i</math>
Il suffit donc de faire un XOR entre la dernière retenue et la précédente pour obtenir le bit de débordement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de décalage et de rotation
| prevText=Les circuits de décalage et de rotation
| next=Les circuits de comparaison
| nextText=Les circuits de comparaison
}}
</noinclude>
1ataz898ix4bvpisayn2ketj7n9y1u1
768661
768660
2026-06-25T19:48:40Z
Mewtow
31375
/* Les additionneurs à anticipation de retenue */
768661
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir les circuits capables de faire une addition ou une soustraction, ainsi que quelques circuits spécialisés. Précisons cependant que les fabricants de processeurs travaillent d'arrache-pied pour trouver des moyens de rendre ces circuits de calcul plus rapides et plus économes en énergie. Autant vous dire que les circuits que vous allez voir sont vraiment des circuits qui font pâle figure comparé à ce que l'on peut trouver dans un vrai processeur commercial !
==Les circuits pour additionner 2 ou 3 bits==
L'addition se fait en binaire de la même manière qu'en décimal. On additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. La soustraction fonctionne sur le même principe, sur le même modèle qu'en décimal.
[[File:Binary Addition Demonstration.svg|centre|vignette|Exemple d'addition en binaire.]]
En clair, additionner deux nombres demande d'additionner 2 bits et une retenue sur chaque colonne, et de propager les retenues d'une colonne à l'autre. La propagation des retenues est quelque chose de simple en apparence, mais qui est sujet à des optimisations extraordinairement nombreuses. Aussi, pour simplifier l'exposition, nous allons voir comment gérer une colonne avant de voir comment sont propagées les retenues.
[[File:Full Adder Block.svg|vignette|upright=1|class=transparent|Additionneur complet.]]
Si on effectue une addition en colonne, on doit additionner les deux bits sur la colonne, mais aussi additionner une éventuelle retenue. Il faut donc créer un circuit qui additionne trois bits : deux bits de données, plus une retenue. Ce circuit qui additionne trois bits est appelé un '''additionneur complet'''. Il fournit en sortie deux bits : un bit de somme et une retenue sortante. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|-
||0||0||0|| ||0||0
|-
||0||0||1|| ||0||1
|-
||0||1||0|| ||0||1
|-
||0||1||1|| ||1||0
|-
||1||0||0|| ||0||1
|-
||1||0||1|| ||1||0
|-
||1||1||0|| ||1||0
|-
||1||1||1|| ||1||1
|}
Il est possible d'utiliser un tableau de Karnaugh pour traduire la table de vérité, mais elle donne un résultat assez compliqué. La retenue et le bit de somme sont calculés à part. La retenue est calculée avec trois portes ET et une porte OU. Le bit de somme est calculé lui avec 4 portes ET, une porte OU, et trois portes NON.
[[File:Full Adler with 3 entries.png|centre|vignette|upright=2|Additionneur complet, fabriqué à partir de la table de vérité.]]
D'autres méthodes donnent des résultats plus compréhensibles. Nous allons les voir dans la suite de cette section.
===L'additionneur complet basé sur des demi-additionneurs===
[[File:1-bit half-adder.svg|class=transparent|vignette|Demi-addtionneur.]]
Nous avons déjà vu comment additionner deux bits dans le chapitre sur les incrémenteurs, mais quelques rappels ne seront pas de trop. Le '''demi-additionneur''' est un circuit qui additionne deux bits. Il implémente la table d'addition, qui est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Mais un demi-additionneur n'additionne que deux bits, il manque de quoi additionner la retenue. Une solution pour cela est d'enchaîner deux demi-additionneurs : un qui additionne les deux bits de données, et un second qui additionne la retenue au résultat du précédent. La retenue finale se calcule en combinant les sorties de retenue des deux demi-additionneurs, avec une porte OU. Le résultat est un additionneur complet fabriqué avec deux demi-additionneurs. Pour vous en convaincre, établissez la table de vérité de ce circuit, vous verrez que ça marche.
[[File:Full Adder Blocks.svg|centre|vignette|upright=2|Additionneur complet fabriqué avec deux demi-additionneurs.]]
[[File:Half-adder.svg|vignette|upright=1|Circuit d'un demi-additionneur naïf.]]
L'implémentation naïve d'un demi-additionneur utilise une porte XOR et une porte ET, comme illustré ci-contre. Le circuit s'obtient en faisant uen traduction littérale de la table de vérité du circuit. Si je dis qu'elle est naïve, c'est car il est possible de l'optimiser, de manière à éliminer des redondances cachées. Mais nous avions déjà vu cela dans le chapitre sur les incrémenteurs, aussi laissons cela pour plus tard.
En combinant deux demi-additionneurs de ce type, on obtient l'additionneur complet suivant. Les deux sorties de retenue des demi-additionneurs sont combinées avec une porte OU, pour calculer la retenue finale.
{| class="flexible"
|[[File:Full Adder Modules.svg|class=transparent|Composition d'un additionneur complet. On voit bien que celui-ci est composé de deux demi-additionneurs, en rouge et en bleu, auxquels on a ajouté une porte OU pour calculer la retenue finale.]]
|[[File:Full-adder.svg|300px|class=transparent|Circuit d'un additionneur complet.]]
|}
Le fait de combiner les deux retenues avec une porte OU n'est pas ce qu'il y a de plus intuitif. La table de vérité nous dit que ça fonctionne, mais on comprend mal pourquoi. Mais tout devient plus clair quand on sait que les deux retenues ne peuvent pas être à 1 en même temps. Même dans l'addition 1 + 1 + 1, seule une retenue est à 1. Soit l'addition des deux bits d'opérande donne naissance à une retenue, soit c'est l'addition ''résultat + retenue entrante''. La retenue sortant est donc à 1 quand une des deux addition donne une retenue. D'où l'usage d'une porte OU. Remarquez qu'une porte XOR donne le même résultat, vu que le cas où les deux retenues sont à 1 n'est jamais rencontré.
===La propagation et la génération des retenues===
L'additionneur complet que nous allons voir dans cette section sert à introduire les concepts de propagation, génération et absorption de retenue. Il s'agit de concepts très importants quand on étudie les additionneurs. l'idée part d'un principe très simple : la retenue sortante dépend de la retenue d'entrée. La relation entre les deux se résume à trois cas, qui dépendent de la valeur des deux bits additionnés, nommés A et B.
* Dans le premier cas, la retenue entrante est égale à la retenue sortante. On dit que la retenue entrante est propagée par l'additionneur.
* Dans le second cas, la retenue est forcée à 0 : la retenue sortante vaut 0, peu importe la valeur de la retenue entrante.
* Dans le troisième cas, la retenue est forcée à 1 : la retenue entrante vaut 1, peu importe la valeur de la retenue entrante.
[[File:Signaux P et G fournis par un demi-additionneur.png|vignette|Signaux P et G fournis par un demi-additionneur]]
Maintenant, créons un circuit qui nous dise si une retenue est propagée ou générée. Il a deux bits de sortie, nommés P et G : P pour ''Propagate'', G pour ''Generate''. Le bit P est à 1 si la retenue entrante est propagée, il est à 0 sinon. Le bit G est à 1 si une retenue est générée, à 0 sinon. Une retenue est considérée comme absorbée si elle n'est pas ni propagée ni générée, pas besoin d'un troisième bit pour gérer ce cas.
Il se trouve que ce circuit n'est autre qu'un demi-additionneur ! Pour vous en rendre compte, regardez la table de vérité d'un additionneur complet, illustrée ci-dessous. Lorsque les deux bits d'opérande sont à 0, la retenue sortante vaut toujours 0. Si ils sont tous deux à 1, alors la retenue sortante vaut 1. S'ils sont différents, alors retenues sortante et entrante sont égales. Le bit P est donc généré par une simple porte XOR. Quant au bit G, il est à 1 si les deux bits d'opérandes sont à 1, ce qui correspond à une porte ET. Il se trouve que ces deux portes forment un demi-additionneur !
{|class="wikitable"
|-
! Opérande 1 !! Opérande 2 !! Retenue entrante !! !! Retenue sortante
|- class="f_rouge"
||0||0||0|| ||0
|- class="f_rouge"
||0||0||1|| ||0
|- class="f_bleu"
||0||1||0|| ||0
|- class="f_bleu"
||0||1||1|| ||1
|- class="f_bleu"
||1||0||0|| ||0
|- class="f_bleu"
||1||0||1|| ||1
|- class="f_vert"
||1||1||0|| ||1
|- class="f_vert"
||1||1||1|| ||1
|}
Ensuite, créons un circuit qui prend ces deux sorties P et G, et calcule la retenue sortante en fonction. Le circuit en question a trois entrées : la retenue entrante, les deux bits P et G. La retenue sortante vaut 1 soit si une retenue est générée, soit si la retenue entrante est propagée et qu'elle vaut 1. En notant <math>{C_{in}}</math> et <math>{C_{out}}</math> les retenues entrantes et sortante, on a :
: <math>{C_{out}} = G + \left( P . C_{in} \right)</math>
Le circuit est donc composé d'une porte OU et d'une porte ET. En combinant un demi-additionneur avec le circuit de calcul de retenue sortante vu plus haut, on a :
[[File:Additionneur complet avec propagation et génération de retenue.png|class=transparent|centre|vignette|upright=2|Additionneur complet avec propagation et génération de retenue.]]
Certes, on retombe sur le circuit vu plus haut. Mais il est possible de remplacer le circuit en orange par un autre. Par exemple, il est possible de le remplacer par multiplexeur, qui choisit entre la retenue générée et la retenue entrante (propagée). Le choix se fait selon la valeur du bit P, qui chosiit entre propager la retenue et la générer.
[[File:Additionneur crée avec un multiplexeur.png|centre|vignette|upright=1.5|Additionneur crée avec un multiplexeur]]
Le circuit exact est illustré ci-dessous. Il semble utiliser plus de portes logiques que l'additionneur complet précédent. Cependant, nous verrons dans quelques chapitres qu'il est possible d'implémenter un multiplexeur avec seulement 6 transistors, voire moins ! L'implémentation utilise des portes à transmission, mais nous en reparlerons dans le chapitre sur les transistors, quand nous verrons les additionneurs à ''Manchester Carry Chain''. Au passage, une variante de ce circuit a été utilisée dans le processeur processeur 8086 d'Intel, comme on le verra dans le chapitre suivant.
[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
[[File:FulladderPG.png|vignette|upright=1|Additionneur complet avec deux sorties P et G.]]
Pour finir, sachez qu'il existe des additionneurs qui fournissent : le bit de résultat, deux sorties P et G qui indiquent si l'addition propage ou génère une retenue. Il ne s'agit pas d'additionneurs complets, car il manque de quoi calculer la retenue sortante. De tels additionneurs seront appelés des '''additionneurs partiels'''. De tels additionneurs sont utilisés dans certains additionneurs pour gagner en performance.
Enfin, il existe aussi ce que j'ai décidé d'appeler des '''additionneurs P/G''', qui sont des additionneurs complets auxquels on a ajouté deux sorties P et G, en plus de la sortie de retenue. Leur circuit est le même que celui d'un additionneur complet, auquel on a ajouté deux fils. Vu que ces deux sorties sont fournies par le premier demi-additionneur, ajouter ces deux sorties demande d'ajouter des fils, pas de portes logiques. De tels additionneurs sont aussi utilisés dans certains additionneurs pour gagner en performance.
===L'additionneur complet basé sur une porte à majorité===
Maintenant, voyons une dernière implémentation possible de l'additionneur complet. Mais avant de voir comment l'additionneur est implémenté, nous allons devoir faire un petit focus sur la retenue sortante. Reprenons la table de vérité, et regardons ce qu'il en est pour la retenue sortante uniquement.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante
|-
||0||0||0|| ||0
|-
||0||0||1|| ||0
|-
||0||1||0|| ||0
|-
||0||1||1|| ||1
|-
||1||0||0|| ||0
|-
||1||0||1|| ||1
|-
||1||1||0|| ||1
|-
||1||1||1|| ||1
|}
Vous remarquerez que la retenue sortante est égale au bit majoritaire, parmi les trois bits d'opérande. Si deux ou trois bits sont à 1, la retenue sortante vaut 1. Et réciproquement avec 0. Le circuit de calcul de la retenue peut donc être remplacé par une '''porte à majorité'''.
[[File:Additionneur crée avec une porte à majorité.png|centre|vignette|upright=2|Additionneur crée avec une porte à majorité]]
Mais une porte à majorité est assez gourmande en circuit, ce qui fait qu'un additionneur pareil ne serait pas pratique, ni utile. Du moins, ce serait le cas s'il n'y avait pas une possibilité d'optimisation extrêmement intéressante. Il est possible de calculer le 'bit de somme' à partir de la retenue sortante ! En effet, le bit de somme est l'inverse de la retenue sortante, sauf dans deux cas : les trois bits d'entrée sont à 0, où ils sont tous à 1. Le bit de somme vaut 0 dans le premier cas, 1 dans le second cas.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|- class="f_bleu"
||0||0||0|| ||0||0
|- class="f_vert"
||0||0||1|| ||0||1
|- class="f_vert"
||0||1||0|| ||0||1
|- class="f_vert"
||0||1||1|| ||1||0
|- class="f_vert"
||1||0||0|| ||0||1
|- class="f_vert"
||1||0||1|| ||1||0
|- class="f_vert"
||1||1||0|| ||1||0
|- class="f_rouge"
||1||1||1|| ||1||1
|}
En clair, 6 lignes de la table de vérité sur 8 peuvent se calculer avec une porte à majorité, le reste demandant quelques portes logiques pour faire la correction. L'implémentation en circuit demande donc trois choses :
* de calculer la retenue sortante et de quoi l'inverser ;
* un circuit qui vérifie si tous les bits opérande valent 0 : une porte NOR ou OU fait l'affaire ;
* un circuit qui vérifie s'ils valent tous 1, à savoir une porte ET.
Ensuite, on combine le résultat des trois circuits précédents pour obtenir le résultat final. La combinaison est le fait du circuit en jaune dans le schéma ci-dessous.
[[File:Full adder basé sur une modification de la retenue.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue]]
Le circuit en jaune est assez simple. Quand la porte ET sort un 1, la sortie doit être forcée à 1. Par contre, quand la porte OU sort un zéro, la sortie doit être forcée à 0. Il suffit donc d'enchainer un circuit de mise à 1 et un circuit de mise à 0. Nous avons vu dans le chapitre sur les opérations de masquage qu'il s'agit respectivement d'une porte OU et d'une porte ET.
[[File:Full adder basé sur une modification de la retenue interne, au complet.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue interne, au complet]]
Divers processeurs utilisaient des additionneurs complets de ce type : le fameux Z80, l'Intel 4004 et de l'Intel 8008 sont les exemples les plus notables. Pourtant, on pourrait s'étonner qu'un tel circuit ait existé. Il utilise beaucoup de portes logiques, a une profondeur logique supérieure : il n'a rien d'avantageux. Sauf qu'il était utilisé sur d'anciens processeurs, qui utilisaient la technologie dite TTL, différente de la technologie CMOS des transistors modernes. Et avec la technologie TTL, il est possible de fusionner plusieurs portes logiques ET et NOR en une seule porte logique ET/OU/NON ! Un additionneur complet construit ainsi ne prenait que deux portes logiques : une pour le calcul de la retenue sortante, une autre pour le reste du circuit.
==L'addition non signée==
Voyons maintenant un circuit capable d'additionner deux nombres entiers: l''''additionneur'''. Dans la version qu'on va voir, ce circuit manipulera des nombres strictement positifs. L'addition des nombres codés en complètement à deux sera vu dans une section ultérieure.
===L'additionneur série===
Il est possible d'additionner deux nombres bit par bit, gr$ace à un additionneur complet associé à plusieurs registres à décalages. Les opérandes sont placées chacune dans un registre à décalage, et l'additionneur complet utilise les bits sortants de ces registres à décalage. Le bit du résultat est envoyé au registre à décalage pour le résultat. La retenue de l'addition est stockée dans une bascule de 1 bit, en attente du prochain cycle d'horloge. Un tel additionneur est appelé un '''additionneur série'''. Il a été utilisé sur d'anciens ordinateurs dans les années 50-60, aussi bien des prototypes que des ordinateurs commerciaux.
[[File:Additionneur série.jpg|centre|vignette|upright=2.5|Additionneur série.]]
===L'additionneur à propagation de retenue===
L''''additionneur à propagation de retenue''' est aussi appelé l'additionneur ''ripple carry'', c'est son nom anglais. J'utiliserais occasionnellement le nom anglais dans ce qui suit, car celui-ci est plus court. Ilutilise un additionneur complet pour chaque colonne de l'addition. Pour gérer les retenues, il suffit ainsi de câbler des additionneurs complets les uns à la suite des autres, chacun envoyant sa retenue à la colonne suivante.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Notons la présence de la retenue sortante, qui est utilisée pour détecter les débordements d'entier, ainsi que pour d'autres opérations. Le bit de retenue final est souvent stocké dans un registre spécial du processeur (généralement appelé ''carry flag'').
Notez aussi, à droite du schéma précédent, l'entrée de retenue <math>\text{R}_\text{Entrée}</math>. Elle est très utile pour l'implémentation de certaines opérations comme l'inversion de signe, la soustraction, l'incrémentation, etc. Certains processeurs sont capables de faire une opération appelée ADC, ADDC ou autre nom signifiant ''Addition with Carry'', qui permet de faire le calcul A + B + Retenue (la retenue en question est la retenue sortante de l'addition précédente, stockée dans le registre ''carry flag''). Son utilité principale est de permettre des additions d'entiers plus grands que ceux supportés par le processeur. Par exemple, cela permet de faire des additions d'entiers 32 bits sur un processeur 16 bits.
[[File:Adder Network 3 Bit Sum Delay 003.svg|vignette|upright=0.5|Propagation de retenue dans l'additionneur.]]
Cet additionneur utilise très peu de portes logiques, ce qui fait qu'il était utilisé sur certains processeurs 8 et 16 bits, dont le budget en portes logiques était limité. Bien que très simple, cet additionneur est cependant peu performant. Son temps de calcul est proportionnel à la taille des opérandes. Par exemple, additionner deux opérandes de 32 bits prendra deux fois plus de temps que pour des opérandes de 16 bits. La raison est que les retenues doivent se propager à travers le circuit, du premier additionneur jusqu'au dernier.
L'addition étant une opération fréquente, d'autres additionneurs ont été inventés. Ils utilisent des optimisations qui utilisent plus de circuits pour gagner en rapidité. Mais avant de voir ces optimisations, nous allons voir s'il est possible d'optimiser les additionneurs à propagation de retenue.
===L'additionneur à saut de retenue===
L'additionneur à propagation de retenue peut être optimisé de deux manières. La première accélère la propagation de la retenue, en agissant au niveau des additionneurs complets. La ''Manchester carry chain'' est une optimisation de ce type, mais on ne peut pas encore expliquer à ce stade du cours, car elle optimisait les additionneurs complets au niveau des transistors eux-mêmes. Elle a été utilisée sur de nombreux processeurs connus, comme le 8086 d'Intel ou le Z80.
La seconde manière rend l'addition plus rapide dans certains cas. Le résultat est un additionneur dont le temps de calcul est variable. Le calcul prendra quelques cycles d'horloges avec certains opérandes, mais d'autres opérandes ne verront aucune amélioration. Il n'améliore pas le pire des cas, dans lequel la retenue doit être propagée du début à la fin, du bit de poids faible au bit de poids fort. Mais dans d'autres cas, il permet d'avoir le résultat en avance. L'optimisation en question donne l''''additionneur à saut de retenue''' (''carry-skip adder'').
Il est composé de blocs qui additionnent 4/5 bits, rarement plus. La taille d'un bloc varie d'un additionneur à l'autre, elle peut même varier dans l'additionneur, mais nous allons prendre un exemple où les blocs font 4 bits. L'idée est de détecter précocement si la retenue entrante est propagée à travers tout le bloc. Si la retenue est propagée dans tous le bloc, on peut directement l'envoyer sur la sortie de retenue, la retenue saute le bloc entier. Dans le cas contraire, on doit calculer la retenue normalement. Le choix entre les deux est le fait d'un multiplexeur. Toute la difficulté est de savoir comment commander le multiplexeur.
[[File:Carry skip adder, principe de base.png|centre|vignette|upright=2.5|Carry skip adder : principe de base]]
Pour savoir si une retenue est propagée dans un bloc entier, il faut utiliser des additionneurs complets, qui ont en plus une sortie P indiquant que l'additionneur propage la retenue. Le bloc propage une retenue si chaque additionneur complet propage la retenue, donc s'ils ont tous leur sortie P à 1. Un simple ET logique suffit à déterminer si c'est le cas. Le signal de commande du multiplexeur est donc un vulgaire ET entre toutes les sorties P des additionneurs complets du bloc.
[[File:Additionneur à saut de retenue.png|centre|vignette|upright=2.5|Calcul de la commande du MUX.]]
Voici ce que ça donne pour un bloc de 4 bits :
[[File:CSAdder4Bit.svg|centre|vignette|upright=1.5|Additionneur ''carry skip'' : bloc de 4 bits.]]
L'additionneur à saut de retenue est construit en assemblant plusieurs blocs de ce type.
[[File:BCSAdder16Bit.svg|centre|vignette|upright=3|Additionneur à saut de retenue.]]
===L'additionneur à sélection de retenue===
L''''additionneur à sélection de retenue''' utilise aussi des blocs, comme les additionneurs précédents. L'addition se fait en deux versions : une avec la retenue du bloc précédent valant zéro, et une autre version avec la retenue du bloc précédent valant 1. Il suffira alors de choisir le bon résultat avec un multiplexeur, une fois cette retenue connue. On gagne ainsi du temps en calculant à l'avance les valeurs de certains bits du résultat, sans connaître la valeur de la retenue. Petit détail : sur certains additionneurs à sélection de retenue, les blocs de base n'ont pas la même taille. Cela permet de tenir compte des temps de propagation des retenues entre les blocs.
[[File:Additionneur à sélection de retenue.png|centre|vignette|upright=1.5|Additionneur à sélection de retenue avec seulement deux blocs.]]
Dans les exemples du dessus, chaque sous-additionneur étaient des additionneurs à propagation de retenue. Mais ce n'est pas une obligation, et tout autre type d’additionneur peut être utilisé. Par exemple, on peut faire en sorte que les sous-additionneurs soient eux-mêmes des additionneurs à sélection de retenue, et poursuivre ainsi de suite, récursivement. On obtient alors un '''additionneur à somme conditionnelle''', plus rapide que l'additionneur à sélection de retenue, mais qui utilise beaucoup plus de portes logiques.
===Les additionneurs à anticipation de retenue===
Les '''additionneurs à anticipation de retenue''' calculent chaque retenue sans avoir à propager les retenues précédentes. Au lieu de calculer les retenues une par une, ils calculent toutes les retenues en parallèle, à partir des bits des opérandes. Une fois les retenues pré-calculées, il suffit de les additionner avec les deux bits adéquats, pour obtenir le résultat. Ces additionneurs sont composés de deux parties :
* un circuit qui pré-calcule les retenues, sans les propager, directement à partir des opérandes ;
* d'une couche d'additionneurs complets, qui additionnent chacun deux bits d'opérande et la retenue pré-calculée.
[[File:Additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Additionneur à anticipation de retenue.]]
Le calcul des retenues se fait en deux étapes. La première détermine si les retenues sont propagées ou générées, pour chaque colonne. Elle détermine, pour chaque colonne, les bits P et G qui indiquent respectivement : qu'une retenue a été générée sur cette colonne, que la colonne propage la retenue précédente. La seconde étape utilise ces signaux P et G pour déterminer la retenue sur chaque colonne. Le circuit de calcul des retenues est donc composé de deux parties : une couche de demi-additionneurs pour générer les signaux P et G, un circuit de calcul des retenues proprement dit.
[[File:Circuit complet d'un additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Circuit complet d'un additionneur à anticipation de retenue.]]
Pour rappel, la retenue de la colonne i, notée <math>C_i</math> est égale à :
: <math>G_i + (P_i . C_{i-1})</math>, avec G le signal de génération de retenue, P le signal de propagation de retenue.
Si on utilisait cette formule sans trop réfléchir, on retomberait sur un additionneur à propagation de retenue inutilement compliqué. L'astuce des additionneurs à anticipation de retenue consiste à remplacer le terme Ci−1 par sa valeur calculée avant. Par exemple, je prends un additionneur 4 bits. Je dispose de deux nombres A et B, contenant chacun 4 bits : A3, A2, A1, et A0 pour le nombre A, et B3, B2, B1, et B0 pour le nombre B. Si j'effectue les remplacements, j'obtiens les formules suivantes :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Ces formules nous permettent de déduire la valeur d'une retenue directement : il reste alors à créer un circuit qui implémente ces formules, et le tour est joué.
[[File:Four bit adder with carry lookahead.svg|centre|vignette|upright=1.5|Additionneur à anticipation de retenue de 4 bits.]]
Plus haut, j'ai dit que le circuit était composé de trois couches : une couche de demi-additionneur pour les signaux P et G ; un circuit d'anticipation de retenue, puis l'addition finale. Mais les deux bits d'opérandes sont déjà additionnés dans la couche de demi-additionneur, autant réutiliser le résultat pour l'additionner à la retenue. Pour éliminer toute redondance de ce type, il est possible d'utiliser des additionneurs partiels (des additionneurs où la sortie de retenue a été remplacée par deux sorties P et G). L'additionneur final est alors composé de plusieurs additionneurs complets de ce type, couplé à un circuit d'anticipation de retenue.
[[File:Cla4bits.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue de 4 bits.]]
Les additionneurs à anticipation de retenue sont plus rapides que les additionneurs à propagation de retenue. La raison est que les retenues sont calculées rapidement, dans un temps plus petit qu'en propageant la retenue. Pour rentrer dans les détails mathématiques, le temps de calcul n'est pas proportionnel au nombre de bits, mais proportionnel au logarithme de ce nombre de bits. Par contre, le gain en temps de calcul s'accompagne d'un cout en portes logiques conséquent. Le circuit qui calcule les retenues anticipées utilise beaucoup de portes logiques.
===Les additionneurs à calcul parallèle de préfixe===
Il existe plusieurs manières d'exploiter de telles redondances, exploitées dans les '''additionneurs à calcul parallèle de préfixe'''. L'optimisation apportée est de générer des signaux ''propagate'' et ''generate'' pour des groupes de 2, 3, 4, ..., N colonnes. Par exemple, il est possible de générer un signal ''P 0 vers 3'', qui indique si la retenue de la première colonne est propagée jusqu'à la 4ème colonne. Un autre exemple est un signal qui indique qu'une retenue a été générée entre les colonnes 4 à 7 génèrent une retenue ou non. En clair, les signaux P et G ont maintenant un intervalle, qui précise de quelle colonne vers quelle colonne se fait la propagation, ou entre quelles colonnes se fait la génération.
Il est possible de combiner les signaux P et G de deux groupes de colonne, s'ils sont contiguës (ils peuvent aussi se recouvrir). Par exemple, il est possible de calculer les bits P et G pour les colonnes 0 à 10, à partir des deux signaux P/G des colonnes 0-4 et 5-10. Pour cela, les équations sont assez simples. Si je prends deux groupes nommés A et B, avec A pour les colonnes de poids fort et B celles de poids faible, on a :
: <math>P_{sortie} = P_A . P_B</math>
: <math>G_{sortie} = G_A . P_A . G_B</math>
Pour les bits des colonnes 2, 4, 8, 16, 32, 64, et autres, le calcul est simple. L'idée est de grouper les colonnes par groupes de 2. Puis, on calcule chaque groupe de 4 bits à partir de deux groupes de 2. Puis les groupes de 8 bits sont calculés à partir de deux groupes de 4 bits, et ainsi de suite pour toute puissance de deux.
[[File:Squelette d'un additionneur à calcul parallèle de préfixe.jpg|centre|vignette|upright=2|Squelette d'un additionneur à calcul parallèle de préfixe]]
Les seules difficultés surviennent pour les colonnes dont le nombre n'est pas une puissance de deux. Il y a plusieurs manières pour subdiviser ces colonnes en intervalles plus petits et combiner le tout. Et elles donnent chacune des additionneurs différents. Ils portent les noms d'''additionneur de Ladner-Fisher'', d'''additionneur de Brent-Kung'', d'''additionneur de Kogge-Stone'', etc. Dans le cas général, l'additionneur de Kogge-Stone est un des plus rapide, mais aussi un des plus gourmand en circuits, alors que c'est l'inverse pour l'additionneur de Brent-Kung. Les deux additionneurs sont illustrés ci-dessous, pour des opérandes de 8 bits. Dans ceux-ci, chaque losange calculent les bits P et G, à partir des signaux P et G de deux groupes précédents.
{|
|[[File:Brent-kung-8-bit.png|vignette|Brent-kung-8-bit]]
|[[File:Kogge-stone-8-bit.png|vignette|upright=1.2|Kogge-stone-8-bit]]
|}
===Les additionneurs à anticipation de retenue multi-niveau===
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Dans cette section, nous allons voir des additionneurs conçus en assemblant des additionneurs plus simples, qui additionnent environ 4 à 5 bits, parfois plus, parfois moins. Ces additionneurs simples seront nommés ''blocs'' dans ce qui suit, et l'un d'entre eux est illustré ci-contre. Chaque bloc prend en entrée un morceau des deux opérandes à additionner, mais aussi une retenue d'entrée.
Les blocs sont tous identiques dans le cas le plus simple, mais il est possible d'utiliser des blocs de taille variable. Par exemple, le premier bloc peut avoir des opérandes de 6 bits, le second des opérandes de 7 bits, etc. Faire ainsi permet de gagner un petit peu en performances, si la taille de chaque bloc est bien choisie. La raison est une question de temps de propagation des retenues. La retenue met plus de temps à se propager à travers 8 blocs qu'à travers 4, ce qui prend plus de temps qu'à travers 2 blocs, etc. En tenir compte fait que la taille des blocs tend à augmenter ou diminuer quand on se rapproche des bits de poids fort.
Il est possible d'utiliser l'anticipation de retenue entre deux blocs. Par exemple, il est possible de créer un additionneur 16 bits en utilisant 4 additionneurs/blocs de 4 bits. L'additionneur utilisé peut être un additionneur à propagation de retenue, ou à anticipation de retenue, peu importe.
Pour cela, les additionneurs de 4 bits doivent fournir deux sorties P et G. Les deux sorties indiquent que le bloc soit a généré une retenue, soit a propagé la retenue entrante. Par propagé, on veut dire que la retenue entrante du bloc s'est retrouvée sur la sortie de retenue du bloc. C'est le cas si les 4 colonnes propagent la retenue, ce qui signifie que :
: <math>P_\text{4 bits} = P_0 \cdot P_1 \cdot P_2 \cdot P_3</math>
Pour la génération, cela veut dire qu'une colonne a générée une retenue, qui a été propagée jusqu'à la sortie. En clair, pour chaque colonne, il faut que le G = 1, et que les signaux P des colonnes soient à 1. Pour les 4 colonnes, cela donne :
: <math>G_\text{4 bits} = G_3 + G_2 \cdot P_3 + G_1 \cdot P_3 \cdot P_2 + G_0 \cdot P_3 \cdot P_2 \cdot P_1</math>
[[File:Cla4bitsPG.png|centre|vignette|upright=2|Additionneur à anticipation de retenue de 4 bits.]]
Pour combiner 4 blocs de 4 bits, on devra donc combiner 4 bits P et G, avec un circuit d'anticipation de retenue.
[[File:16-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 16 bits.]]
Il est même possible d'aller plus loin et de combiner 4 additionneurs précédents pour obtenir un additionneur 64 bits.
[[File:64-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 64 bits.]]
Le résultat est un additionneur à plusieurs niveaux d’anticipation de retenue. Une anticipation de retenue dans un blocs de 4 bits (facultative), entre blocs de 4 bits, entre blocs de 16 bits. La même logique peut être utilisée avec des blocs de taille différente de 4, 16 et 64 bits.
===Le calcul parallèle de la retenue===
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Utiliser de l'anticipation de retenue entre blocs utilise beaucoup de circuits. Aussi, une autre option est d'utiliser la propagation de retenue entre blocs. L'anticipation de retenue est utilisée à l'intérieur des blocs, pas entre eux. Par exemple, on peut utiliser 4 additionneurs à anticipation de retenue de 4 bits, et propager les retenues entre eux. Le premier bloc calcule une retenue, qui est propagée au second bloc. Le second bloc calcule lui aussi une retenue, qui est propagée au troisième bloc, etc.
[[File:4008 Functional Diagram.svg|vignette|upright=1|4008 Functional Diagram]]
Le gain en performance est significatif, mais il est possible de faire presque aussi rapide, tout en économisant beaucoup de circuits. L'idée est de simplifier les additionneurs 4 bits, afin de ne pas utiliser une anticipation de retenue complète. L'anticipation de retenue n'est utilisée que pour la retenue sortante, alors que les bits de somme sont calculées avec propagation de retenue. La retenue sortante est donc calculée en parallèle de l'addition. Chaque bloc contient, à côté d'un additionneur proprement dit, un circuit qui calcule la retenue sortante. Il existe de nombreuses manières de calculer la retenue sortante.
Le gain en portes logiques est assez significatif. En effet, on se passe de circuits d'anticipation de retenue pour les 4 bits de somme, on ne garde que celui pour la retenue sortante. Le fait d'utiliser la propagation de retenue pour calculer les 4 bits de somme est censé avoir un léger cout en performance, mais il se trouve que ce cout est très faible. En effet, le résultat n'est connu qu'une fois les retenues propagées jusqu'au dernier bloc. Et malgré l'anticipation des retenues, la propagation est assez lente. les additionneurs à propagation de retenue auront terminé leur travail bien avant.
Un exemple est celui de l'additionneur CMOS 4008, un additionneur de 4 bit. Il est composé en trois sections. Une première couche de demi-additionneurs calcule les signaux P et G utilisés à la fois pour l'anticipation de la retenue sortante, que pour calculer les bits de somme. Le circuit de calcul de la retenue sortante utilise l'anticipation de retenue. Le reste du circuit fait l'addition en propageant les retenues.
[[File:CMOS 4008, circuit découpé en sections.png|centre|vignette|upright=1.5|CMOS 4008, circuit découpé en sections]]
==L'addition signée et la soustraction==
Après avoir vu l'addition, il est logique de passer à la soustraction, les deux opérations étant très proches. Si on sait câbler une addition entre entiers positifs, câbler une soustraction n'est pas très compliqué. De plus, la soustraction permet de faire des additions de nombres signés.
===Le soustracteur pour opérandes entiers===
Pour soustraire deux nombres entiers, on peut adapter l'algorithme de soustraction utilisé en décimal, celui que vous avez appris à l'école. Celui-ci ressemble fortement à l'algorithme d'addition : on soustrait les bits de même poids, et on propage éventuellement une retenue sur la colonne suivante. À la différence de l'addition, la retenue est soustraite, et non ajoutée. La table de soustraction nous dit quel est le résultat de la soustraction de deux bits. La voici :
* 0 - 0 = 0 ;
* 0 - 1 = 1 et une retenue ;
* 1 - 0 = 1 ;
* 1 - 1 = 0.
[[File:Algebra1 05 fig014.svg|centre|vignette|Soustraction en binaire, avec les retenues en rouge.]]
La table de soustraction peut servir de table de vérité pour construire un circuit qui soustrait deux bits. Celui-ci est appelé un '''demi-soustracteur'''. Il ressemble beaucoup à un demi-additionneur, les différences se résumant à une porte NON ajoutée pour le calcul de la retenue.
[[File:Half Subtractor Vektor.svg|centre|vignette|Demi-soustracteur.]]
Comme pour l'additionneur, deux demi-soustracteurs peuvent être combinés pour donner un '''soustracteur complet'''. Le calcul de la retenue se fait en combinant les deux retenues des demi-soustracteurs avec une porte OU. Les soustracteurs complets sont utilisés pour créer des soustracteurs à propagation de retenue ou tout autre circuit soustracteur, sur le même modèle que les additionneurs.
[[File:FullSubtractor.svg|centre|vignette|upright=2|Soustracteur complet.]]
Vous l'aurez sans doute remarqué, mais le demi-soustracteur ressemble beaucoup au demi-additionneur. La seule différence est une porte NON ajoutée au bon endroit dans le demi-soustracteur.
{|
|[[File:Half-adder.svg|vignette|Demi-additionneur]]
|[[File:Half Subtractor Vektor.svg|vignette|upright=1.3|Demi-soustracteur]]
|}
La conséquence est qu'il est possible de créer un circuit capable de faire à la fois des additions et des soustractions. L'idée est de remplacer la porte NON du demi-soustracteur par un inverseur commandable. Si on envoie un zéro sur l'entrée de commande, l'inverseur commandable se comportera comme une porte OUI. Le circuit sera alors identique à un demi-additionneur. Si on envoie un 1 sur l'entrée de commande, l'inverseur commandable se comportera comme une porte NON. le circuit sera alors un demi-soustracteur. Il faut faire cela pour chaque demi-soustracteur, ce qui donne ceci pour un soustracteur complet. le résultat est alors appelé un '''additionneur-soustracteur complet'''.
[[File:Additionneur-soustracteur complet.png|centre|vignette|upright=2|Additionneur-soustracteur complet]]
===L'additionneur-soustracteur pour opérandes codées en complément à deux===
Étudions le cas de la soustraction en complément à deux, dans l'objectif de créer un circuit soustracteur. Vous savez sûrement que a−b et a+(−b) sont deux expressions équivalentes. Et en complément à deux, − b = not(b) + 1. Dit autrement, a − b = a + not(b) + 1. On pourrait se dire qu'il faut deux additionneurs pour faire le calcul, mais la majorité des additionneurs possède une entrée de retenue pour incrémenter le résultat de l'addition. Un soustracteur en complément à deux est donc simplement composé d'un additionneur et d'un inverseur.
[[File:Additionneur soustracteur.png|centre|vignette|upright=2|Soustracteur en complément à deux.]]
Il est possible de créer un circuit capable d'effectuer soit une addition, soit une soustraction : il suffit de remplacer l'inverseur par un inverseur commandable, qui peut être désactivé. On a vu comment créer un tel inverseur commandable dans le chapitre sur les circuits combinatoires. On peut remarquer que l'entrée de retenue et l'entrée de commande de l'inverseur sont activées en même temps : on peut fusionner les deux signaux en un seul.
[[File:Additionneur-soustracteur en complément à deux.png|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux.]]
Une implémentation alternative est la suivante. Elle remplace l'inverseur commandable par un multiplexeur.
[[File:4-bit ripple carry adder-subtracter.svg|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux, version alternative.]]
===L'additionneur-soustracteur pour opérandes codées en signe-magnitude===
Passons maintenant aux nombres codés en signe-valeur absolue, les deux opérandes étant notées A et B. Suivant les signes des deux opérandes, on a quatre cas possibles : A + B, A − B (B négatif), −A + B (A négatif) et −A − B (A et B négatifs). Une astuce est que le circuit n'a besoin que de calculer A + B et A − B : il peut les inverser pour obtenir − A − B ou B − A. A + B et A − B peuvent se calculer avec un additionneur-soustracteur, reste à corriger le résultat. Il suffit de lui ajouter un inverseur commandable pour obtenir le circuit d'addition finale.
[[File:Additionneur en signe-valeur absolue.png|centre|vignette|upright=2|Additionneur en signe-valeur absolue.]]
Toute la difficulté tient dans le calcul du bit de signe du résultat, quand interviennent des soustractions. Autant l'addition de deux nombres de même signe (A + B et −A − B) ne pose aucun problème, autant les soustractions posent problème (A − B et −A + B). Suivant que <math>A<B</math> ou que <math>A>B</math>, le signe du résultat ne sera pas le même. Déterminer le signe du résultat se fait en regardant les bits de débordement d'entier, comme on le verra plus bas.
===L'additionneur-soustracteur pour opérandes codées en représentation par excès===
Passons maintenant aux nombres codés en représentation par excès. On pourrait croire que ces nombres s'additionnent comme des nombres non-signés, mais ce serait oublier la présence du biais, qui pose problème. Dans les cas de nombres signés gérés avec un biais, voyons ce que donne l'addition de deux nombres :
: <math>( n_1 + biais ) + ( n_2 + biais ) = n_1 + n_2 + 2 \times biais</math>
Or, le résultat correct serait :
: <math>n_1 + n_2 + biais</math>
En effectuant l'addition telle quelle, le biais est compté deux fois. On doit donc le soustraire après l'addition pour obtenir le résultat correct.
Même chose pour la soustraction qui donne ceci :
: <math>(n_1 + biais) - (n_2 + biais) = n_1 - n_2</math>
Or, le résultat correct serait :
: <math> ( n_1 - n_2 ) + biais </math>
Il faut rajouter le biais pour obtenir l'exposant correct.
On a donc besoin de deux additionneurs/soustracteurs : un pour additionner/soustraire les représentations binaires des opérandes, et un autre pour ajouter/retirer le biais en trop/manquant.
==L'additionneur BCD==
Maintenant, voyons un additionneur qui additionne deux entiers au format BCD. Pour cela, nous allons devoir passer par deux étapes. La première est de créer un circuit capable d'additionneur deux chiffres BCD. Ensuite, nous allons voir comment enchaîner ces circuits pour créer un additionneur BCD complet.
===L'additionneur BCD qui fait l'opération chiffre par chiffre===
Nous allons commencer par voir un additionneur qui additionne deux chiffres en BCD, une sorte d'équivalent BCD de l'additionneur complet. Il fournit un résultat sur 4 bits et une retenue qui est mise à 1 si le résultat dépasse 10 (la limite d'un chiffre BCD). Les deux opérandes sont des chiffres BCD codés sur 4 bits et sont additionnés en binaire par un additionneur des plus normaux, similaire à ceux vus plus haut. Le résultat est alors un entier codé en binaire, sur 5 bits, qu'on corrige/convertit pour obtenir un chiffre BCD et une retenue sortante.
Pour corriger le résultat, une idée intuitive serait de prendre le résultat et de faire une division par 10. Le quotient donne la retenue, alors que le reste est le résultat, le chiffre BCD. Mais un circuit diviseur par 10 utilise beaucoup de portes logiques, ce qui ne vaut pas le coup. Une autre méthode détecte si le résultat est égal ou supérieur à 10, ce qui correspond à un "débordement" (on dépasse les limites d'un chiffre BCD). Si le résultat est plus petit que 10, il n'y a rien à faire : le résultat est bon et la retenue est de zéro. Par contre, si le résultat vaut 10 ou plus, il faut corriger le résultat et générer une retenue à 1.
Il faut donc ajouter un circuit qui détecte si le résultat est supérieur à 9, qui calcule directement la retenue. Ce circuit peut se fabriquer simplement à partir de sa table de vérité, ou en utilisant les techniques que nous verrons dans un chapitre ultérieur sur les comparateurs. La solution la plus simple est clairement d'utiliser la table de vérité, ce qui est très simple, assez pour être laissé en exercice au lecteur. Pour comprendre comment corriger le résultat, établissons une table de vérité qui associe le résultat et le résultat corrigé. L'entrée vaut au minimum 10 et au maximum 9 + 9 = 18. On considère la sortie comme un tout, la retenue étant un 5ème bit, le bit de poids fort.
{|class="wikitable"
|-
! colspan="5" | Entrée
!
! rowspan="10" |
! Retenue
! Résultat corrigé (sans retenue)
! interprétation de la sortie en binaire (retenue inclue)
|-
| 0 || 1 || 0 || 1 || 0
| (10)
| 1 || 0000
| (16)
|-
| 0 || 1 || 0 || 1 || 1
| (11)
| 1 || 0001
| (17)
|-
| 0 || 1 || 1 || 0 || 0
| (12)
| 1 || 0010
| (18)
|-
| 0 || 1 || 1 || 0 || 1
| (13)
| 1 || 0011
| (19)
|-
| 0 || 1 || 1 || 1 || 0
| (14)
| 1 || 0100
| (20)
|-
| 0 || 1 || 1 || 1 || 1
| (15)
| 1 || 0101
| (21)
|-
| 1 || 0 || 0 || 0 || 0
| (16)
| 1 || 0110
| (22)
|-
| 1 || 0 || 0 || 0 || 1
| (17)
| 1 || 0111
| (23)
|-
| 1 || 0 || 0 || 1 || 0
| (18)
| 1 || 1000
| (24)
|}
En analysant le tableau, on voit que pour corriger le résultat, il suffit d'ajouter 6. La raison est que le résultat déborde d'un nibble à 16 en binaire, mais à 10 en décimal : il suffit d'ajouter la différence entre les deux, à savoir 6, et le débordement binaire fait son travail. Donc, la correction après une addition est très simple : si le résultat dépasse 9, on ajoute 6.
On peut maintenant implémenter l'additionneur BCD, en combinant le comparateur avec 10, le circuit de correction, et l'additionneur. La première solution calcule deux versions du résultat : la version corrigée, la version normale. Le choix entre les deux est réalisée par un multiplexeur, commandé par le comparateur.
[[File:Additionneur BCD.png|centre|vignette|upright=1.5|Additionneur BCD]]
L'autre solution utilise un circuit commandable qui soit additionne 6, soit ne fait rien. Le choix entre les deux est commandé par le bit calculé par le comparateur.
[[File:Additionneur BCD, seconde version.png|centre|vignette|upright=1.5|Additionneur BCD, seconde version.]]
Une version alternative du circuit précédent est la suivante. Il contient deux additionneurs : un pour additionner les deux chiffres BCD, un autre pour additionner 6 si besoin. Le résultat du comparateur est directement utilisé pour générer l'opérande du second additionneur : 0 ou 6. Le circuit est simple à concevoir, mais gaspille beaucoup de circuit. Idéalement, il vaudrait mieux utiliser un circuit combinatoire d'addition avec une constante.
[[File:Adder 4 Bit BCD.svg|centre|vignette|upright=2|Additionneur BCD, circuit complet.]]
Pour obtenir un additionneur BCD complet, il suffit d’enchaîner les additionneurs précédents, comme on le ferait avec les additionneurs complets dans un additionneur à propagation de retenue. Au final, l'additionneur BCD est beaucoup plus compliqué qu'un additionneur normal, car il rajoute un comparateur ">9", un petit additionneur pour ajouter 6 et éventuellement d'autres circuits. De plus, il est difficile d'appliquer les optimisations disponibles sur les additionneurs non-BCD. Notamment, les circuits d'anticipation de retenue sont totalement à refaire et le résultat est relativement compliqué. C'est ce qui explique pourquoi le BCD a progressivement été abandonné au profit du binaire simple.
La soustraction en BCD se fait comme en binaire : le nombre à soustraire est remplacé par son complément, le circuit additionne le complément et l'autre opérande, le débordement d'entier fait que le résultat marche. Sauf qu'ici, le complément est un complément à 9. Il se calcule chiffre par chiffre : chaque chiffre est remplacé par (9 - le chiffre en question).
===L'additionneur BCD par ajustement décimal===
L'additionneur BCD précédent effectuait son travail chiffre BCD par chiffre BCD, mais il existe des additionneurs BCD qui font autrement. Sur les premiers processeurs x86, il n'y avait pas d'opération d'addition BCD proprement dit, seulement une addition binaire normale de 8, 16 ou 32 bits. Par contre, elle était secondée par une opération dite d''''ajustement décimal''' qui transformait un nombre binaire en nombre codé en BCD. L'opération d'ajustement décimal prenait un opérande de 8 bits codé en binaire et fournissait un résultat de la même taille, c'est à dire deux chiffres BCD. Effectuer une addition BCD demandait donc de faire deux opérations à la suite : une addition binaire simple, suivie par l'opération d'ajustement décimal. Cela permettait de gérer des nombres entiers en binaire usuel et des entiers BCD sans avoir deux instructions d'addition séparées pour les deux, sans compter que cela simplifiait aussi les circuits d'addition.
L'ajustement décimal s'effectue en ajoutant une constante bien précise à l'opérande à convertir en BCD. L'idée est que la constante est découpée en morceaux de 4 bits, correspondant chacun à un chiffre BCD de l'opérande, chaque morceau contenant soit un 0, soit 6. Cela permet d'ajouter soit 0, soit 6, à chaque chiffre BCD, et donc de le corriger. La propagation des retenues d'un chiffre à l'autre est effectuée automatiquement par l'addition binaire de la constante. L'opération d'ajustement décimal calcule automatiquement la constante. Elle découpe l'opérande en ''nibbles'', vérifie si chaque ''nibble'' est supérieur ou égal à 10, puis détermine la valeur de chaque ''nibble'' de la constante finale. Par exemple, si je prends l'opérande 1001 1110, le ''nibble'' de poids faible déborde, alors que celui de poids fort non. La constante sera donc 0000 0110 : 0x06. Inversement, si le ''nibble'' de poids fort déborde et pas celui de poids faible, la constante sera alors 0x60. Et la constante est de 0x66 si les deux ''nibbles'' débordent, de 0x00 si aucun ne déborde.
Le circuit d’ajustement décimal est donc composé de trois étapes : deux étapes pour calculer la constante, et un circuit d'addition pour additionner cette constante au nombre de départ. La première étape découpe l'opérande en morceaux de 4 bits, en chiffres BCD, et vérifie si chacun d'entre eux vaut 10 ou plus. La seconde étape prend les résultats de la première étape, et les combine pour calculer la constante. Enfin, on trouve l'addition finale, qui était réalisée par un circuit d'addition utilisé à la fois pour l'ajustement décimal et l'addition binaire. La différence entre une addition normale et une opération d'ajustement décimal tient dans le fait que les deux premières étapes sont désactivées dans une addition normale.
[[File:Additionneur BCD parallèle.png|centre|vignette|upright=1.5|Additionneur BCD parallèle]]
===L'additionneur biquinaire===
Les entiers BCD ne sont qu'un des encodages hybrides entre décimal et binaire. L'encodage biquinaire est l'un d'entre eux et nous allons faire un rappel rapide à ce sujet. Pour simplifier, un chiffre encodé en biquinaire est composé de deux parties : un bit, couplé à une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits (0, 1, 2, 3 et 4). Le bit indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9.
Additionner deux nombres de biquinaire demande donc d'additionner deux parties quinaires encodées en ''one-hot'' et d'additionner deux bits. Mais attention : il faut tenir compte de la retenue de l'addition des parties quinaires. Et idéalement, il faut aussi tenir compte d'une retenue entrante, provenant de l'addition de la colonne de chiffres précédente. Toute la difficulté vient de la création de l'additionneur ''one-hot''. Heureusement, vu qu'il n'y a que 4-5 bits à additionner, il est souvent fabriqué à partir de sa table de vérité.
[[File:Additionneur bi-quinaire.png|centre|vignette|upright=2|Additionneur bi-quinaire]]
Un avantage du biquinaire est que le calcul du complément à 9 est très simple. Il faut pour cela : inverser la partie binaire avec une porte NON, puis inverser l'ordre des bits de la partie quinaire. Concrètement, le bit de poids faible devient le bit de poids fort, et ainsi de suite. Par exemple, une partie quinaire 01000 devient 00010, 10000 devient 00001, 00100 ne change pas, etc. Le tout peut se calculer avec une porte NON et 5 multiplexeurs.
===L'additionneur BCD avec calculs intermédiaires en biquinaire===
L'ordinateur IBM 1401, un ancien ''mainframe'' des années 60, utilisait un additionneur BCD un peu particulier. Les nombres étaient encodés en BCD dans la mémoire de l'ordinateur, mais les circuits de calcul utilisaient la représentation biquinaire. Lors d'un calcul, le processeur de l'ordinateur traduisait les chiffres BCD en représentation biquinaire, faisait une addition en biquinaire, avant de traduire le résultat en BCD normal.
Pour être précis, l'IBM 1401 utilisait une variante du biquinaire. L'encodage biquinaire de l'IBM 1401 est le suivant : la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre pair encodé par la partie quinaire. Si l'IBM 1401 utilisait cette variante du biquinaire, c'est car elle donnait des circuits de conversion BCD-biquinaire plus économes en portes logiques et plus rapides.
La partie binaire est le bit de poids faible du chiffre BCD, la partie biquinaire est calculée par un simple décodeur qui prend en entrée le chiffre BCD, amputé de son bit de poids faible. La traduction inverse demande d'utiliser un encodeur, à la place du décodeur. Par contre, le circuit d'addition biquinaire était plus compliqué du fait de la gestion des retenues. L'addition des parties binaires et quinaires se faisait en parallèle, dans deux additionneurs séparés. Cependant, l'addition des parties binaire fournit une retenue, qu'il faut prendre en compte. Pour cela, l'IBM 1401 disposait d'un troisième additionneur qui fournissait le résultat final, encodé en biquinaire.
[[File:Additionneur bi-quinaire de l'IBM 1401.png|centre|vignette|upright=2|Additionneur biquinaire de l'IBM 1401]]
Une implémentation moderne demanderait d'utiliser des portes ET combinées à des portes OU, le circuit pouvant être construit simplement à partir de sa table de vérité. Sur l'IBM 1401, le circuit était cependant différent, en raison de l'utilisation de OU câblés, des croisements de fils qui fonctionnent comme des portes OU, que nous n'avons pas encore vu pour le moment, mais qui seront détaillés dans quelques chapitres. Les OU câblés étaient utilisés pour simplifier le design du circuit, mais demandaient des portes logiques spécifiques, ce qui collait avec le fait que ce ''mainframe'' utilisait des transistors en Germanium. L'implémentation exacte est décrite dans cet article de blog, mais je ne recommande sa lecture qu'à ceux qui savent ce qu'est un OU câblé :
* [https://www.righto.com/2015/10/qui-binary-arithmetic-how-1960s-ibm.html Qui-binary arithmetic: how a 1960s IBM mainframe does math].
==Les débordements d'entier lors d'une addition/soustraction==
Les instructions arithmétiques manipulent des entiers codés sur un nombre fixe de bits, qui ne peuvent prendre leurs valeurs que dans un intervalle. Pour les nombres positifs, un ordinateur qui code ses entiers sur n bits pourra coder tous les entiers allant de 0 à <math>2^n -1</math>. Pour les nombres négatifs, l'intervalle est différent et dépend de la représentation utilisée. Dans le cas général, l'ordinateur peut coder les valeurs comprises de <math>N_\text{min}</math> à <math>N_\text{max}</math>. Si le résultat d'un calcul sort de cet intervalle, il ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math> , alors que la valeur basse vaut <math>N_\text{min} - 1</math> (avec <math>N_\text{max}</math> et <math>N_\text{min}</math> respectivement la plus grande et la plus petite valeur codable par l'ordinateur).
===La correction des débordements d'entier : l'arithmétique saturée===
Quand un débordement d'entier survient, tous les circuits de calcul ne procèdent pas de la même manière. Dans les grandes lignes, il y a deux réactions possibles : soit on corrige automatiquement le résultat du débordement, soit on ne fait rien et on se contente de détecter le débordement.
Si le débordement n'est pas corrigé automatiquement, le circuit ne conserve que les bits de poids faibles du résultat. Les bits en trop sont simplement ignorés. On dit qu'on utilise l''''arithmétique modulaire'''. Le problème avec ce genre d'arithmétique, c'est qu'une opération entre deux grands nombres peut donner un résultat très petit. Par exemple, si je dispose de registres 4 bits et que je souhaite faire l'addition 1111 + 0010 (ce qui donne 15 + 2), le résultat est censé être 10001 (17), ce qui est un résultat plus grand que la taille d'un registre. En conservant les 4 bits de poids faible, j’obtiens 0001 (1). En clair, un résultat très grand est transformé en un résultat très petit. Cela peut poser problèmes si on travaille uniquement avec des nombres positifs, mais c'est aussi utilisé pour coder des nombres en complément à deux.
D'autres circuits utilisent ce qu'on appelle l''''arithmétique saturée''' : si un calcul génère un débordement, on arrondi le résultat au plus grand entier supporté par le circuit. Les circuits capables de calculer en arithmétique saturée sont un peu plus complexes, vu qu'il faut rajouter des circuits pour corriger le résultat en cas de débordement. Il suffit généralement de rajouter un ''circuit de saturation'', qui prend en entrée le résultat et le corrige en cas de débordement. Ce circuit de saturation met la valeur maximale en sortie si un débordement survient, mais se contente de recopier le résultat du calcul sur sa sortie s'il n'y a pas de débordement. Typiquement, il est composé d'une couche de multiplexeurs, qui sélectionnent quelle valeur mettre sur la sortie : soit le résultat du calcul, soit le plus grand nombre entier géré par le processeur, soit le plus petit (pour les nombres négatifs/soustractions).
L'arithmétique saturée est utilisée pour les additions et soustractions, mais c'est plus rare pour les multiplications/divisions. Une des raisons est que le résultat d'une addition/soustraction prend un bit de plus que le résultat, là où les multiplications doublent le nombre de bits. Quand une addition déborde, le résultat réel est proche de la valeur maximale codable. mais quand une multiplication déborde, le résultat peut parfois valoir 200 à 60000 fois plus que la valeur maximale codable. Les calculs avec une valeur saturée/corrigée sont donc crédibles pour une suite d'additions, mais pas pour une suite de multiplications.
===La détection des débordements entiers===
Quand un débordement d'entier a eu lieu, il vaut mieux que l'additionneur prévienne ! Pour cela, l'additionneur a une '''sortie de débordement''', parfois nommée ''Overflow'', dont la valeur indique si l'addition a généré un débordement d'entier ou non. Reste que détecter un débordement ne se fait pas de la même manière selon que l'on parle d'un additionneur non-signé ou signé.
Pour les additionneur non-signés, l'additionneur calcule un bit de plus que ce qui est supporté par l'ordinateur. Par exemple, un additionneur 32 bits fournit un résultat sur 33 bits, un débordement d'entier a lieu quand le 33ème bit est à 1. Précisément, la sortie de débordement n'est autre que la retenue finale, celle fournie par le dernier additionneur complet. Le seul type de débordement possible est un débordement par le haut, où le résultat dépasse la valeur maximale. Avec l'arithmétique saturée, le circuit de saturation consiste en une seule couche de multiplexeurs, voire en un circuit de mise à la valeur maximale tel que vu dans le chapitre sur les opérations bits à bits.
[[File:Gestion des débordements d'entiers.png|centre|vignette|upright=2.5|Gestion des débordements d'entiers lors d'une addition non-signée.]]
Pour les additionneurs non-signés, la gestion des débordements d'entiers dépend fortement de la représentation signée. Nous allons étudier le cas du complément à deux. Si vous vous rappelez le chapitre 1, les calculs sur des nombres en complètement à deux utilisent les règles de l'arithmétique modulaire, c'est une condition nécessaire. À priori, on peut penser que dans ces conditions, les débordements d'entiers sont une chose parfaitement normale, qui nous permet d'avoir des résultats corrects. Néanmoins, certains débordements d'entiers peuvent survenir malgré tout et produire des bugs assez ennuyeux.
Si l'on tient en compte les règles du complément à deux, on sait que le bit de poids fort (le plus à gauche) permet de déterminer si le nombre est positif ou négatif : il indique le signe du nombre. Tout se passe comme si les entiers en complément à deux étaient codés sur un bit de moins, et avaient leur longueur amputé du bit de poids fort. Si le résultat d'un calcul écrase le bit de poids fort, il y a un débordement d'entiers. Il existe une règle simple qui permet de détecter ces débordements d'entiers. L'addition de deux nombres positifs ne peut pas être un nombre négatif. Si on additionne deux nombres dont le bit de signe est à 0 et que le bit de signe du résultat est à 1, on est en face d'un débordement d'entiers. Même chose pour deux nombres négatifs : le résultat de l'addition ne peut pas être positif. On peut résumer cela en une phrase : si deux nombres de même signe sont ajoutés, un débordement a lieu quand le bit du signe du résultat a le signe opposé.
Modifier les circuits d'au-dessus pour qu'ils détectent les débordements en complément à deux est simple comme bonjour : il suffit créer un petit circuit combinatoire qui prenne en entrée les bits de signe des opérandes et du résultat, et qui fasse le calcul de l'indicateur de débordements. Si l'on rédige sa table de vérité, on doit se retrouver avec la table suivante :
{|class="wikitable"
|-
!Entrées
!Sortie
|-
|000||0
|-
|001||1
|-
|010||0
|-
|011||0
|-
|100||0
|-
|101||0
|-
|110||1
|-
|111||0
|}
L'équation de ce circuit est la suivante, avec <math>S_a</math> et <math>S_b</math> les signes des deux opérandes, et <math>C_i</math> la retenue de la colonne précédente :
: <math> ( S_a . S_b . \overline{C_i} ) + ( \overline{S_a} . \overline{S_b} . C_i )</math>
En simplifiant, on obtient alors :
: <math>( S_a . S_b ) \oplus C_i</math>
Or, il se trouve que <math>S_a . S_b</math> est tout simplement la retenue en sortie du dernier additionneur, que nous noterons <math>C_o</math>. On trouve donc :
: <math>C_o \oplus C_i</math>
Il suffit donc de faire un XOR entre la dernière retenue et la précédente pour obtenir le bit de débordement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de décalage et de rotation
| prevText=Les circuits de décalage et de rotation
| next=Les circuits de comparaison
| nextText=Les circuits de comparaison
}}
</noinclude>
rnw8kgwv276kqrool4cqn9zcf0xyb5p
768662
768661
2026-06-25T19:48:51Z
Mewtow
31375
/* Les additionneurs à calcul parallèle de préfixe */
768662
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir les circuits capables de faire une addition ou une soustraction, ainsi que quelques circuits spécialisés. Précisons cependant que les fabricants de processeurs travaillent d'arrache-pied pour trouver des moyens de rendre ces circuits de calcul plus rapides et plus économes en énergie. Autant vous dire que les circuits que vous allez voir sont vraiment des circuits qui font pâle figure comparé à ce que l'on peut trouver dans un vrai processeur commercial !
==Les circuits pour additionner 2 ou 3 bits==
L'addition se fait en binaire de la même manière qu'en décimal. On additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. La soustraction fonctionne sur le même principe, sur le même modèle qu'en décimal.
[[File:Binary Addition Demonstration.svg|centre|vignette|Exemple d'addition en binaire.]]
En clair, additionner deux nombres demande d'additionner 2 bits et une retenue sur chaque colonne, et de propager les retenues d'une colonne à l'autre. La propagation des retenues est quelque chose de simple en apparence, mais qui est sujet à des optimisations extraordinairement nombreuses. Aussi, pour simplifier l'exposition, nous allons voir comment gérer une colonne avant de voir comment sont propagées les retenues.
[[File:Full Adder Block.svg|vignette|upright=1|class=transparent|Additionneur complet.]]
Si on effectue une addition en colonne, on doit additionner les deux bits sur la colonne, mais aussi additionner une éventuelle retenue. Il faut donc créer un circuit qui additionne trois bits : deux bits de données, plus une retenue. Ce circuit qui additionne trois bits est appelé un '''additionneur complet'''. Il fournit en sortie deux bits : un bit de somme et une retenue sortante. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|-
||0||0||0|| ||0||0
|-
||0||0||1|| ||0||1
|-
||0||1||0|| ||0||1
|-
||0||1||1|| ||1||0
|-
||1||0||0|| ||0||1
|-
||1||0||1|| ||1||0
|-
||1||1||0|| ||1||0
|-
||1||1||1|| ||1||1
|}
Il est possible d'utiliser un tableau de Karnaugh pour traduire la table de vérité, mais elle donne un résultat assez compliqué. La retenue et le bit de somme sont calculés à part. La retenue est calculée avec trois portes ET et une porte OU. Le bit de somme est calculé lui avec 4 portes ET, une porte OU, et trois portes NON.
[[File:Full Adler with 3 entries.png|centre|vignette|upright=2|Additionneur complet, fabriqué à partir de la table de vérité.]]
D'autres méthodes donnent des résultats plus compréhensibles. Nous allons les voir dans la suite de cette section.
===L'additionneur complet basé sur des demi-additionneurs===
[[File:1-bit half-adder.svg|class=transparent|vignette|Demi-addtionneur.]]
Nous avons déjà vu comment additionner deux bits dans le chapitre sur les incrémenteurs, mais quelques rappels ne seront pas de trop. Le '''demi-additionneur''' est un circuit qui additionne deux bits. Il implémente la table d'addition, qui est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Mais un demi-additionneur n'additionne que deux bits, il manque de quoi additionner la retenue. Une solution pour cela est d'enchaîner deux demi-additionneurs : un qui additionne les deux bits de données, et un second qui additionne la retenue au résultat du précédent. La retenue finale se calcule en combinant les sorties de retenue des deux demi-additionneurs, avec une porte OU. Le résultat est un additionneur complet fabriqué avec deux demi-additionneurs. Pour vous en convaincre, établissez la table de vérité de ce circuit, vous verrez que ça marche.
[[File:Full Adder Blocks.svg|centre|vignette|upright=2|Additionneur complet fabriqué avec deux demi-additionneurs.]]
[[File:Half-adder.svg|vignette|upright=1|Circuit d'un demi-additionneur naïf.]]
L'implémentation naïve d'un demi-additionneur utilise une porte XOR et une porte ET, comme illustré ci-contre. Le circuit s'obtient en faisant uen traduction littérale de la table de vérité du circuit. Si je dis qu'elle est naïve, c'est car il est possible de l'optimiser, de manière à éliminer des redondances cachées. Mais nous avions déjà vu cela dans le chapitre sur les incrémenteurs, aussi laissons cela pour plus tard.
En combinant deux demi-additionneurs de ce type, on obtient l'additionneur complet suivant. Les deux sorties de retenue des demi-additionneurs sont combinées avec une porte OU, pour calculer la retenue finale.
{| class="flexible"
|[[File:Full Adder Modules.svg|class=transparent|Composition d'un additionneur complet. On voit bien que celui-ci est composé de deux demi-additionneurs, en rouge et en bleu, auxquels on a ajouté une porte OU pour calculer la retenue finale.]]
|[[File:Full-adder.svg|300px|class=transparent|Circuit d'un additionneur complet.]]
|}
Le fait de combiner les deux retenues avec une porte OU n'est pas ce qu'il y a de plus intuitif. La table de vérité nous dit que ça fonctionne, mais on comprend mal pourquoi. Mais tout devient plus clair quand on sait que les deux retenues ne peuvent pas être à 1 en même temps. Même dans l'addition 1 + 1 + 1, seule une retenue est à 1. Soit l'addition des deux bits d'opérande donne naissance à une retenue, soit c'est l'addition ''résultat + retenue entrante''. La retenue sortant est donc à 1 quand une des deux addition donne une retenue. D'où l'usage d'une porte OU. Remarquez qu'une porte XOR donne le même résultat, vu que le cas où les deux retenues sont à 1 n'est jamais rencontré.
===La propagation et la génération des retenues===
L'additionneur complet que nous allons voir dans cette section sert à introduire les concepts de propagation, génération et absorption de retenue. Il s'agit de concepts très importants quand on étudie les additionneurs. l'idée part d'un principe très simple : la retenue sortante dépend de la retenue d'entrée. La relation entre les deux se résume à trois cas, qui dépendent de la valeur des deux bits additionnés, nommés A et B.
* Dans le premier cas, la retenue entrante est égale à la retenue sortante. On dit que la retenue entrante est propagée par l'additionneur.
* Dans le second cas, la retenue est forcée à 0 : la retenue sortante vaut 0, peu importe la valeur de la retenue entrante.
* Dans le troisième cas, la retenue est forcée à 1 : la retenue entrante vaut 1, peu importe la valeur de la retenue entrante.
[[File:Signaux P et G fournis par un demi-additionneur.png|vignette|Signaux P et G fournis par un demi-additionneur]]
Maintenant, créons un circuit qui nous dise si une retenue est propagée ou générée. Il a deux bits de sortie, nommés P et G : P pour ''Propagate'', G pour ''Generate''. Le bit P est à 1 si la retenue entrante est propagée, il est à 0 sinon. Le bit G est à 1 si une retenue est générée, à 0 sinon. Une retenue est considérée comme absorbée si elle n'est pas ni propagée ni générée, pas besoin d'un troisième bit pour gérer ce cas.
Il se trouve que ce circuit n'est autre qu'un demi-additionneur ! Pour vous en rendre compte, regardez la table de vérité d'un additionneur complet, illustrée ci-dessous. Lorsque les deux bits d'opérande sont à 0, la retenue sortante vaut toujours 0. Si ils sont tous deux à 1, alors la retenue sortante vaut 1. S'ils sont différents, alors retenues sortante et entrante sont égales. Le bit P est donc généré par une simple porte XOR. Quant au bit G, il est à 1 si les deux bits d'opérandes sont à 1, ce qui correspond à une porte ET. Il se trouve que ces deux portes forment un demi-additionneur !
{|class="wikitable"
|-
! Opérande 1 !! Opérande 2 !! Retenue entrante !! !! Retenue sortante
|- class="f_rouge"
||0||0||0|| ||0
|- class="f_rouge"
||0||0||1|| ||0
|- class="f_bleu"
||0||1||0|| ||0
|- class="f_bleu"
||0||1||1|| ||1
|- class="f_bleu"
||1||0||0|| ||0
|- class="f_bleu"
||1||0||1|| ||1
|- class="f_vert"
||1||1||0|| ||1
|- class="f_vert"
||1||1||1|| ||1
|}
Ensuite, créons un circuit qui prend ces deux sorties P et G, et calcule la retenue sortante en fonction. Le circuit en question a trois entrées : la retenue entrante, les deux bits P et G. La retenue sortante vaut 1 soit si une retenue est générée, soit si la retenue entrante est propagée et qu'elle vaut 1. En notant <math>{C_{in}}</math> et <math>{C_{out}}</math> les retenues entrantes et sortante, on a :
: <math>{C_{out}} = G + \left( P . C_{in} \right)</math>
Le circuit est donc composé d'une porte OU et d'une porte ET. En combinant un demi-additionneur avec le circuit de calcul de retenue sortante vu plus haut, on a :
[[File:Additionneur complet avec propagation et génération de retenue.png|class=transparent|centre|vignette|upright=2|Additionneur complet avec propagation et génération de retenue.]]
Certes, on retombe sur le circuit vu plus haut. Mais il est possible de remplacer le circuit en orange par un autre. Par exemple, il est possible de le remplacer par multiplexeur, qui choisit entre la retenue générée et la retenue entrante (propagée). Le choix se fait selon la valeur du bit P, qui chosiit entre propager la retenue et la générer.
[[File:Additionneur crée avec un multiplexeur.png|centre|vignette|upright=1.5|Additionneur crée avec un multiplexeur]]
Le circuit exact est illustré ci-dessous. Il semble utiliser plus de portes logiques que l'additionneur complet précédent. Cependant, nous verrons dans quelques chapitres qu'il est possible d'implémenter un multiplexeur avec seulement 6 transistors, voire moins ! L'implémentation utilise des portes à transmission, mais nous en reparlerons dans le chapitre sur les transistors, quand nous verrons les additionneurs à ''Manchester Carry Chain''. Au passage, une variante de ce circuit a été utilisée dans le processeur processeur 8086 d'Intel, comme on le verra dans le chapitre suivant.
[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
[[File:FulladderPG.png|vignette|upright=1|Additionneur complet avec deux sorties P et G.]]
Pour finir, sachez qu'il existe des additionneurs qui fournissent : le bit de résultat, deux sorties P et G qui indiquent si l'addition propage ou génère une retenue. Il ne s'agit pas d'additionneurs complets, car il manque de quoi calculer la retenue sortante. De tels additionneurs seront appelés des '''additionneurs partiels'''. De tels additionneurs sont utilisés dans certains additionneurs pour gagner en performance.
Enfin, il existe aussi ce que j'ai décidé d'appeler des '''additionneurs P/G''', qui sont des additionneurs complets auxquels on a ajouté deux sorties P et G, en plus de la sortie de retenue. Leur circuit est le même que celui d'un additionneur complet, auquel on a ajouté deux fils. Vu que ces deux sorties sont fournies par le premier demi-additionneur, ajouter ces deux sorties demande d'ajouter des fils, pas de portes logiques. De tels additionneurs sont aussi utilisés dans certains additionneurs pour gagner en performance.
===L'additionneur complet basé sur une porte à majorité===
Maintenant, voyons une dernière implémentation possible de l'additionneur complet. Mais avant de voir comment l'additionneur est implémenté, nous allons devoir faire un petit focus sur la retenue sortante. Reprenons la table de vérité, et regardons ce qu'il en est pour la retenue sortante uniquement.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante
|-
||0||0||0|| ||0
|-
||0||0||1|| ||0
|-
||0||1||0|| ||0
|-
||0||1||1|| ||1
|-
||1||0||0|| ||0
|-
||1||0||1|| ||1
|-
||1||1||0|| ||1
|-
||1||1||1|| ||1
|}
Vous remarquerez que la retenue sortante est égale au bit majoritaire, parmi les trois bits d'opérande. Si deux ou trois bits sont à 1, la retenue sortante vaut 1. Et réciproquement avec 0. Le circuit de calcul de la retenue peut donc être remplacé par une '''porte à majorité'''.
[[File:Additionneur crée avec une porte à majorité.png|centre|vignette|upright=2|Additionneur crée avec une porte à majorité]]
Mais une porte à majorité est assez gourmande en circuit, ce qui fait qu'un additionneur pareil ne serait pas pratique, ni utile. Du moins, ce serait le cas s'il n'y avait pas une possibilité d'optimisation extrêmement intéressante. Il est possible de calculer le 'bit de somme' à partir de la retenue sortante ! En effet, le bit de somme est l'inverse de la retenue sortante, sauf dans deux cas : les trois bits d'entrée sont à 0, où ils sont tous à 1. Le bit de somme vaut 0 dans le premier cas, 1 dans le second cas.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|- class="f_bleu"
||0||0||0|| ||0||0
|- class="f_vert"
||0||0||1|| ||0||1
|- class="f_vert"
||0||1||0|| ||0||1
|- class="f_vert"
||0||1||1|| ||1||0
|- class="f_vert"
||1||0||0|| ||0||1
|- class="f_vert"
||1||0||1|| ||1||0
|- class="f_vert"
||1||1||0|| ||1||0
|- class="f_rouge"
||1||1||1|| ||1||1
|}
En clair, 6 lignes de la table de vérité sur 8 peuvent se calculer avec une porte à majorité, le reste demandant quelques portes logiques pour faire la correction. L'implémentation en circuit demande donc trois choses :
* de calculer la retenue sortante et de quoi l'inverser ;
* un circuit qui vérifie si tous les bits opérande valent 0 : une porte NOR ou OU fait l'affaire ;
* un circuit qui vérifie s'ils valent tous 1, à savoir une porte ET.
Ensuite, on combine le résultat des trois circuits précédents pour obtenir le résultat final. La combinaison est le fait du circuit en jaune dans le schéma ci-dessous.
[[File:Full adder basé sur une modification de la retenue.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue]]
Le circuit en jaune est assez simple. Quand la porte ET sort un 1, la sortie doit être forcée à 1. Par contre, quand la porte OU sort un zéro, la sortie doit être forcée à 0. Il suffit donc d'enchainer un circuit de mise à 1 et un circuit de mise à 0. Nous avons vu dans le chapitre sur les opérations de masquage qu'il s'agit respectivement d'une porte OU et d'une porte ET.
[[File:Full adder basé sur une modification de la retenue interne, au complet.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue interne, au complet]]
Divers processeurs utilisaient des additionneurs complets de ce type : le fameux Z80, l'Intel 4004 et de l'Intel 8008 sont les exemples les plus notables. Pourtant, on pourrait s'étonner qu'un tel circuit ait existé. Il utilise beaucoup de portes logiques, a une profondeur logique supérieure : il n'a rien d'avantageux. Sauf qu'il était utilisé sur d'anciens processeurs, qui utilisaient la technologie dite TTL, différente de la technologie CMOS des transistors modernes. Et avec la technologie TTL, il est possible de fusionner plusieurs portes logiques ET et NOR en une seule porte logique ET/OU/NON ! Un additionneur complet construit ainsi ne prenait que deux portes logiques : une pour le calcul de la retenue sortante, une autre pour le reste du circuit.
==L'addition non signée==
Voyons maintenant un circuit capable d'additionner deux nombres entiers: l''''additionneur'''. Dans la version qu'on va voir, ce circuit manipulera des nombres strictement positifs. L'addition des nombres codés en complètement à deux sera vu dans une section ultérieure.
===L'additionneur série===
Il est possible d'additionner deux nombres bit par bit, gr$ace à un additionneur complet associé à plusieurs registres à décalages. Les opérandes sont placées chacune dans un registre à décalage, et l'additionneur complet utilise les bits sortants de ces registres à décalage. Le bit du résultat est envoyé au registre à décalage pour le résultat. La retenue de l'addition est stockée dans une bascule de 1 bit, en attente du prochain cycle d'horloge. Un tel additionneur est appelé un '''additionneur série'''. Il a été utilisé sur d'anciens ordinateurs dans les années 50-60, aussi bien des prototypes que des ordinateurs commerciaux.
[[File:Additionneur série.jpg|centre|vignette|upright=2.5|Additionneur série.]]
===L'additionneur à propagation de retenue===
L''''additionneur à propagation de retenue''' est aussi appelé l'additionneur ''ripple carry'', c'est son nom anglais. J'utiliserais occasionnellement le nom anglais dans ce qui suit, car celui-ci est plus court. Ilutilise un additionneur complet pour chaque colonne de l'addition. Pour gérer les retenues, il suffit ainsi de câbler des additionneurs complets les uns à la suite des autres, chacun envoyant sa retenue à la colonne suivante.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Notons la présence de la retenue sortante, qui est utilisée pour détecter les débordements d'entier, ainsi que pour d'autres opérations. Le bit de retenue final est souvent stocké dans un registre spécial du processeur (généralement appelé ''carry flag'').
Notez aussi, à droite du schéma précédent, l'entrée de retenue <math>\text{R}_\text{Entrée}</math>. Elle est très utile pour l'implémentation de certaines opérations comme l'inversion de signe, la soustraction, l'incrémentation, etc. Certains processeurs sont capables de faire une opération appelée ADC, ADDC ou autre nom signifiant ''Addition with Carry'', qui permet de faire le calcul A + B + Retenue (la retenue en question est la retenue sortante de l'addition précédente, stockée dans le registre ''carry flag''). Son utilité principale est de permettre des additions d'entiers plus grands que ceux supportés par le processeur. Par exemple, cela permet de faire des additions d'entiers 32 bits sur un processeur 16 bits.
[[File:Adder Network 3 Bit Sum Delay 003.svg|vignette|upright=0.5|Propagation de retenue dans l'additionneur.]]
Cet additionneur utilise très peu de portes logiques, ce qui fait qu'il était utilisé sur certains processeurs 8 et 16 bits, dont le budget en portes logiques était limité. Bien que très simple, cet additionneur est cependant peu performant. Son temps de calcul est proportionnel à la taille des opérandes. Par exemple, additionner deux opérandes de 32 bits prendra deux fois plus de temps que pour des opérandes de 16 bits. La raison est que les retenues doivent se propager à travers le circuit, du premier additionneur jusqu'au dernier.
L'addition étant une opération fréquente, d'autres additionneurs ont été inventés. Ils utilisent des optimisations qui utilisent plus de circuits pour gagner en rapidité. Mais avant de voir ces optimisations, nous allons voir s'il est possible d'optimiser les additionneurs à propagation de retenue.
===L'additionneur à saut de retenue===
L'additionneur à propagation de retenue peut être optimisé de deux manières. La première accélère la propagation de la retenue, en agissant au niveau des additionneurs complets. La ''Manchester carry chain'' est une optimisation de ce type, mais on ne peut pas encore expliquer à ce stade du cours, car elle optimisait les additionneurs complets au niveau des transistors eux-mêmes. Elle a été utilisée sur de nombreux processeurs connus, comme le 8086 d'Intel ou le Z80.
La seconde manière rend l'addition plus rapide dans certains cas. Le résultat est un additionneur dont le temps de calcul est variable. Le calcul prendra quelques cycles d'horloges avec certains opérandes, mais d'autres opérandes ne verront aucune amélioration. Il n'améliore pas le pire des cas, dans lequel la retenue doit être propagée du début à la fin, du bit de poids faible au bit de poids fort. Mais dans d'autres cas, il permet d'avoir le résultat en avance. L'optimisation en question donne l''''additionneur à saut de retenue''' (''carry-skip adder'').
Il est composé de blocs qui additionnent 4/5 bits, rarement plus. La taille d'un bloc varie d'un additionneur à l'autre, elle peut même varier dans l'additionneur, mais nous allons prendre un exemple où les blocs font 4 bits. L'idée est de détecter précocement si la retenue entrante est propagée à travers tout le bloc. Si la retenue est propagée dans tous le bloc, on peut directement l'envoyer sur la sortie de retenue, la retenue saute le bloc entier. Dans le cas contraire, on doit calculer la retenue normalement. Le choix entre les deux est le fait d'un multiplexeur. Toute la difficulté est de savoir comment commander le multiplexeur.
[[File:Carry skip adder, principe de base.png|centre|vignette|upright=2.5|Carry skip adder : principe de base]]
Pour savoir si une retenue est propagée dans un bloc entier, il faut utiliser des additionneurs complets, qui ont en plus une sortie P indiquant que l'additionneur propage la retenue. Le bloc propage une retenue si chaque additionneur complet propage la retenue, donc s'ils ont tous leur sortie P à 1. Un simple ET logique suffit à déterminer si c'est le cas. Le signal de commande du multiplexeur est donc un vulgaire ET entre toutes les sorties P des additionneurs complets du bloc.
[[File:Additionneur à saut de retenue.png|centre|vignette|upright=2.5|Calcul de la commande du MUX.]]
Voici ce que ça donne pour un bloc de 4 bits :
[[File:CSAdder4Bit.svg|centre|vignette|upright=1.5|Additionneur ''carry skip'' : bloc de 4 bits.]]
L'additionneur à saut de retenue est construit en assemblant plusieurs blocs de ce type.
[[File:BCSAdder16Bit.svg|centre|vignette|upright=3|Additionneur à saut de retenue.]]
===L'additionneur à sélection de retenue===
L''''additionneur à sélection de retenue''' utilise aussi des blocs, comme les additionneurs précédents. L'addition se fait en deux versions : une avec la retenue du bloc précédent valant zéro, et une autre version avec la retenue du bloc précédent valant 1. Il suffira alors de choisir le bon résultat avec un multiplexeur, une fois cette retenue connue. On gagne ainsi du temps en calculant à l'avance les valeurs de certains bits du résultat, sans connaître la valeur de la retenue. Petit détail : sur certains additionneurs à sélection de retenue, les blocs de base n'ont pas la même taille. Cela permet de tenir compte des temps de propagation des retenues entre les blocs.
[[File:Additionneur à sélection de retenue.png|centre|vignette|upright=1.5|Additionneur à sélection de retenue avec seulement deux blocs.]]
Dans les exemples du dessus, chaque sous-additionneur étaient des additionneurs à propagation de retenue. Mais ce n'est pas une obligation, et tout autre type d’additionneur peut être utilisé. Par exemple, on peut faire en sorte que les sous-additionneurs soient eux-mêmes des additionneurs à sélection de retenue, et poursuivre ainsi de suite, récursivement. On obtient alors un '''additionneur à somme conditionnelle''', plus rapide que l'additionneur à sélection de retenue, mais qui utilise beaucoup plus de portes logiques.
===Les additionneurs à anticipation de retenue===
Les '''additionneurs à anticipation de retenue''' calculent chaque retenue sans avoir à propager les retenues précédentes. Au lieu de calculer les retenues une par une, ils calculent toutes les retenues en parallèle, à partir des bits des opérandes. Une fois les retenues pré-calculées, il suffit de les additionner avec les deux bits adéquats, pour obtenir le résultat. Ces additionneurs sont composés de deux parties :
* un circuit qui pré-calcule les retenues, sans les propager, directement à partir des opérandes ;
* d'une couche d'additionneurs complets, qui additionnent chacun deux bits d'opérande et la retenue pré-calculée.
[[File:Additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Additionneur à anticipation de retenue.]]
Le calcul des retenues se fait en deux étapes. La première détermine si les retenues sont propagées ou générées, pour chaque colonne. Elle détermine, pour chaque colonne, les bits P et G qui indiquent respectivement : qu'une retenue a été générée sur cette colonne, que la colonne propage la retenue précédente. La seconde étape utilise ces signaux P et G pour déterminer la retenue sur chaque colonne. Le circuit de calcul des retenues est donc composé de deux parties : une couche de demi-additionneurs pour générer les signaux P et G, un circuit de calcul des retenues proprement dit.
[[File:Circuit complet d'un additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Circuit complet d'un additionneur à anticipation de retenue.]]
Pour rappel, la retenue de la colonne i, notée <math>C_i</math> est égale à :
: <math>G_i + (P_i . C_{i-1})</math>, avec G le signal de génération de retenue, P le signal de propagation de retenue.
Si on utilisait cette formule sans trop réfléchir, on retomberait sur un additionneur à propagation de retenue inutilement compliqué. L'astuce des additionneurs à anticipation de retenue consiste à remplacer le terme Ci−1 par sa valeur calculée avant. Par exemple, je prends un additionneur 4 bits. Je dispose de deux nombres A et B, contenant chacun 4 bits : A3, A2, A1, et A0 pour le nombre A, et B3, B2, B1, et B0 pour le nombre B. Si j'effectue les remplacements, j'obtiens les formules suivantes :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Ces formules nous permettent de déduire la valeur d'une retenue directement : il reste alors à créer un circuit qui implémente ces formules, et le tour est joué.
[[File:Four bit adder with carry lookahead.svg|centre|vignette|upright=1.5|Additionneur à anticipation de retenue de 4 bits.]]
Plus haut, j'ai dit que le circuit était composé de trois couches : une couche de demi-additionneur pour les signaux P et G ; un circuit d'anticipation de retenue, puis l'addition finale. Mais les deux bits d'opérandes sont déjà additionnés dans la couche de demi-additionneur, autant réutiliser le résultat pour l'additionner à la retenue. Pour éliminer toute redondance de ce type, il est possible d'utiliser des additionneurs partiels (des additionneurs où la sortie de retenue a été remplacée par deux sorties P et G). L'additionneur final est alors composé de plusieurs additionneurs complets de ce type, couplé à un circuit d'anticipation de retenue.
[[File:Cla4bits.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue de 4 bits.]]
Les additionneurs à anticipation de retenue sont plus rapides que les additionneurs à propagation de retenue. La raison est que les retenues sont calculées rapidement, dans un temps plus petit qu'en propageant la retenue. Pour rentrer dans les détails mathématiques, le temps de calcul n'est pas proportionnel au nombre de bits, mais proportionnel au logarithme de ce nombre de bits. Par contre, le gain en temps de calcul s'accompagne d'un cout en portes logiques conséquent. Le circuit qui calcule les retenues anticipées utilise beaucoup de portes logiques.
===Les additionneurs à calcul parallèle de préfixe===
Du fait du cout en portes logiques, utiliser un additionneur à anticipation de retenue est une solution pratique pour additionner 4 ou 8 bits. Mais pour des opérandes plus grands, de 16 ou 32 bits, cela utiliserait trop de portes logiques. Une partie de ce cout en portes logiques est cependant lié à des redondances. Si on analyse deux retenues différentes, à des colonnes différentes. Les circuits qui calculent ces deux retenues seront différents, mais ils contiendront des sous-circuits identiques. Pour vous en rendre compte, reprenons les quatre équations obtenues précédemment et essayez de trouver des redondances :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Vous devriez trouver :
* le terme ''P0 · C0'' dans toutes les lignes ;
* les termes ''P1 · P0 · C0'' et ''P1 · P0'' dans les trois dernières lignes ;
* le terme ''P2 · P1 · P0 · C0'' ; ''P2 · P1 · G0'' et ''P2 · G1'' dans les deux dernières lignes.
Il existe plusieurs manières d'exploiter de telles redondances, exploitées dans les '''additionneurs à calcul parallèle de préfixe'''. L'optimisation apportée est de générer des signaux ''propagate'' et ''generate'' pour des groupes de 2, 3, 4, ..., N colonnes. Par exemple, il est possible de générer un signal ''P 0 vers 3'', qui indique si la retenue de la première colonne est propagée jusqu'à la 4ème colonne. Un autre exemple est un signal qui indique qu'une retenue a été générée entre les colonnes 4 à 7 génèrent une retenue ou non. En clair, les signaux P et G ont maintenant un intervalle, qui précise de quelle colonne vers quelle colonne se fait la propagation, ou entre quelles colonnes se fait la génération.
Il est possible de combiner les signaux P et G de deux groupes de colonne, s'ils sont contiguës (ils peuvent aussi se recouvrir). Par exemple, il est possible de calculer les bits P et G pour les colonnes 0 à 10, à partir des deux signaux P/G des colonnes 0-4 et 5-10. Pour cela, les équations sont assez simples. Si je prends deux groupes nommés A et B, avec A pour les colonnes de poids fort et B celles de poids faible, on a :
: <math>P_{sortie} = P_A . P_B</math>
: <math>G_{sortie} = G_A . P_A . G_B</math>
Pour les bits des colonnes 2, 4, 8, 16, 32, 64, et autres, le calcul est simple. L'idée est de grouper les colonnes par groupes de 2. Puis, on calcule chaque groupe de 4 bits à partir de deux groupes de 2. Puis les groupes de 8 bits sont calculés à partir de deux groupes de 4 bits, et ainsi de suite pour toute puissance de deux.
[[File:Squelette d'un additionneur à calcul parallèle de préfixe.jpg|centre|vignette|upright=2|Squelette d'un additionneur à calcul parallèle de préfixe]]
Les seules difficultés surviennent pour les colonnes dont le nombre n'est pas une puissance de deux. Il y a plusieurs manières pour subdiviser ces colonnes en intervalles plus petits et combiner le tout. Et elles donnent chacune des additionneurs différents. Ils portent les noms d'''additionneur de Ladner-Fisher'', d'''additionneur de Brent-Kung'', d'''additionneur de Kogge-Stone'', etc. Dans le cas général, l'additionneur de Kogge-Stone est un des plus rapide, mais aussi un des plus gourmand en circuits, alors que c'est l'inverse pour l'additionneur de Brent-Kung. Les deux additionneurs sont illustrés ci-dessous, pour des opérandes de 8 bits. Dans ceux-ci, chaque losange calculent les bits P et G, à partir des signaux P et G de deux groupes précédents.
{|
|[[File:Brent-kung-8-bit.png|vignette|Brent-kung-8-bit]]
|[[File:Kogge-stone-8-bit.png|vignette|upright=1.2|Kogge-stone-8-bit]]
|}
===Les additionneurs à anticipation de retenue multi-niveau===
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Dans cette section, nous allons voir des additionneurs conçus en assemblant des additionneurs plus simples, qui additionnent environ 4 à 5 bits, parfois plus, parfois moins. Ces additionneurs simples seront nommés ''blocs'' dans ce qui suit, et l'un d'entre eux est illustré ci-contre. Chaque bloc prend en entrée un morceau des deux opérandes à additionner, mais aussi une retenue d'entrée.
Les blocs sont tous identiques dans le cas le plus simple, mais il est possible d'utiliser des blocs de taille variable. Par exemple, le premier bloc peut avoir des opérandes de 6 bits, le second des opérandes de 7 bits, etc. Faire ainsi permet de gagner un petit peu en performances, si la taille de chaque bloc est bien choisie. La raison est une question de temps de propagation des retenues. La retenue met plus de temps à se propager à travers 8 blocs qu'à travers 4, ce qui prend plus de temps qu'à travers 2 blocs, etc. En tenir compte fait que la taille des blocs tend à augmenter ou diminuer quand on se rapproche des bits de poids fort.
Il est possible d'utiliser l'anticipation de retenue entre deux blocs. Par exemple, il est possible de créer un additionneur 16 bits en utilisant 4 additionneurs/blocs de 4 bits. L'additionneur utilisé peut être un additionneur à propagation de retenue, ou à anticipation de retenue, peu importe.
Pour cela, les additionneurs de 4 bits doivent fournir deux sorties P et G. Les deux sorties indiquent que le bloc soit a généré une retenue, soit a propagé la retenue entrante. Par propagé, on veut dire que la retenue entrante du bloc s'est retrouvée sur la sortie de retenue du bloc. C'est le cas si les 4 colonnes propagent la retenue, ce qui signifie que :
: <math>P_\text{4 bits} = P_0 \cdot P_1 \cdot P_2 \cdot P_3</math>
Pour la génération, cela veut dire qu'une colonne a générée une retenue, qui a été propagée jusqu'à la sortie. En clair, pour chaque colonne, il faut que le G = 1, et que les signaux P des colonnes soient à 1. Pour les 4 colonnes, cela donne :
: <math>G_\text{4 bits} = G_3 + G_2 \cdot P_3 + G_1 \cdot P_3 \cdot P_2 + G_0 \cdot P_3 \cdot P_2 \cdot P_1</math>
[[File:Cla4bitsPG.png|centre|vignette|upright=2|Additionneur à anticipation de retenue de 4 bits.]]
Pour combiner 4 blocs de 4 bits, on devra donc combiner 4 bits P et G, avec un circuit d'anticipation de retenue.
[[File:16-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 16 bits.]]
Il est même possible d'aller plus loin et de combiner 4 additionneurs précédents pour obtenir un additionneur 64 bits.
[[File:64-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 64 bits.]]
Le résultat est un additionneur à plusieurs niveaux d’anticipation de retenue. Une anticipation de retenue dans un blocs de 4 bits (facultative), entre blocs de 4 bits, entre blocs de 16 bits. La même logique peut être utilisée avec des blocs de taille différente de 4, 16 et 64 bits.
===Le calcul parallèle de la retenue===
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Utiliser de l'anticipation de retenue entre blocs utilise beaucoup de circuits. Aussi, une autre option est d'utiliser la propagation de retenue entre blocs. L'anticipation de retenue est utilisée à l'intérieur des blocs, pas entre eux. Par exemple, on peut utiliser 4 additionneurs à anticipation de retenue de 4 bits, et propager les retenues entre eux. Le premier bloc calcule une retenue, qui est propagée au second bloc. Le second bloc calcule lui aussi une retenue, qui est propagée au troisième bloc, etc.
[[File:4008 Functional Diagram.svg|vignette|upright=1|4008 Functional Diagram]]
Le gain en performance est significatif, mais il est possible de faire presque aussi rapide, tout en économisant beaucoup de circuits. L'idée est de simplifier les additionneurs 4 bits, afin de ne pas utiliser une anticipation de retenue complète. L'anticipation de retenue n'est utilisée que pour la retenue sortante, alors que les bits de somme sont calculées avec propagation de retenue. La retenue sortante est donc calculée en parallèle de l'addition. Chaque bloc contient, à côté d'un additionneur proprement dit, un circuit qui calcule la retenue sortante. Il existe de nombreuses manières de calculer la retenue sortante.
Le gain en portes logiques est assez significatif. En effet, on se passe de circuits d'anticipation de retenue pour les 4 bits de somme, on ne garde que celui pour la retenue sortante. Le fait d'utiliser la propagation de retenue pour calculer les 4 bits de somme est censé avoir un léger cout en performance, mais il se trouve que ce cout est très faible. En effet, le résultat n'est connu qu'une fois les retenues propagées jusqu'au dernier bloc. Et malgré l'anticipation des retenues, la propagation est assez lente. les additionneurs à propagation de retenue auront terminé leur travail bien avant.
Un exemple est celui de l'additionneur CMOS 4008, un additionneur de 4 bit. Il est composé en trois sections. Une première couche de demi-additionneurs calcule les signaux P et G utilisés à la fois pour l'anticipation de la retenue sortante, que pour calculer les bits de somme. Le circuit de calcul de la retenue sortante utilise l'anticipation de retenue. Le reste du circuit fait l'addition en propageant les retenues.
[[File:CMOS 4008, circuit découpé en sections.png|centre|vignette|upright=1.5|CMOS 4008, circuit découpé en sections]]
==L'addition signée et la soustraction==
Après avoir vu l'addition, il est logique de passer à la soustraction, les deux opérations étant très proches. Si on sait câbler une addition entre entiers positifs, câbler une soustraction n'est pas très compliqué. De plus, la soustraction permet de faire des additions de nombres signés.
===Le soustracteur pour opérandes entiers===
Pour soustraire deux nombres entiers, on peut adapter l'algorithme de soustraction utilisé en décimal, celui que vous avez appris à l'école. Celui-ci ressemble fortement à l'algorithme d'addition : on soustrait les bits de même poids, et on propage éventuellement une retenue sur la colonne suivante. À la différence de l'addition, la retenue est soustraite, et non ajoutée. La table de soustraction nous dit quel est le résultat de la soustraction de deux bits. La voici :
* 0 - 0 = 0 ;
* 0 - 1 = 1 et une retenue ;
* 1 - 0 = 1 ;
* 1 - 1 = 0.
[[File:Algebra1 05 fig014.svg|centre|vignette|Soustraction en binaire, avec les retenues en rouge.]]
La table de soustraction peut servir de table de vérité pour construire un circuit qui soustrait deux bits. Celui-ci est appelé un '''demi-soustracteur'''. Il ressemble beaucoup à un demi-additionneur, les différences se résumant à une porte NON ajoutée pour le calcul de la retenue.
[[File:Half Subtractor Vektor.svg|centre|vignette|Demi-soustracteur.]]
Comme pour l'additionneur, deux demi-soustracteurs peuvent être combinés pour donner un '''soustracteur complet'''. Le calcul de la retenue se fait en combinant les deux retenues des demi-soustracteurs avec une porte OU. Les soustracteurs complets sont utilisés pour créer des soustracteurs à propagation de retenue ou tout autre circuit soustracteur, sur le même modèle que les additionneurs.
[[File:FullSubtractor.svg|centre|vignette|upright=2|Soustracteur complet.]]
Vous l'aurez sans doute remarqué, mais le demi-soustracteur ressemble beaucoup au demi-additionneur. La seule différence est une porte NON ajoutée au bon endroit dans le demi-soustracteur.
{|
|[[File:Half-adder.svg|vignette|Demi-additionneur]]
|[[File:Half Subtractor Vektor.svg|vignette|upright=1.3|Demi-soustracteur]]
|}
La conséquence est qu'il est possible de créer un circuit capable de faire à la fois des additions et des soustractions. L'idée est de remplacer la porte NON du demi-soustracteur par un inverseur commandable. Si on envoie un zéro sur l'entrée de commande, l'inverseur commandable se comportera comme une porte OUI. Le circuit sera alors identique à un demi-additionneur. Si on envoie un 1 sur l'entrée de commande, l'inverseur commandable se comportera comme une porte NON. le circuit sera alors un demi-soustracteur. Il faut faire cela pour chaque demi-soustracteur, ce qui donne ceci pour un soustracteur complet. le résultat est alors appelé un '''additionneur-soustracteur complet'''.
[[File:Additionneur-soustracteur complet.png|centre|vignette|upright=2|Additionneur-soustracteur complet]]
===L'additionneur-soustracteur pour opérandes codées en complément à deux===
Étudions le cas de la soustraction en complément à deux, dans l'objectif de créer un circuit soustracteur. Vous savez sûrement que a−b et a+(−b) sont deux expressions équivalentes. Et en complément à deux, − b = not(b) + 1. Dit autrement, a − b = a + not(b) + 1. On pourrait se dire qu'il faut deux additionneurs pour faire le calcul, mais la majorité des additionneurs possède une entrée de retenue pour incrémenter le résultat de l'addition. Un soustracteur en complément à deux est donc simplement composé d'un additionneur et d'un inverseur.
[[File:Additionneur soustracteur.png|centre|vignette|upright=2|Soustracteur en complément à deux.]]
Il est possible de créer un circuit capable d'effectuer soit une addition, soit une soustraction : il suffit de remplacer l'inverseur par un inverseur commandable, qui peut être désactivé. On a vu comment créer un tel inverseur commandable dans le chapitre sur les circuits combinatoires. On peut remarquer que l'entrée de retenue et l'entrée de commande de l'inverseur sont activées en même temps : on peut fusionner les deux signaux en un seul.
[[File:Additionneur-soustracteur en complément à deux.png|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux.]]
Une implémentation alternative est la suivante. Elle remplace l'inverseur commandable par un multiplexeur.
[[File:4-bit ripple carry adder-subtracter.svg|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux, version alternative.]]
===L'additionneur-soustracteur pour opérandes codées en signe-magnitude===
Passons maintenant aux nombres codés en signe-valeur absolue, les deux opérandes étant notées A et B. Suivant les signes des deux opérandes, on a quatre cas possibles : A + B, A − B (B négatif), −A + B (A négatif) et −A − B (A et B négatifs). Une astuce est que le circuit n'a besoin que de calculer A + B et A − B : il peut les inverser pour obtenir − A − B ou B − A. A + B et A − B peuvent se calculer avec un additionneur-soustracteur, reste à corriger le résultat. Il suffit de lui ajouter un inverseur commandable pour obtenir le circuit d'addition finale.
[[File:Additionneur en signe-valeur absolue.png|centre|vignette|upright=2|Additionneur en signe-valeur absolue.]]
Toute la difficulté tient dans le calcul du bit de signe du résultat, quand interviennent des soustractions. Autant l'addition de deux nombres de même signe (A + B et −A − B) ne pose aucun problème, autant les soustractions posent problème (A − B et −A + B). Suivant que <math>A<B</math> ou que <math>A>B</math>, le signe du résultat ne sera pas le même. Déterminer le signe du résultat se fait en regardant les bits de débordement d'entier, comme on le verra plus bas.
===L'additionneur-soustracteur pour opérandes codées en représentation par excès===
Passons maintenant aux nombres codés en représentation par excès. On pourrait croire que ces nombres s'additionnent comme des nombres non-signés, mais ce serait oublier la présence du biais, qui pose problème. Dans les cas de nombres signés gérés avec un biais, voyons ce que donne l'addition de deux nombres :
: <math>( n_1 + biais ) + ( n_2 + biais ) = n_1 + n_2 + 2 \times biais</math>
Or, le résultat correct serait :
: <math>n_1 + n_2 + biais</math>
En effectuant l'addition telle quelle, le biais est compté deux fois. On doit donc le soustraire après l'addition pour obtenir le résultat correct.
Même chose pour la soustraction qui donne ceci :
: <math>(n_1 + biais) - (n_2 + biais) = n_1 - n_2</math>
Or, le résultat correct serait :
: <math> ( n_1 - n_2 ) + biais </math>
Il faut rajouter le biais pour obtenir l'exposant correct.
On a donc besoin de deux additionneurs/soustracteurs : un pour additionner/soustraire les représentations binaires des opérandes, et un autre pour ajouter/retirer le biais en trop/manquant.
==L'additionneur BCD==
Maintenant, voyons un additionneur qui additionne deux entiers au format BCD. Pour cela, nous allons devoir passer par deux étapes. La première est de créer un circuit capable d'additionneur deux chiffres BCD. Ensuite, nous allons voir comment enchaîner ces circuits pour créer un additionneur BCD complet.
===L'additionneur BCD qui fait l'opération chiffre par chiffre===
Nous allons commencer par voir un additionneur qui additionne deux chiffres en BCD, une sorte d'équivalent BCD de l'additionneur complet. Il fournit un résultat sur 4 bits et une retenue qui est mise à 1 si le résultat dépasse 10 (la limite d'un chiffre BCD). Les deux opérandes sont des chiffres BCD codés sur 4 bits et sont additionnés en binaire par un additionneur des plus normaux, similaire à ceux vus plus haut. Le résultat est alors un entier codé en binaire, sur 5 bits, qu'on corrige/convertit pour obtenir un chiffre BCD et une retenue sortante.
Pour corriger le résultat, une idée intuitive serait de prendre le résultat et de faire une division par 10. Le quotient donne la retenue, alors que le reste est le résultat, le chiffre BCD. Mais un circuit diviseur par 10 utilise beaucoup de portes logiques, ce qui ne vaut pas le coup. Une autre méthode détecte si le résultat est égal ou supérieur à 10, ce qui correspond à un "débordement" (on dépasse les limites d'un chiffre BCD). Si le résultat est plus petit que 10, il n'y a rien à faire : le résultat est bon et la retenue est de zéro. Par contre, si le résultat vaut 10 ou plus, il faut corriger le résultat et générer une retenue à 1.
Il faut donc ajouter un circuit qui détecte si le résultat est supérieur à 9, qui calcule directement la retenue. Ce circuit peut se fabriquer simplement à partir de sa table de vérité, ou en utilisant les techniques que nous verrons dans un chapitre ultérieur sur les comparateurs. La solution la plus simple est clairement d'utiliser la table de vérité, ce qui est très simple, assez pour être laissé en exercice au lecteur. Pour comprendre comment corriger le résultat, établissons une table de vérité qui associe le résultat et le résultat corrigé. L'entrée vaut au minimum 10 et au maximum 9 + 9 = 18. On considère la sortie comme un tout, la retenue étant un 5ème bit, le bit de poids fort.
{|class="wikitable"
|-
! colspan="5" | Entrée
!
! rowspan="10" |
! Retenue
! Résultat corrigé (sans retenue)
! interprétation de la sortie en binaire (retenue inclue)
|-
| 0 || 1 || 0 || 1 || 0
| (10)
| 1 || 0000
| (16)
|-
| 0 || 1 || 0 || 1 || 1
| (11)
| 1 || 0001
| (17)
|-
| 0 || 1 || 1 || 0 || 0
| (12)
| 1 || 0010
| (18)
|-
| 0 || 1 || 1 || 0 || 1
| (13)
| 1 || 0011
| (19)
|-
| 0 || 1 || 1 || 1 || 0
| (14)
| 1 || 0100
| (20)
|-
| 0 || 1 || 1 || 1 || 1
| (15)
| 1 || 0101
| (21)
|-
| 1 || 0 || 0 || 0 || 0
| (16)
| 1 || 0110
| (22)
|-
| 1 || 0 || 0 || 0 || 1
| (17)
| 1 || 0111
| (23)
|-
| 1 || 0 || 0 || 1 || 0
| (18)
| 1 || 1000
| (24)
|}
En analysant le tableau, on voit que pour corriger le résultat, il suffit d'ajouter 6. La raison est que le résultat déborde d'un nibble à 16 en binaire, mais à 10 en décimal : il suffit d'ajouter la différence entre les deux, à savoir 6, et le débordement binaire fait son travail. Donc, la correction après une addition est très simple : si le résultat dépasse 9, on ajoute 6.
On peut maintenant implémenter l'additionneur BCD, en combinant le comparateur avec 10, le circuit de correction, et l'additionneur. La première solution calcule deux versions du résultat : la version corrigée, la version normale. Le choix entre les deux est réalisée par un multiplexeur, commandé par le comparateur.
[[File:Additionneur BCD.png|centre|vignette|upright=1.5|Additionneur BCD]]
L'autre solution utilise un circuit commandable qui soit additionne 6, soit ne fait rien. Le choix entre les deux est commandé par le bit calculé par le comparateur.
[[File:Additionneur BCD, seconde version.png|centre|vignette|upright=1.5|Additionneur BCD, seconde version.]]
Une version alternative du circuit précédent est la suivante. Il contient deux additionneurs : un pour additionner les deux chiffres BCD, un autre pour additionner 6 si besoin. Le résultat du comparateur est directement utilisé pour générer l'opérande du second additionneur : 0 ou 6. Le circuit est simple à concevoir, mais gaspille beaucoup de circuit. Idéalement, il vaudrait mieux utiliser un circuit combinatoire d'addition avec une constante.
[[File:Adder 4 Bit BCD.svg|centre|vignette|upright=2|Additionneur BCD, circuit complet.]]
Pour obtenir un additionneur BCD complet, il suffit d’enchaîner les additionneurs précédents, comme on le ferait avec les additionneurs complets dans un additionneur à propagation de retenue. Au final, l'additionneur BCD est beaucoup plus compliqué qu'un additionneur normal, car il rajoute un comparateur ">9", un petit additionneur pour ajouter 6 et éventuellement d'autres circuits. De plus, il est difficile d'appliquer les optimisations disponibles sur les additionneurs non-BCD. Notamment, les circuits d'anticipation de retenue sont totalement à refaire et le résultat est relativement compliqué. C'est ce qui explique pourquoi le BCD a progressivement été abandonné au profit du binaire simple.
La soustraction en BCD se fait comme en binaire : le nombre à soustraire est remplacé par son complément, le circuit additionne le complément et l'autre opérande, le débordement d'entier fait que le résultat marche. Sauf qu'ici, le complément est un complément à 9. Il se calcule chiffre par chiffre : chaque chiffre est remplacé par (9 - le chiffre en question).
===L'additionneur BCD par ajustement décimal===
L'additionneur BCD précédent effectuait son travail chiffre BCD par chiffre BCD, mais il existe des additionneurs BCD qui font autrement. Sur les premiers processeurs x86, il n'y avait pas d'opération d'addition BCD proprement dit, seulement une addition binaire normale de 8, 16 ou 32 bits. Par contre, elle était secondée par une opération dite d''''ajustement décimal''' qui transformait un nombre binaire en nombre codé en BCD. L'opération d'ajustement décimal prenait un opérande de 8 bits codé en binaire et fournissait un résultat de la même taille, c'est à dire deux chiffres BCD. Effectuer une addition BCD demandait donc de faire deux opérations à la suite : une addition binaire simple, suivie par l'opération d'ajustement décimal. Cela permettait de gérer des nombres entiers en binaire usuel et des entiers BCD sans avoir deux instructions d'addition séparées pour les deux, sans compter que cela simplifiait aussi les circuits d'addition.
L'ajustement décimal s'effectue en ajoutant une constante bien précise à l'opérande à convertir en BCD. L'idée est que la constante est découpée en morceaux de 4 bits, correspondant chacun à un chiffre BCD de l'opérande, chaque morceau contenant soit un 0, soit 6. Cela permet d'ajouter soit 0, soit 6, à chaque chiffre BCD, et donc de le corriger. La propagation des retenues d'un chiffre à l'autre est effectuée automatiquement par l'addition binaire de la constante. L'opération d'ajustement décimal calcule automatiquement la constante. Elle découpe l'opérande en ''nibbles'', vérifie si chaque ''nibble'' est supérieur ou égal à 10, puis détermine la valeur de chaque ''nibble'' de la constante finale. Par exemple, si je prends l'opérande 1001 1110, le ''nibble'' de poids faible déborde, alors que celui de poids fort non. La constante sera donc 0000 0110 : 0x06. Inversement, si le ''nibble'' de poids fort déborde et pas celui de poids faible, la constante sera alors 0x60. Et la constante est de 0x66 si les deux ''nibbles'' débordent, de 0x00 si aucun ne déborde.
Le circuit d’ajustement décimal est donc composé de trois étapes : deux étapes pour calculer la constante, et un circuit d'addition pour additionner cette constante au nombre de départ. La première étape découpe l'opérande en morceaux de 4 bits, en chiffres BCD, et vérifie si chacun d'entre eux vaut 10 ou plus. La seconde étape prend les résultats de la première étape, et les combine pour calculer la constante. Enfin, on trouve l'addition finale, qui était réalisée par un circuit d'addition utilisé à la fois pour l'ajustement décimal et l'addition binaire. La différence entre une addition normale et une opération d'ajustement décimal tient dans le fait que les deux premières étapes sont désactivées dans une addition normale.
[[File:Additionneur BCD parallèle.png|centre|vignette|upright=1.5|Additionneur BCD parallèle]]
===L'additionneur biquinaire===
Les entiers BCD ne sont qu'un des encodages hybrides entre décimal et binaire. L'encodage biquinaire est l'un d'entre eux et nous allons faire un rappel rapide à ce sujet. Pour simplifier, un chiffre encodé en biquinaire est composé de deux parties : un bit, couplé à une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits (0, 1, 2, 3 et 4). Le bit indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9.
Additionner deux nombres de biquinaire demande donc d'additionner deux parties quinaires encodées en ''one-hot'' et d'additionner deux bits. Mais attention : il faut tenir compte de la retenue de l'addition des parties quinaires. Et idéalement, il faut aussi tenir compte d'une retenue entrante, provenant de l'addition de la colonne de chiffres précédente. Toute la difficulté vient de la création de l'additionneur ''one-hot''. Heureusement, vu qu'il n'y a que 4-5 bits à additionner, il est souvent fabriqué à partir de sa table de vérité.
[[File:Additionneur bi-quinaire.png|centre|vignette|upright=2|Additionneur bi-quinaire]]
Un avantage du biquinaire est que le calcul du complément à 9 est très simple. Il faut pour cela : inverser la partie binaire avec une porte NON, puis inverser l'ordre des bits de la partie quinaire. Concrètement, le bit de poids faible devient le bit de poids fort, et ainsi de suite. Par exemple, une partie quinaire 01000 devient 00010, 10000 devient 00001, 00100 ne change pas, etc. Le tout peut se calculer avec une porte NON et 5 multiplexeurs.
===L'additionneur BCD avec calculs intermédiaires en biquinaire===
L'ordinateur IBM 1401, un ancien ''mainframe'' des années 60, utilisait un additionneur BCD un peu particulier. Les nombres étaient encodés en BCD dans la mémoire de l'ordinateur, mais les circuits de calcul utilisaient la représentation biquinaire. Lors d'un calcul, le processeur de l'ordinateur traduisait les chiffres BCD en représentation biquinaire, faisait une addition en biquinaire, avant de traduire le résultat en BCD normal.
Pour être précis, l'IBM 1401 utilisait une variante du biquinaire. L'encodage biquinaire de l'IBM 1401 est le suivant : la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre pair encodé par la partie quinaire. Si l'IBM 1401 utilisait cette variante du biquinaire, c'est car elle donnait des circuits de conversion BCD-biquinaire plus économes en portes logiques et plus rapides.
La partie binaire est le bit de poids faible du chiffre BCD, la partie biquinaire est calculée par un simple décodeur qui prend en entrée le chiffre BCD, amputé de son bit de poids faible. La traduction inverse demande d'utiliser un encodeur, à la place du décodeur. Par contre, le circuit d'addition biquinaire était plus compliqué du fait de la gestion des retenues. L'addition des parties binaires et quinaires se faisait en parallèle, dans deux additionneurs séparés. Cependant, l'addition des parties binaire fournit une retenue, qu'il faut prendre en compte. Pour cela, l'IBM 1401 disposait d'un troisième additionneur qui fournissait le résultat final, encodé en biquinaire.
[[File:Additionneur bi-quinaire de l'IBM 1401.png|centre|vignette|upright=2|Additionneur biquinaire de l'IBM 1401]]
Une implémentation moderne demanderait d'utiliser des portes ET combinées à des portes OU, le circuit pouvant être construit simplement à partir de sa table de vérité. Sur l'IBM 1401, le circuit était cependant différent, en raison de l'utilisation de OU câblés, des croisements de fils qui fonctionnent comme des portes OU, que nous n'avons pas encore vu pour le moment, mais qui seront détaillés dans quelques chapitres. Les OU câblés étaient utilisés pour simplifier le design du circuit, mais demandaient des portes logiques spécifiques, ce qui collait avec le fait que ce ''mainframe'' utilisait des transistors en Germanium. L'implémentation exacte est décrite dans cet article de blog, mais je ne recommande sa lecture qu'à ceux qui savent ce qu'est un OU câblé :
* [https://www.righto.com/2015/10/qui-binary-arithmetic-how-1960s-ibm.html Qui-binary arithmetic: how a 1960s IBM mainframe does math].
==Les débordements d'entier lors d'une addition/soustraction==
Les instructions arithmétiques manipulent des entiers codés sur un nombre fixe de bits, qui ne peuvent prendre leurs valeurs que dans un intervalle. Pour les nombres positifs, un ordinateur qui code ses entiers sur n bits pourra coder tous les entiers allant de 0 à <math>2^n -1</math>. Pour les nombres négatifs, l'intervalle est différent et dépend de la représentation utilisée. Dans le cas général, l'ordinateur peut coder les valeurs comprises de <math>N_\text{min}</math> à <math>N_\text{max}</math>. Si le résultat d'un calcul sort de cet intervalle, il ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math> , alors que la valeur basse vaut <math>N_\text{min} - 1</math> (avec <math>N_\text{max}</math> et <math>N_\text{min}</math> respectivement la plus grande et la plus petite valeur codable par l'ordinateur).
===La correction des débordements d'entier : l'arithmétique saturée===
Quand un débordement d'entier survient, tous les circuits de calcul ne procèdent pas de la même manière. Dans les grandes lignes, il y a deux réactions possibles : soit on corrige automatiquement le résultat du débordement, soit on ne fait rien et on se contente de détecter le débordement.
Si le débordement n'est pas corrigé automatiquement, le circuit ne conserve que les bits de poids faibles du résultat. Les bits en trop sont simplement ignorés. On dit qu'on utilise l''''arithmétique modulaire'''. Le problème avec ce genre d'arithmétique, c'est qu'une opération entre deux grands nombres peut donner un résultat très petit. Par exemple, si je dispose de registres 4 bits et que je souhaite faire l'addition 1111 + 0010 (ce qui donne 15 + 2), le résultat est censé être 10001 (17), ce qui est un résultat plus grand que la taille d'un registre. En conservant les 4 bits de poids faible, j’obtiens 0001 (1). En clair, un résultat très grand est transformé en un résultat très petit. Cela peut poser problèmes si on travaille uniquement avec des nombres positifs, mais c'est aussi utilisé pour coder des nombres en complément à deux.
D'autres circuits utilisent ce qu'on appelle l''''arithmétique saturée''' : si un calcul génère un débordement, on arrondi le résultat au plus grand entier supporté par le circuit. Les circuits capables de calculer en arithmétique saturée sont un peu plus complexes, vu qu'il faut rajouter des circuits pour corriger le résultat en cas de débordement. Il suffit généralement de rajouter un ''circuit de saturation'', qui prend en entrée le résultat et le corrige en cas de débordement. Ce circuit de saturation met la valeur maximale en sortie si un débordement survient, mais se contente de recopier le résultat du calcul sur sa sortie s'il n'y a pas de débordement. Typiquement, il est composé d'une couche de multiplexeurs, qui sélectionnent quelle valeur mettre sur la sortie : soit le résultat du calcul, soit le plus grand nombre entier géré par le processeur, soit le plus petit (pour les nombres négatifs/soustractions).
L'arithmétique saturée est utilisée pour les additions et soustractions, mais c'est plus rare pour les multiplications/divisions. Une des raisons est que le résultat d'une addition/soustraction prend un bit de plus que le résultat, là où les multiplications doublent le nombre de bits. Quand une addition déborde, le résultat réel est proche de la valeur maximale codable. mais quand une multiplication déborde, le résultat peut parfois valoir 200 à 60000 fois plus que la valeur maximale codable. Les calculs avec une valeur saturée/corrigée sont donc crédibles pour une suite d'additions, mais pas pour une suite de multiplications.
===La détection des débordements entiers===
Quand un débordement d'entier a eu lieu, il vaut mieux que l'additionneur prévienne ! Pour cela, l'additionneur a une '''sortie de débordement''', parfois nommée ''Overflow'', dont la valeur indique si l'addition a généré un débordement d'entier ou non. Reste que détecter un débordement ne se fait pas de la même manière selon que l'on parle d'un additionneur non-signé ou signé.
Pour les additionneur non-signés, l'additionneur calcule un bit de plus que ce qui est supporté par l'ordinateur. Par exemple, un additionneur 32 bits fournit un résultat sur 33 bits, un débordement d'entier a lieu quand le 33ème bit est à 1. Précisément, la sortie de débordement n'est autre que la retenue finale, celle fournie par le dernier additionneur complet. Le seul type de débordement possible est un débordement par le haut, où le résultat dépasse la valeur maximale. Avec l'arithmétique saturée, le circuit de saturation consiste en une seule couche de multiplexeurs, voire en un circuit de mise à la valeur maximale tel que vu dans le chapitre sur les opérations bits à bits.
[[File:Gestion des débordements d'entiers.png|centre|vignette|upright=2.5|Gestion des débordements d'entiers lors d'une addition non-signée.]]
Pour les additionneurs non-signés, la gestion des débordements d'entiers dépend fortement de la représentation signée. Nous allons étudier le cas du complément à deux. Si vous vous rappelez le chapitre 1, les calculs sur des nombres en complètement à deux utilisent les règles de l'arithmétique modulaire, c'est une condition nécessaire. À priori, on peut penser que dans ces conditions, les débordements d'entiers sont une chose parfaitement normale, qui nous permet d'avoir des résultats corrects. Néanmoins, certains débordements d'entiers peuvent survenir malgré tout et produire des bugs assez ennuyeux.
Si l'on tient en compte les règles du complément à deux, on sait que le bit de poids fort (le plus à gauche) permet de déterminer si le nombre est positif ou négatif : il indique le signe du nombre. Tout se passe comme si les entiers en complément à deux étaient codés sur un bit de moins, et avaient leur longueur amputé du bit de poids fort. Si le résultat d'un calcul écrase le bit de poids fort, il y a un débordement d'entiers. Il existe une règle simple qui permet de détecter ces débordements d'entiers. L'addition de deux nombres positifs ne peut pas être un nombre négatif. Si on additionne deux nombres dont le bit de signe est à 0 et que le bit de signe du résultat est à 1, on est en face d'un débordement d'entiers. Même chose pour deux nombres négatifs : le résultat de l'addition ne peut pas être positif. On peut résumer cela en une phrase : si deux nombres de même signe sont ajoutés, un débordement a lieu quand le bit du signe du résultat a le signe opposé.
Modifier les circuits d'au-dessus pour qu'ils détectent les débordements en complément à deux est simple comme bonjour : il suffit créer un petit circuit combinatoire qui prenne en entrée les bits de signe des opérandes et du résultat, et qui fasse le calcul de l'indicateur de débordements. Si l'on rédige sa table de vérité, on doit se retrouver avec la table suivante :
{|class="wikitable"
|-
!Entrées
!Sortie
|-
|000||0
|-
|001||1
|-
|010||0
|-
|011||0
|-
|100||0
|-
|101||0
|-
|110||1
|-
|111||0
|}
L'équation de ce circuit est la suivante, avec <math>S_a</math> et <math>S_b</math> les signes des deux opérandes, et <math>C_i</math> la retenue de la colonne précédente :
: <math> ( S_a . S_b . \overline{C_i} ) + ( \overline{S_a} . \overline{S_b} . C_i )</math>
En simplifiant, on obtient alors :
: <math>( S_a . S_b ) \oplus C_i</math>
Or, il se trouve que <math>S_a . S_b</math> est tout simplement la retenue en sortie du dernier additionneur, que nous noterons <math>C_o</math>. On trouve donc :
: <math>C_o \oplus C_i</math>
Il suffit donc de faire un XOR entre la dernière retenue et la précédente pour obtenir le bit de débordement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de décalage et de rotation
| prevText=Les circuits de décalage et de rotation
| next=Les circuits de comparaison
| nextText=Les circuits de comparaison
}}
</noinclude>
ruqyphkumnpd5iienkxbe3p0mpgmj8f
768663
768662
2026-06-25T19:57:48Z
Mewtow
31375
/* L'addition non signée */
768663
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir les circuits capables de faire une addition ou une soustraction, ainsi que quelques circuits spécialisés. Précisons cependant que les fabricants de processeurs travaillent d'arrache-pied pour trouver des moyens de rendre ces circuits de calcul plus rapides et plus économes en énergie. Autant vous dire que les circuits que vous allez voir sont vraiment des circuits qui font pâle figure comparé à ce que l'on peut trouver dans un vrai processeur commercial !
==Les circuits pour additionner 2 ou 3 bits==
L'addition se fait en binaire de la même manière qu'en décimal. On additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. La soustraction fonctionne sur le même principe, sur le même modèle qu'en décimal.
[[File:Binary Addition Demonstration.svg|centre|vignette|Exemple d'addition en binaire.]]
En clair, additionner deux nombres demande d'additionner 2 bits et une retenue sur chaque colonne, et de propager les retenues d'une colonne à l'autre. La propagation des retenues est quelque chose de simple en apparence, mais qui est sujet à des optimisations extraordinairement nombreuses. Aussi, pour simplifier l'exposition, nous allons voir comment gérer une colonne avant de voir comment sont propagées les retenues.
[[File:Full Adder Block.svg|vignette|upright=1|class=transparent|Additionneur complet.]]
Si on effectue une addition en colonne, on doit additionner les deux bits sur la colonne, mais aussi additionner une éventuelle retenue. Il faut donc créer un circuit qui additionne trois bits : deux bits de données, plus une retenue. Ce circuit qui additionne trois bits est appelé un '''additionneur complet'''. Il fournit en sortie deux bits : un bit de somme et une retenue sortante. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|-
||0||0||0|| ||0||0
|-
||0||0||1|| ||0||1
|-
||0||1||0|| ||0||1
|-
||0||1||1|| ||1||0
|-
||1||0||0|| ||0||1
|-
||1||0||1|| ||1||0
|-
||1||1||0|| ||1||0
|-
||1||1||1|| ||1||1
|}
Il est possible d'utiliser un tableau de Karnaugh pour traduire la table de vérité, mais elle donne un résultat assez compliqué. La retenue et le bit de somme sont calculés à part. La retenue est calculée avec trois portes ET et une porte OU. Le bit de somme est calculé lui avec 4 portes ET, une porte OU, et trois portes NON.
[[File:Full Adler with 3 entries.png|centre|vignette|upright=2|Additionneur complet, fabriqué à partir de la table de vérité.]]
D'autres méthodes donnent des résultats plus compréhensibles. Nous allons les voir dans la suite de cette section.
===L'additionneur complet basé sur des demi-additionneurs===
[[File:1-bit half-adder.svg|class=transparent|vignette|Demi-addtionneur.]]
Nous avons déjà vu comment additionner deux bits dans le chapitre sur les incrémenteurs, mais quelques rappels ne seront pas de trop. Le '''demi-additionneur''' est un circuit qui additionne deux bits. Il implémente la table d'addition, qui est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Mais un demi-additionneur n'additionne que deux bits, il manque de quoi additionner la retenue. Une solution pour cela est d'enchaîner deux demi-additionneurs : un qui additionne les deux bits de données, et un second qui additionne la retenue au résultat du précédent. La retenue finale se calcule en combinant les sorties de retenue des deux demi-additionneurs, avec une porte OU. Le résultat est un additionneur complet fabriqué avec deux demi-additionneurs. Pour vous en convaincre, établissez la table de vérité de ce circuit, vous verrez que ça marche.
[[File:Full Adder Blocks.svg|centre|vignette|upright=2|Additionneur complet fabriqué avec deux demi-additionneurs.]]
[[File:Half-adder.svg|vignette|upright=1|Circuit d'un demi-additionneur naïf.]]
L'implémentation naïve d'un demi-additionneur utilise une porte XOR et une porte ET, comme illustré ci-contre. Le circuit s'obtient en faisant uen traduction littérale de la table de vérité du circuit. Si je dis qu'elle est naïve, c'est car il est possible de l'optimiser, de manière à éliminer des redondances cachées. Mais nous avions déjà vu cela dans le chapitre sur les incrémenteurs, aussi laissons cela pour plus tard.
En combinant deux demi-additionneurs de ce type, on obtient l'additionneur complet suivant. Les deux sorties de retenue des demi-additionneurs sont combinées avec une porte OU, pour calculer la retenue finale.
{| class="flexible"
|[[File:Full Adder Modules.svg|class=transparent|Composition d'un additionneur complet. On voit bien que celui-ci est composé de deux demi-additionneurs, en rouge et en bleu, auxquels on a ajouté une porte OU pour calculer la retenue finale.]]
|[[File:Full-adder.svg|300px|class=transparent|Circuit d'un additionneur complet.]]
|}
Le fait de combiner les deux retenues avec une porte OU n'est pas ce qu'il y a de plus intuitif. La table de vérité nous dit que ça fonctionne, mais on comprend mal pourquoi. Mais tout devient plus clair quand on sait que les deux retenues ne peuvent pas être à 1 en même temps. Même dans l'addition 1 + 1 + 1, seule une retenue est à 1. Soit l'addition des deux bits d'opérande donne naissance à une retenue, soit c'est l'addition ''résultat + retenue entrante''. La retenue sortant est donc à 1 quand une des deux addition donne une retenue. D'où l'usage d'une porte OU. Remarquez qu'une porte XOR donne le même résultat, vu que le cas où les deux retenues sont à 1 n'est jamais rencontré.
===La propagation et la génération des retenues===
L'additionneur complet que nous allons voir dans cette section sert à introduire les concepts de propagation, génération et absorption de retenue. Il s'agit de concepts très importants quand on étudie les additionneurs. l'idée part d'un principe très simple : la retenue sortante dépend de la retenue d'entrée. La relation entre les deux se résume à trois cas, qui dépendent de la valeur des deux bits additionnés, nommés A et B.
* Dans le premier cas, la retenue entrante est égale à la retenue sortante. On dit que la retenue entrante est propagée par l'additionneur.
* Dans le second cas, la retenue est forcée à 0 : la retenue sortante vaut 0, peu importe la valeur de la retenue entrante.
* Dans le troisième cas, la retenue est forcée à 1 : la retenue entrante vaut 1, peu importe la valeur de la retenue entrante.
[[File:Signaux P et G fournis par un demi-additionneur.png|vignette|Signaux P et G fournis par un demi-additionneur]]
Maintenant, créons un circuit qui nous dise si une retenue est propagée ou générée. Il a deux bits de sortie, nommés P et G : P pour ''Propagate'', G pour ''Generate''. Le bit P est à 1 si la retenue entrante est propagée, il est à 0 sinon. Le bit G est à 1 si une retenue est générée, à 0 sinon. Une retenue est considérée comme absorbée si elle n'est pas ni propagée ni générée, pas besoin d'un troisième bit pour gérer ce cas.
Il se trouve que ce circuit n'est autre qu'un demi-additionneur ! Pour vous en rendre compte, regardez la table de vérité d'un additionneur complet, illustrée ci-dessous. Lorsque les deux bits d'opérande sont à 0, la retenue sortante vaut toujours 0. Si ils sont tous deux à 1, alors la retenue sortante vaut 1. S'ils sont différents, alors retenues sortante et entrante sont égales. Le bit P est donc généré par une simple porte XOR. Quant au bit G, il est à 1 si les deux bits d'opérandes sont à 1, ce qui correspond à une porte ET. Il se trouve que ces deux portes forment un demi-additionneur !
{|class="wikitable"
|-
! Opérande 1 !! Opérande 2 !! Retenue entrante !! !! Retenue sortante
|- class="f_rouge"
||0||0||0|| ||0
|- class="f_rouge"
||0||0||1|| ||0
|- class="f_bleu"
||0||1||0|| ||0
|- class="f_bleu"
||0||1||1|| ||1
|- class="f_bleu"
||1||0||0|| ||0
|- class="f_bleu"
||1||0||1|| ||1
|- class="f_vert"
||1||1||0|| ||1
|- class="f_vert"
||1||1||1|| ||1
|}
Ensuite, créons un circuit qui prend ces deux sorties P et G, et calcule la retenue sortante en fonction. Le circuit en question a trois entrées : la retenue entrante, les deux bits P et G. La retenue sortante vaut 1 soit si une retenue est générée, soit si la retenue entrante est propagée et qu'elle vaut 1. En notant <math>{C_{in}}</math> et <math>{C_{out}}</math> les retenues entrantes et sortante, on a :
: <math>{C_{out}} = G + \left( P . C_{in} \right)</math>
Le circuit est donc composé d'une porte OU et d'une porte ET. En combinant un demi-additionneur avec le circuit de calcul de retenue sortante vu plus haut, on a :
[[File:Additionneur complet avec propagation et génération de retenue.png|class=transparent|centre|vignette|upright=2|Additionneur complet avec propagation et génération de retenue.]]
Certes, on retombe sur le circuit vu plus haut. Mais il est possible de remplacer le circuit en orange par un autre. Par exemple, il est possible de le remplacer par multiplexeur, qui choisit entre la retenue générée et la retenue entrante (propagée). Le choix se fait selon la valeur du bit P, qui chosiit entre propager la retenue et la générer.
[[File:Additionneur crée avec un multiplexeur.png|centre|vignette|upright=1.5|Additionneur crée avec un multiplexeur]]
Le circuit exact est illustré ci-dessous. Il semble utiliser plus de portes logiques que l'additionneur complet précédent. Cependant, nous verrons dans quelques chapitres qu'il est possible d'implémenter un multiplexeur avec seulement 6 transistors, voire moins ! L'implémentation utilise des portes à transmission, mais nous en reparlerons dans le chapitre sur les transistors, quand nous verrons les additionneurs à ''Manchester Carry Chain''. Au passage, une variante de ce circuit a été utilisée dans le processeur processeur 8086 d'Intel, comme on le verra dans le chapitre suivant.
[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
[[File:FulladderPG.png|vignette|upright=1|Additionneur complet avec deux sorties P et G.]]
Pour finir, sachez qu'il existe des additionneurs qui fournissent : le bit de résultat, deux sorties P et G qui indiquent si l'addition propage ou génère une retenue. Il ne s'agit pas d'additionneurs complets, car il manque de quoi calculer la retenue sortante. De tels additionneurs seront appelés des '''additionneurs partiels'''. De tels additionneurs sont utilisés dans certains additionneurs pour gagner en performance.
Enfin, il existe aussi ce que j'ai décidé d'appeler des '''additionneurs P/G''', qui sont des additionneurs complets auxquels on a ajouté deux sorties P et G, en plus de la sortie de retenue. Leur circuit est le même que celui d'un additionneur complet, auquel on a ajouté deux fils. Vu que ces deux sorties sont fournies par le premier demi-additionneur, ajouter ces deux sorties demande d'ajouter des fils, pas de portes logiques. De tels additionneurs sont aussi utilisés dans certains additionneurs pour gagner en performance.
===L'additionneur complet basé sur une porte à majorité===
Maintenant, voyons une dernière implémentation possible de l'additionneur complet. Mais avant de voir comment l'additionneur est implémenté, nous allons devoir faire un petit focus sur la retenue sortante. Reprenons la table de vérité, et regardons ce qu'il en est pour la retenue sortante uniquement.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante
|-
||0||0||0|| ||0
|-
||0||0||1|| ||0
|-
||0||1||0|| ||0
|-
||0||1||1|| ||1
|-
||1||0||0|| ||0
|-
||1||0||1|| ||1
|-
||1||1||0|| ||1
|-
||1||1||1|| ||1
|}
Vous remarquerez que la retenue sortante est égale au bit majoritaire, parmi les trois bits d'opérande. Si deux ou trois bits sont à 1, la retenue sortante vaut 1. Et réciproquement avec 0. Le circuit de calcul de la retenue peut donc être remplacé par une '''porte à majorité'''.
[[File:Additionneur crée avec une porte à majorité.png|centre|vignette|upright=2|Additionneur crée avec une porte à majorité]]
Mais une porte à majorité est assez gourmande en circuit, ce qui fait qu'un additionneur pareil ne serait pas pratique, ni utile. Du moins, ce serait le cas s'il n'y avait pas une possibilité d'optimisation extrêmement intéressante. Il est possible de calculer le 'bit de somme' à partir de la retenue sortante ! En effet, le bit de somme est l'inverse de la retenue sortante, sauf dans deux cas : les trois bits d'entrée sont à 0, où ils sont tous à 1. Le bit de somme vaut 0 dans le premier cas, 1 dans le second cas.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|- class="f_bleu"
||0||0||0|| ||0||0
|- class="f_vert"
||0||0||1|| ||0||1
|- class="f_vert"
||0||1||0|| ||0||1
|- class="f_vert"
||0||1||1|| ||1||0
|- class="f_vert"
||1||0||0|| ||0||1
|- class="f_vert"
||1||0||1|| ||1||0
|- class="f_vert"
||1||1||0|| ||1||0
|- class="f_rouge"
||1||1||1|| ||1||1
|}
En clair, 6 lignes de la table de vérité sur 8 peuvent se calculer avec une porte à majorité, le reste demandant quelques portes logiques pour faire la correction. L'implémentation en circuit demande donc trois choses :
* de calculer la retenue sortante et de quoi l'inverser ;
* un circuit qui vérifie si tous les bits opérande valent 0 : une porte NOR ou OU fait l'affaire ;
* un circuit qui vérifie s'ils valent tous 1, à savoir une porte ET.
Ensuite, on combine le résultat des trois circuits précédents pour obtenir le résultat final. La combinaison est le fait du circuit en jaune dans le schéma ci-dessous.
[[File:Full adder basé sur une modification de la retenue.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue]]
Le circuit en jaune est assez simple. Quand la porte ET sort un 1, la sortie doit être forcée à 1. Par contre, quand la porte OU sort un zéro, la sortie doit être forcée à 0. Il suffit donc d'enchainer un circuit de mise à 1 et un circuit de mise à 0. Nous avons vu dans le chapitre sur les opérations de masquage qu'il s'agit respectivement d'une porte OU et d'une porte ET.
[[File:Full adder basé sur une modification de la retenue interne, au complet.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue interne, au complet]]
Divers processeurs utilisaient des additionneurs complets de ce type : le fameux Z80, l'Intel 4004 et de l'Intel 8008 sont les exemples les plus notables. Pourtant, on pourrait s'étonner qu'un tel circuit ait existé. Il utilise beaucoup de portes logiques, a une profondeur logique supérieure : il n'a rien d'avantageux. Sauf qu'il était utilisé sur d'anciens processeurs, qui utilisaient la technologie dite TTL, différente de la technologie CMOS des transistors modernes. Et avec la technologie TTL, il est possible de fusionner plusieurs portes logiques ET et NOR en une seule porte logique ET/OU/NON ! Un additionneur complet construit ainsi ne prenait que deux portes logiques : une pour le calcul de la retenue sortante, une autre pour le reste du circuit.
==L'addition non signée==
Voyons maintenant un circuit capable d'additionner deux nombres entiers: l''''additionneur'''. Dans la version qu'on va voir, ce circuit manipulera des nombres strictement positifs. L'addition des nombres codés en complètement à deux sera vu dans une section ultérieure.
===L'additionneur série===
Il est possible d'additionner deux nombres bit par bit, gr$ace à un additionneur complet associé à plusieurs registres à décalages. Les opérandes sont placées chacune dans un registre à décalage, et l'additionneur complet utilise les bits sortants de ces registres à décalage. Le bit du résultat est envoyé au registre à décalage pour le résultat. La retenue de l'addition est stockée dans une bascule de 1 bit, en attente du prochain cycle d'horloge. Un tel additionneur est appelé un '''additionneur série'''. Il a été utilisé sur d'anciens ordinateurs dans les années 50-60, aussi bien des prototypes que des ordinateurs commerciaux.
[[File:Additionneur série.jpg|centre|vignette|upright=2.5|Additionneur série.]]
===L'additionneur à propagation de retenue===
L''''additionneur à propagation de retenue''' est aussi appelé l'additionneur ''ripple carry'', c'est son nom anglais. J'utiliserais occasionnellement le nom anglais dans ce qui suit, car celui-ci est plus court. Ilutilise un additionneur complet pour chaque colonne de l'addition. Pour gérer les retenues, il suffit ainsi de câbler des additionneurs complets les uns à la suite des autres, chacun envoyant sa retenue à la colonne suivante.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Notons la présence de la retenue sortante, qui est utilisée pour détecter les débordements d'entier, ainsi que pour d'autres opérations. Le bit de retenue final est souvent stocké dans un registre spécial du processeur (généralement appelé ''carry flag'').
Notez aussi, à droite du schéma précédent, l'entrée de retenue <math>\text{R}_\text{Entrée}</math>. Elle est très utile pour l'implémentation de certaines opérations comme l'inversion de signe, la soustraction, l'incrémentation, etc. Certains processeurs sont capables de faire une opération appelée ADC, ADDC ou autre nom signifiant ''Addition with Carry'', qui permet de faire le calcul A + B + Retenue (la retenue en question est la retenue sortante de l'addition précédente, stockée dans le registre ''carry flag''). Son utilité principale est de permettre des additions d'entiers plus grands que ceux supportés par le processeur. Par exemple, cela permet de faire des additions d'entiers 32 bits sur un processeur 16 bits.
[[File:Adder Network 3 Bit Sum Delay 003.svg|vignette|upright=0.5|Propagation de retenue dans l'additionneur.]]
Cet additionneur utilise très peu de portes logiques, ce qui fait qu'il était utilisé sur certains processeurs 8 et 16 bits, dont le budget en portes logiques était limité. Bien que très simple, cet additionneur est cependant peu performant. Son temps de calcul est proportionnel à la taille des opérandes. Par exemple, additionner deux opérandes de 32 bits prendra deux fois plus de temps que pour des opérandes de 16 bits. La raison est que les retenues doivent se propager à travers le circuit, du premier additionneur jusqu'au dernier.
L'addition étant une opération fréquente, d'autres additionneurs ont été inventés. Ils utilisent des optimisations qui utilisent plus de circuits pour gagner en rapidité. Mais avant de voir ces optimisations, nous allons voir s'il est possible d'optimiser les additionneurs à propagation de retenue.
===L'additionneur à saut de retenue===
L'additionneur à propagation de retenue peut être optimisé de deux manières. La première accélère la propagation de la retenue, en agissant au niveau des additionneurs complets. La ''Manchester carry chain'' est une optimisation de ce type, mais on ne peut pas encore expliquer à ce stade du cours, car elle optimisait les additionneurs complets au niveau des transistors eux-mêmes. Elle a été utilisée sur de nombreux processeurs connus, comme le 8086 d'Intel ou le Z80.
La seconde manière rend l'addition plus rapide dans certains cas. Le résultat est un additionneur dont le temps de calcul est variable. Le calcul prendra quelques cycles d'horloges avec certains opérandes, mais d'autres opérandes ne verront aucune amélioration. Il n'améliore pas le pire des cas, dans lequel la retenue doit être propagée du début à la fin, du bit de poids faible au bit de poids fort. Mais dans d'autres cas, il permet d'avoir le résultat en avance. L'optimisation en question donne l''''additionneur à saut de retenue''' (''carry-skip adder'').
Il est composé de blocs qui additionnent 4/5 bits, rarement plus. La taille d'un bloc varie d'un additionneur à l'autre, elle peut même varier dans l'additionneur, mais nous allons prendre un exemple où les blocs font 4 bits. L'idée est de détecter précocement si la retenue entrante est propagée à travers tout le bloc. Si la retenue est propagée dans tous le bloc, on peut directement l'envoyer sur la sortie de retenue, la retenue saute le bloc entier. Dans le cas contraire, on doit calculer la retenue normalement. Le choix entre les deux est le fait d'un multiplexeur. Toute la difficulté est de savoir comment commander le multiplexeur.
[[File:Carry skip adder, principe de base.png|centre|vignette|upright=2.5|Carry skip adder : principe de base]]
Pour savoir si une retenue est propagée dans un bloc entier, il faut utiliser des additionneurs complets, qui ont en plus une sortie P indiquant que l'additionneur propage la retenue. Le bloc propage une retenue si chaque additionneur complet propage la retenue, donc s'ils ont tous leur sortie P à 1. Un simple ET logique suffit à déterminer si c'est le cas. Le signal de commande du multiplexeur est donc un vulgaire ET entre toutes les sorties P des additionneurs complets du bloc.
[[File:Additionneur à saut de retenue.png|centre|vignette|upright=2.5|Calcul de la commande du MUX.]]
Voici ce que ça donne pour un bloc de 4 bits :
[[File:CSAdder4Bit.svg|centre|vignette|upright=1.5|Additionneur ''carry skip'' : bloc de 4 bits.]]
L'additionneur à saut de retenue est construit en assemblant plusieurs blocs de ce type.
[[File:BCSAdder16Bit.svg|centre|vignette|upright=3|Additionneur à saut de retenue.]]
===L'additionneur à sélection de retenue===
L''''additionneur à sélection de retenue''' utilise aussi des blocs, comme les additionneurs précédents. L'addition se fait en deux versions : une avec la retenue du bloc précédent valant zéro, et une autre version avec la retenue du bloc précédent valant 1. Il suffira alors de choisir le bon résultat avec un multiplexeur, une fois cette retenue connue. On gagne ainsi du temps en calculant à l'avance les valeurs de certains bits du résultat, sans connaître la valeur de la retenue. Petit détail : sur certains additionneurs à sélection de retenue, les blocs de base n'ont pas la même taille. Cela permet de tenir compte des temps de propagation des retenues entre les blocs.
[[File:Additionneur à sélection de retenue.png|centre|vignette|upright=1.5|Additionneur à sélection de retenue avec seulement deux blocs.]]
Dans les exemples du dessus, chaque sous-additionneur étaient des additionneurs à propagation de retenue. Mais ce n'est pas une obligation, et tout autre type d’additionneur peut être utilisé. Par exemple, on peut faire en sorte que les sous-additionneurs soient eux-mêmes des additionneurs à sélection de retenue, et poursuivre ainsi de suite, récursivement. On obtient alors un '''additionneur à somme conditionnelle''', plus rapide que l'additionneur à sélection de retenue, mais qui utilise beaucoup plus de portes logiques.
===Les additionneurs à anticipation de retenue===
Les '''additionneurs à anticipation de retenue''' calculent chaque retenue sans avoir à propager les retenues précédentes. Au lieu de calculer les retenues une par une, ils calculent toutes les retenues en parallèle, à partir des bits des opérandes. Une fois les retenues pré-calculées, il suffit de les additionner avec les deux bits adéquats, pour obtenir le résultat. Ces additionneurs sont composés de deux parties :
* un circuit qui pré-calcule les retenues, sans les propager, directement à partir des opérandes ;
* d'une couche d'additionneurs complets, qui additionnent chacun deux bits d'opérande et la retenue pré-calculée.
[[File:Additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Additionneur à anticipation de retenue.]]
Le calcul des retenues se fait en deux étapes. La première détermine si les retenues sont propagées ou générées, pour chaque colonne. Elle détermine, pour chaque colonne, les bits P et G qui indiquent respectivement : qu'une retenue a été générée sur cette colonne, que la colonne propage la retenue précédente. La seconde étape utilise ces signaux P et G pour déterminer la retenue sur chaque colonne. Le circuit de calcul des retenues est donc composé de deux parties : une couche de demi-additionneurs pour générer les signaux P et G, un circuit de calcul des retenues proprement dit.
[[File:Circuit complet d'un additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Circuit complet d'un additionneur à anticipation de retenue.]]
Pour rappel, la retenue de la colonne i, notée <math>C_i</math> est égale à :
: <math>G_i + (P_i . C_{i-1})</math>, avec G le signal de génération de retenue, P le signal de propagation de retenue.
Si on utilisait cette formule sans trop réfléchir, on retomberait sur un additionneur à propagation de retenue inutilement compliqué. L'astuce des additionneurs à anticipation de retenue consiste à remplacer le terme Ci−1 par sa valeur calculée avant. Par exemple, je prends un additionneur 4 bits. Je dispose de deux nombres A et B, contenant chacun 4 bits : A3, A2, A1, et A0 pour le nombre A, et B3, B2, B1, et B0 pour le nombre B. Si j'effectue les remplacements, j'obtiens les formules suivantes :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Ces formules nous permettent de déduire la valeur d'une retenue directement : il reste alors à créer un circuit qui implémente ces formules, et le tour est joué.
[[File:Four bit adder with carry lookahead.svg|centre|vignette|upright=1.5|Additionneur à anticipation de retenue de 4 bits.]]
Plus haut, j'ai dit que le circuit était composé de trois couches : une couche de demi-additionneur pour les signaux P et G ; un circuit d'anticipation de retenue, puis l'addition finale. Mais les deux bits d'opérandes sont déjà additionnés dans la couche de demi-additionneur, autant réutiliser le résultat pour l'additionner à la retenue. Pour éliminer toute redondance de ce type, il est possible d'utiliser des additionneurs partiels (des additionneurs où la sortie de retenue a été remplacée par deux sorties P et G). L'additionneur final est alors composé de plusieurs additionneurs complets de ce type, couplé à un circuit d'anticipation de retenue.
[[File:Cla4bits.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue de 4 bits.]]
Les additionneurs à anticipation de retenue sont plus rapides que les additionneurs à propagation de retenue. La raison est que les retenues sont calculées rapidement, dans un temps plus petit qu'en propageant la retenue. Pour rentrer dans les détails mathématiques, le temps de calcul n'est pas proportionnel au nombre de bits, mais proportionnel au logarithme de ce nombre de bits. Par contre, le gain en temps de calcul s'accompagne d'un cout en portes logiques conséquent. Le circuit qui calcule les retenues anticipées utilise beaucoup de portes logiques.
===Les additionneurs à calcul parallèle de préfixe===
Du fait du cout en portes logiques, utiliser un additionneur à anticipation de retenue est une solution pratique pour additionner 4 ou 8 bits. Mais pour des opérandes plus grands, de 16 ou 32 bits, cela utiliserait trop de portes logiques. Une partie de ce cout en portes logiques est cependant lié à des redondances. Si on analyse deux retenues différentes, à des colonnes différentes. Les circuits qui calculent ces deux retenues seront différents, mais ils contiendront des sous-circuits identiques. Pour vous en rendre compte, reprenons les quatre équations obtenues précédemment et essayez de trouver des redondances :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Vous devriez trouver :
* le terme ''P0 · C0'' dans toutes les lignes ;
* les termes ''P1 · P0 · C0'' et ''P1 · P0'' dans les trois dernières lignes ;
* le terme ''P2 · P1 · P0 · C0'' ; ''P2 · P1 · G0'' et ''P2 · G1'' dans les deux dernières lignes.
Il existe plusieurs manières d'exploiter de telles redondances, exploitées dans les '''additionneurs à calcul parallèle de préfixe'''. L'optimisation apportée est de générer des signaux ''propagate'' et ''generate'' pour des groupes de 2, 3, 4, ..., N colonnes. Par exemple, il est possible de générer un signal ''P 0 vers 3'', qui indique si la retenue de la première colonne est propagée jusqu'à la 4ème colonne. Un autre exemple est un signal qui indique qu'une retenue a été générée entre les colonnes 4 à 7 génèrent une retenue ou non. En clair, les signaux P et G ont maintenant un intervalle, qui précise de quelle colonne vers quelle colonne se fait la propagation, ou entre quelles colonnes se fait la génération.
Il est possible de combiner les signaux P et G de deux groupes de colonne, s'ils sont contiguës (ils peuvent aussi se recouvrir). Par exemple, il est possible de calculer les bits P et G pour les colonnes 0 à 10, à partir des deux signaux P/G des colonnes 0-4 et 5-10. Pour cela, les équations sont assez simples. Si je prends deux groupes nommés A et B, avec A pour les colonnes de poids fort et B celles de poids faible, on a :
: <math>P_{sortie} = P_A . P_B</math>
: <math>G_{sortie} = G_A . P_A . G_B</math>
Pour les bits des colonnes 2, 4, 8, 16, 32, 64, et autres, le calcul est simple. L'idée est de grouper les colonnes par groupes de 2. Puis, on calcule chaque groupe de 4 bits à partir de deux groupes de 2. Puis les groupes de 8 bits sont calculés à partir de deux groupes de 4 bits, et ainsi de suite pour toute puissance de deux.
[[File:Squelette d'un additionneur à calcul parallèle de préfixe.jpg|centre|vignette|upright=2|Squelette d'un additionneur à calcul parallèle de préfixe]]
Les seules difficultés surviennent pour les colonnes dont le nombre n'est pas une puissance de deux. Il y a plusieurs manières pour subdiviser ces colonnes en intervalles plus petits et combiner le tout. Et elles donnent chacune des additionneurs différents. Ils portent les noms d'''additionneur de Ladner-Fisher'', d'''additionneur de Brent-Kung'', d'''additionneur de Kogge-Stone'', etc. Dans le cas général, l'additionneur de Kogge-Stone est un des plus rapide, mais aussi un des plus gourmand en circuits, alors que c'est l'inverse pour l'additionneur de Brent-Kung. Les deux additionneurs sont illustrés ci-dessous, pour des opérandes de 8 bits. Dans ceux-ci, chaque losange calculent les bits P et G, à partir des signaux P et G de deux groupes précédents.
{|
|[[File:Brent-kung-8-bit.png|vignette|Brent-kung-8-bit]]
|[[File:Kogge-stone-8-bit.png|vignette|upright=1.2|Kogge-stone-8-bit]]
|}
===Le calcul parallèle de la retenue===
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
L'anticipation de retenue est très rapide, mais utilise beaucoup de circuits, même en utilisant le calcul parallèle de préfixe. Les additionneurs à propagation de retenue font eux le compromis inverse. Mais il existe des intermédiaires, qui visent à obtenir un compromis entre performance et cout en portes logiques.
Un de ces compromis découpe un additionneur de 16/32/64 bits en additionneurs de 4/5 bits, qui sont enchainés en utilisant la propagation de retenue. Par contre, les petits additionneurs utilisent l'anticipation de retenue à l'intérieur. Ces additionneurs simples seront nommés ''blocs'' dans ce qui suit, et l'un d'entre eux est illustré ci-contre. Vous remarquerez qu'un bloc a une retenue entrante et une retenue sortante. Par exemple, on peut utiliser 4 additionneurs à anticipation de retenue de 4 bits, et propager les retenues entre eux. Le premier bloc calcule une retenue, qui est propagée au second bloc. Le second bloc calcule lui aussi une retenue, qui est propagée au troisième bloc, etc.
[[File:4008 Functional Diagram.svg|vignette|upright=1|4008 Functional Diagram]]
Le gain en performance est significatif, mais il est possible de faire presque aussi rapide, tout en économisant beaucoup de circuits. L'idée est de simplifier les additionneurs 4 bits, afin de ne pas utiliser une anticipation de retenue complète. L'anticipation de retenue n'est utilisée que pour la retenue sortante, alors que les bits de somme sont calculées avec propagation de retenue. La retenue sortante est donc calculée en parallèle de l'addition. Chaque bloc contient, à côté d'un additionneur proprement dit, un circuit qui calcule la retenue sortante. Il existe de nombreuses manières de calculer la retenue sortante.
Le gain en portes logiques est assez significatif. En effet, on se passe de circuits d'anticipation de retenue pour les 4 bits de somme, on ne garde que celui pour la retenue sortante. Le fait d'utiliser la propagation de retenue pour calculer les 4 bits de somme est censé avoir un léger cout en performance, mais il se trouve que ce cout est très faible. En effet, le résultat n'est connu qu'une fois les retenues propagées jusqu'au dernier bloc. Et malgré l'anticipation des retenues, la propagation est assez lente. les additionneurs à propagation de retenue auront terminé leur travail bien avant.
Un exemple est celui de l'additionneur CMOS 4008, un additionneur de 4 bit. Il est composé en trois sections. Une première couche de demi-additionneurs calcule les signaux P et G utilisés à la fois pour l'anticipation de la retenue sortante, que pour calculer les bits de somme. Le circuit de calcul de la retenue sortante utilise l'anticipation de retenue. Le reste du circuit fait l'addition en propageant les retenues.
[[File:CMOS 4008, circuit découpé en sections.png|centre|vignette|upright=1.5|CMOS 4008, circuit découpé en sections]]
===Les additionneurs à anticipation de retenue multi-niveau===
Dans cette section, nous allons voir des additionneurs conçus en assemblant des additionneurs plus simples, qui additionnent environ 4 à 5 bits, parfois plus, parfois moins.
Les blocs sont tous identiques dans le cas le plus simple, mais il est possible d'utiliser des blocs de taille variable. Par exemple, le premier bloc peut avoir des opérandes de 6 bits, le second des opérandes de 7 bits, etc. Faire ainsi permet de gagner un petit peu en performances, si la taille de chaque bloc est bien choisie. La raison est une question de temps de propagation des retenues. La retenue met plus de temps à se propager à travers 8 blocs qu'à travers 4, ce qui prend plus de temps qu'à travers 2 blocs, etc. En tenir compte fait que la taille des blocs tend à augmenter ou diminuer quand on se rapproche des bits de poids fort.
Il est possible d'utiliser l'anticipation de retenue entre deux blocs. Par exemple, il est possible de créer un additionneur 16 bits en utilisant 4 additionneurs/blocs de 4 bits. L'additionneur utilisé peut être un additionneur à propagation de retenue, ou à anticipation de retenue, peu importe.
Pour cela, les additionneurs de 4 bits doivent fournir deux sorties P et G. Les deux sorties indiquent que le bloc soit a généré une retenue, soit a propagé la retenue entrante. Par propagé, on veut dire que la retenue entrante du bloc s'est retrouvée sur la sortie de retenue du bloc. C'est le cas si les 4 colonnes propagent la retenue, ce qui signifie que :
: <math>P_\text{4 bits} = P_0 \cdot P_1 \cdot P_2 \cdot P_3</math>
Pour la génération, cela veut dire qu'une colonne a générée une retenue, qui a été propagée jusqu'à la sortie. En clair, pour chaque colonne, il faut que le G = 1, et que les signaux P des colonnes soient à 1. Pour les 4 colonnes, cela donne :
: <math>G_\text{4 bits} = G_3 + G_2 \cdot P_3 + G_1 \cdot P_3 \cdot P_2 + G_0 \cdot P_3 \cdot P_2 \cdot P_1</math>
[[File:Cla4bitsPG.png|centre|vignette|upright=2|Additionneur à anticipation de retenue de 4 bits.]]
Pour combiner 4 blocs de 4 bits, on devra donc combiner 4 bits P et G, avec un circuit d'anticipation de retenue.
[[File:16-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 16 bits.]]
Il est même possible d'aller plus loin et de combiner 4 additionneurs précédents pour obtenir un additionneur 64 bits.
[[File:64-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 64 bits.]]
Le résultat est un additionneur à plusieurs niveaux d’anticipation de retenue. Une anticipation de retenue dans un blocs de 4 bits (facultative), entre blocs de 4 bits, entre blocs de 16 bits. La même logique peut être utilisée avec des blocs de taille différente de 4, 16 et 64 bits.
==L'addition signée et la soustraction==
Après avoir vu l'addition, il est logique de passer à la soustraction, les deux opérations étant très proches. Si on sait câbler une addition entre entiers positifs, câbler une soustraction n'est pas très compliqué. De plus, la soustraction permet de faire des additions de nombres signés.
===Le soustracteur pour opérandes entiers===
Pour soustraire deux nombres entiers, on peut adapter l'algorithme de soustraction utilisé en décimal, celui que vous avez appris à l'école. Celui-ci ressemble fortement à l'algorithme d'addition : on soustrait les bits de même poids, et on propage éventuellement une retenue sur la colonne suivante. À la différence de l'addition, la retenue est soustraite, et non ajoutée. La table de soustraction nous dit quel est le résultat de la soustraction de deux bits. La voici :
* 0 - 0 = 0 ;
* 0 - 1 = 1 et une retenue ;
* 1 - 0 = 1 ;
* 1 - 1 = 0.
[[File:Algebra1 05 fig014.svg|centre|vignette|Soustraction en binaire, avec les retenues en rouge.]]
La table de soustraction peut servir de table de vérité pour construire un circuit qui soustrait deux bits. Celui-ci est appelé un '''demi-soustracteur'''. Il ressemble beaucoup à un demi-additionneur, les différences se résumant à une porte NON ajoutée pour le calcul de la retenue.
[[File:Half Subtractor Vektor.svg|centre|vignette|Demi-soustracteur.]]
Comme pour l'additionneur, deux demi-soustracteurs peuvent être combinés pour donner un '''soustracteur complet'''. Le calcul de la retenue se fait en combinant les deux retenues des demi-soustracteurs avec une porte OU. Les soustracteurs complets sont utilisés pour créer des soustracteurs à propagation de retenue ou tout autre circuit soustracteur, sur le même modèle que les additionneurs.
[[File:FullSubtractor.svg|centre|vignette|upright=2|Soustracteur complet.]]
Vous l'aurez sans doute remarqué, mais le demi-soustracteur ressemble beaucoup au demi-additionneur. La seule différence est une porte NON ajoutée au bon endroit dans le demi-soustracteur.
{|
|[[File:Half-adder.svg|vignette|Demi-additionneur]]
|[[File:Half Subtractor Vektor.svg|vignette|upright=1.3|Demi-soustracteur]]
|}
La conséquence est qu'il est possible de créer un circuit capable de faire à la fois des additions et des soustractions. L'idée est de remplacer la porte NON du demi-soustracteur par un inverseur commandable. Si on envoie un zéro sur l'entrée de commande, l'inverseur commandable se comportera comme une porte OUI. Le circuit sera alors identique à un demi-additionneur. Si on envoie un 1 sur l'entrée de commande, l'inverseur commandable se comportera comme une porte NON. le circuit sera alors un demi-soustracteur. Il faut faire cela pour chaque demi-soustracteur, ce qui donne ceci pour un soustracteur complet. le résultat est alors appelé un '''additionneur-soustracteur complet'''.
[[File:Additionneur-soustracteur complet.png|centre|vignette|upright=2|Additionneur-soustracteur complet]]
===L'additionneur-soustracteur pour opérandes codées en complément à deux===
Étudions le cas de la soustraction en complément à deux, dans l'objectif de créer un circuit soustracteur. Vous savez sûrement que a−b et a+(−b) sont deux expressions équivalentes. Et en complément à deux, − b = not(b) + 1. Dit autrement, a − b = a + not(b) + 1. On pourrait se dire qu'il faut deux additionneurs pour faire le calcul, mais la majorité des additionneurs possède une entrée de retenue pour incrémenter le résultat de l'addition. Un soustracteur en complément à deux est donc simplement composé d'un additionneur et d'un inverseur.
[[File:Additionneur soustracteur.png|centre|vignette|upright=2|Soustracteur en complément à deux.]]
Il est possible de créer un circuit capable d'effectuer soit une addition, soit une soustraction : il suffit de remplacer l'inverseur par un inverseur commandable, qui peut être désactivé. On a vu comment créer un tel inverseur commandable dans le chapitre sur les circuits combinatoires. On peut remarquer que l'entrée de retenue et l'entrée de commande de l'inverseur sont activées en même temps : on peut fusionner les deux signaux en un seul.
[[File:Additionneur-soustracteur en complément à deux.png|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux.]]
Une implémentation alternative est la suivante. Elle remplace l'inverseur commandable par un multiplexeur.
[[File:4-bit ripple carry adder-subtracter.svg|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux, version alternative.]]
===L'additionneur-soustracteur pour opérandes codées en signe-magnitude===
Passons maintenant aux nombres codés en signe-valeur absolue, les deux opérandes étant notées A et B. Suivant les signes des deux opérandes, on a quatre cas possibles : A + B, A − B (B négatif), −A + B (A négatif) et −A − B (A et B négatifs). Une astuce est que le circuit n'a besoin que de calculer A + B et A − B : il peut les inverser pour obtenir − A − B ou B − A. A + B et A − B peuvent se calculer avec un additionneur-soustracteur, reste à corriger le résultat. Il suffit de lui ajouter un inverseur commandable pour obtenir le circuit d'addition finale.
[[File:Additionneur en signe-valeur absolue.png|centre|vignette|upright=2|Additionneur en signe-valeur absolue.]]
Toute la difficulté tient dans le calcul du bit de signe du résultat, quand interviennent des soustractions. Autant l'addition de deux nombres de même signe (A + B et −A − B) ne pose aucun problème, autant les soustractions posent problème (A − B et −A + B). Suivant que <math>A<B</math> ou que <math>A>B</math>, le signe du résultat ne sera pas le même. Déterminer le signe du résultat se fait en regardant les bits de débordement d'entier, comme on le verra plus bas.
===L'additionneur-soustracteur pour opérandes codées en représentation par excès===
Passons maintenant aux nombres codés en représentation par excès. On pourrait croire que ces nombres s'additionnent comme des nombres non-signés, mais ce serait oublier la présence du biais, qui pose problème. Dans les cas de nombres signés gérés avec un biais, voyons ce que donne l'addition de deux nombres :
: <math>( n_1 + biais ) + ( n_2 + biais ) = n_1 + n_2 + 2 \times biais</math>
Or, le résultat correct serait :
: <math>n_1 + n_2 + biais</math>
En effectuant l'addition telle quelle, le biais est compté deux fois. On doit donc le soustraire après l'addition pour obtenir le résultat correct.
Même chose pour la soustraction qui donne ceci :
: <math>(n_1 + biais) - (n_2 + biais) = n_1 - n_2</math>
Or, le résultat correct serait :
: <math> ( n_1 - n_2 ) + biais </math>
Il faut rajouter le biais pour obtenir l'exposant correct.
On a donc besoin de deux additionneurs/soustracteurs : un pour additionner/soustraire les représentations binaires des opérandes, et un autre pour ajouter/retirer le biais en trop/manquant.
==L'additionneur BCD==
Maintenant, voyons un additionneur qui additionne deux entiers au format BCD. Pour cela, nous allons devoir passer par deux étapes. La première est de créer un circuit capable d'additionneur deux chiffres BCD. Ensuite, nous allons voir comment enchaîner ces circuits pour créer un additionneur BCD complet.
===L'additionneur BCD qui fait l'opération chiffre par chiffre===
Nous allons commencer par voir un additionneur qui additionne deux chiffres en BCD, une sorte d'équivalent BCD de l'additionneur complet. Il fournit un résultat sur 4 bits et une retenue qui est mise à 1 si le résultat dépasse 10 (la limite d'un chiffre BCD). Les deux opérandes sont des chiffres BCD codés sur 4 bits et sont additionnés en binaire par un additionneur des plus normaux, similaire à ceux vus plus haut. Le résultat est alors un entier codé en binaire, sur 5 bits, qu'on corrige/convertit pour obtenir un chiffre BCD et une retenue sortante.
Pour corriger le résultat, une idée intuitive serait de prendre le résultat et de faire une division par 10. Le quotient donne la retenue, alors que le reste est le résultat, le chiffre BCD. Mais un circuit diviseur par 10 utilise beaucoup de portes logiques, ce qui ne vaut pas le coup. Une autre méthode détecte si le résultat est égal ou supérieur à 10, ce qui correspond à un "débordement" (on dépasse les limites d'un chiffre BCD). Si le résultat est plus petit que 10, il n'y a rien à faire : le résultat est bon et la retenue est de zéro. Par contre, si le résultat vaut 10 ou plus, il faut corriger le résultat et générer une retenue à 1.
Il faut donc ajouter un circuit qui détecte si le résultat est supérieur à 9, qui calcule directement la retenue. Ce circuit peut se fabriquer simplement à partir de sa table de vérité, ou en utilisant les techniques que nous verrons dans un chapitre ultérieur sur les comparateurs. La solution la plus simple est clairement d'utiliser la table de vérité, ce qui est très simple, assez pour être laissé en exercice au lecteur. Pour comprendre comment corriger le résultat, établissons une table de vérité qui associe le résultat et le résultat corrigé. L'entrée vaut au minimum 10 et au maximum 9 + 9 = 18. On considère la sortie comme un tout, la retenue étant un 5ème bit, le bit de poids fort.
{|class="wikitable"
|-
! colspan="5" | Entrée
!
! rowspan="10" |
! Retenue
! Résultat corrigé (sans retenue)
! interprétation de la sortie en binaire (retenue inclue)
|-
| 0 || 1 || 0 || 1 || 0
| (10)
| 1 || 0000
| (16)
|-
| 0 || 1 || 0 || 1 || 1
| (11)
| 1 || 0001
| (17)
|-
| 0 || 1 || 1 || 0 || 0
| (12)
| 1 || 0010
| (18)
|-
| 0 || 1 || 1 || 0 || 1
| (13)
| 1 || 0011
| (19)
|-
| 0 || 1 || 1 || 1 || 0
| (14)
| 1 || 0100
| (20)
|-
| 0 || 1 || 1 || 1 || 1
| (15)
| 1 || 0101
| (21)
|-
| 1 || 0 || 0 || 0 || 0
| (16)
| 1 || 0110
| (22)
|-
| 1 || 0 || 0 || 0 || 1
| (17)
| 1 || 0111
| (23)
|-
| 1 || 0 || 0 || 1 || 0
| (18)
| 1 || 1000
| (24)
|}
En analysant le tableau, on voit que pour corriger le résultat, il suffit d'ajouter 6. La raison est que le résultat déborde d'un nibble à 16 en binaire, mais à 10 en décimal : il suffit d'ajouter la différence entre les deux, à savoir 6, et le débordement binaire fait son travail. Donc, la correction après une addition est très simple : si le résultat dépasse 9, on ajoute 6.
On peut maintenant implémenter l'additionneur BCD, en combinant le comparateur avec 10, le circuit de correction, et l'additionneur. La première solution calcule deux versions du résultat : la version corrigée, la version normale. Le choix entre les deux est réalisée par un multiplexeur, commandé par le comparateur.
[[File:Additionneur BCD.png|centre|vignette|upright=1.5|Additionneur BCD]]
L'autre solution utilise un circuit commandable qui soit additionne 6, soit ne fait rien. Le choix entre les deux est commandé par le bit calculé par le comparateur.
[[File:Additionneur BCD, seconde version.png|centre|vignette|upright=1.5|Additionneur BCD, seconde version.]]
Une version alternative du circuit précédent est la suivante. Il contient deux additionneurs : un pour additionner les deux chiffres BCD, un autre pour additionner 6 si besoin. Le résultat du comparateur est directement utilisé pour générer l'opérande du second additionneur : 0 ou 6. Le circuit est simple à concevoir, mais gaspille beaucoup de circuit. Idéalement, il vaudrait mieux utiliser un circuit combinatoire d'addition avec une constante.
[[File:Adder 4 Bit BCD.svg|centre|vignette|upright=2|Additionneur BCD, circuit complet.]]
Pour obtenir un additionneur BCD complet, il suffit d’enchaîner les additionneurs précédents, comme on le ferait avec les additionneurs complets dans un additionneur à propagation de retenue. Au final, l'additionneur BCD est beaucoup plus compliqué qu'un additionneur normal, car il rajoute un comparateur ">9", un petit additionneur pour ajouter 6 et éventuellement d'autres circuits. De plus, il est difficile d'appliquer les optimisations disponibles sur les additionneurs non-BCD. Notamment, les circuits d'anticipation de retenue sont totalement à refaire et le résultat est relativement compliqué. C'est ce qui explique pourquoi le BCD a progressivement été abandonné au profit du binaire simple.
La soustraction en BCD se fait comme en binaire : le nombre à soustraire est remplacé par son complément, le circuit additionne le complément et l'autre opérande, le débordement d'entier fait que le résultat marche. Sauf qu'ici, le complément est un complément à 9. Il se calcule chiffre par chiffre : chaque chiffre est remplacé par (9 - le chiffre en question).
===L'additionneur BCD par ajustement décimal===
L'additionneur BCD précédent effectuait son travail chiffre BCD par chiffre BCD, mais il existe des additionneurs BCD qui font autrement. Sur les premiers processeurs x86, il n'y avait pas d'opération d'addition BCD proprement dit, seulement une addition binaire normale de 8, 16 ou 32 bits. Par contre, elle était secondée par une opération dite d''''ajustement décimal''' qui transformait un nombre binaire en nombre codé en BCD. L'opération d'ajustement décimal prenait un opérande de 8 bits codé en binaire et fournissait un résultat de la même taille, c'est à dire deux chiffres BCD. Effectuer une addition BCD demandait donc de faire deux opérations à la suite : une addition binaire simple, suivie par l'opération d'ajustement décimal. Cela permettait de gérer des nombres entiers en binaire usuel et des entiers BCD sans avoir deux instructions d'addition séparées pour les deux, sans compter que cela simplifiait aussi les circuits d'addition.
L'ajustement décimal s'effectue en ajoutant une constante bien précise à l'opérande à convertir en BCD. L'idée est que la constante est découpée en morceaux de 4 bits, correspondant chacun à un chiffre BCD de l'opérande, chaque morceau contenant soit un 0, soit 6. Cela permet d'ajouter soit 0, soit 6, à chaque chiffre BCD, et donc de le corriger. La propagation des retenues d'un chiffre à l'autre est effectuée automatiquement par l'addition binaire de la constante. L'opération d'ajustement décimal calcule automatiquement la constante. Elle découpe l'opérande en ''nibbles'', vérifie si chaque ''nibble'' est supérieur ou égal à 10, puis détermine la valeur de chaque ''nibble'' de la constante finale. Par exemple, si je prends l'opérande 1001 1110, le ''nibble'' de poids faible déborde, alors que celui de poids fort non. La constante sera donc 0000 0110 : 0x06. Inversement, si le ''nibble'' de poids fort déborde et pas celui de poids faible, la constante sera alors 0x60. Et la constante est de 0x66 si les deux ''nibbles'' débordent, de 0x00 si aucun ne déborde.
Le circuit d’ajustement décimal est donc composé de trois étapes : deux étapes pour calculer la constante, et un circuit d'addition pour additionner cette constante au nombre de départ. La première étape découpe l'opérande en morceaux de 4 bits, en chiffres BCD, et vérifie si chacun d'entre eux vaut 10 ou plus. La seconde étape prend les résultats de la première étape, et les combine pour calculer la constante. Enfin, on trouve l'addition finale, qui était réalisée par un circuit d'addition utilisé à la fois pour l'ajustement décimal et l'addition binaire. La différence entre une addition normale et une opération d'ajustement décimal tient dans le fait que les deux premières étapes sont désactivées dans une addition normale.
[[File:Additionneur BCD parallèle.png|centre|vignette|upright=1.5|Additionneur BCD parallèle]]
===L'additionneur biquinaire===
Les entiers BCD ne sont qu'un des encodages hybrides entre décimal et binaire. L'encodage biquinaire est l'un d'entre eux et nous allons faire un rappel rapide à ce sujet. Pour simplifier, un chiffre encodé en biquinaire est composé de deux parties : un bit, couplé à une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits (0, 1, 2, 3 et 4). Le bit indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9.
Additionner deux nombres de biquinaire demande donc d'additionner deux parties quinaires encodées en ''one-hot'' et d'additionner deux bits. Mais attention : il faut tenir compte de la retenue de l'addition des parties quinaires. Et idéalement, il faut aussi tenir compte d'une retenue entrante, provenant de l'addition de la colonne de chiffres précédente. Toute la difficulté vient de la création de l'additionneur ''one-hot''. Heureusement, vu qu'il n'y a que 4-5 bits à additionner, il est souvent fabriqué à partir de sa table de vérité.
[[File:Additionneur bi-quinaire.png|centre|vignette|upright=2|Additionneur bi-quinaire]]
Un avantage du biquinaire est que le calcul du complément à 9 est très simple. Il faut pour cela : inverser la partie binaire avec une porte NON, puis inverser l'ordre des bits de la partie quinaire. Concrètement, le bit de poids faible devient le bit de poids fort, et ainsi de suite. Par exemple, une partie quinaire 01000 devient 00010, 10000 devient 00001, 00100 ne change pas, etc. Le tout peut se calculer avec une porte NON et 5 multiplexeurs.
===L'additionneur BCD avec calculs intermédiaires en biquinaire===
L'ordinateur IBM 1401, un ancien ''mainframe'' des années 60, utilisait un additionneur BCD un peu particulier. Les nombres étaient encodés en BCD dans la mémoire de l'ordinateur, mais les circuits de calcul utilisaient la représentation biquinaire. Lors d'un calcul, le processeur de l'ordinateur traduisait les chiffres BCD en représentation biquinaire, faisait une addition en biquinaire, avant de traduire le résultat en BCD normal.
Pour être précis, l'IBM 1401 utilisait une variante du biquinaire. L'encodage biquinaire de l'IBM 1401 est le suivant : la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre pair encodé par la partie quinaire. Si l'IBM 1401 utilisait cette variante du biquinaire, c'est car elle donnait des circuits de conversion BCD-biquinaire plus économes en portes logiques et plus rapides.
La partie binaire est le bit de poids faible du chiffre BCD, la partie biquinaire est calculée par un simple décodeur qui prend en entrée le chiffre BCD, amputé de son bit de poids faible. La traduction inverse demande d'utiliser un encodeur, à la place du décodeur. Par contre, le circuit d'addition biquinaire était plus compliqué du fait de la gestion des retenues. L'addition des parties binaires et quinaires se faisait en parallèle, dans deux additionneurs séparés. Cependant, l'addition des parties binaire fournit une retenue, qu'il faut prendre en compte. Pour cela, l'IBM 1401 disposait d'un troisième additionneur qui fournissait le résultat final, encodé en biquinaire.
[[File:Additionneur bi-quinaire de l'IBM 1401.png|centre|vignette|upright=2|Additionneur biquinaire de l'IBM 1401]]
Une implémentation moderne demanderait d'utiliser des portes ET combinées à des portes OU, le circuit pouvant être construit simplement à partir de sa table de vérité. Sur l'IBM 1401, le circuit était cependant différent, en raison de l'utilisation de OU câblés, des croisements de fils qui fonctionnent comme des portes OU, que nous n'avons pas encore vu pour le moment, mais qui seront détaillés dans quelques chapitres. Les OU câblés étaient utilisés pour simplifier le design du circuit, mais demandaient des portes logiques spécifiques, ce qui collait avec le fait que ce ''mainframe'' utilisait des transistors en Germanium. L'implémentation exacte est décrite dans cet article de blog, mais je ne recommande sa lecture qu'à ceux qui savent ce qu'est un OU câblé :
* [https://www.righto.com/2015/10/qui-binary-arithmetic-how-1960s-ibm.html Qui-binary arithmetic: how a 1960s IBM mainframe does math].
==Les débordements d'entier lors d'une addition/soustraction==
Les instructions arithmétiques manipulent des entiers codés sur un nombre fixe de bits, qui ne peuvent prendre leurs valeurs que dans un intervalle. Pour les nombres positifs, un ordinateur qui code ses entiers sur n bits pourra coder tous les entiers allant de 0 à <math>2^n -1</math>. Pour les nombres négatifs, l'intervalle est différent et dépend de la représentation utilisée. Dans le cas général, l'ordinateur peut coder les valeurs comprises de <math>N_\text{min}</math> à <math>N_\text{max}</math>. Si le résultat d'un calcul sort de cet intervalle, il ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math> , alors que la valeur basse vaut <math>N_\text{min} - 1</math> (avec <math>N_\text{max}</math> et <math>N_\text{min}</math> respectivement la plus grande et la plus petite valeur codable par l'ordinateur).
===La correction des débordements d'entier : l'arithmétique saturée===
Quand un débordement d'entier survient, tous les circuits de calcul ne procèdent pas de la même manière. Dans les grandes lignes, il y a deux réactions possibles : soit on corrige automatiquement le résultat du débordement, soit on ne fait rien et on se contente de détecter le débordement.
Si le débordement n'est pas corrigé automatiquement, le circuit ne conserve que les bits de poids faibles du résultat. Les bits en trop sont simplement ignorés. On dit qu'on utilise l''''arithmétique modulaire'''. Le problème avec ce genre d'arithmétique, c'est qu'une opération entre deux grands nombres peut donner un résultat très petit. Par exemple, si je dispose de registres 4 bits et que je souhaite faire l'addition 1111 + 0010 (ce qui donne 15 + 2), le résultat est censé être 10001 (17), ce qui est un résultat plus grand que la taille d'un registre. En conservant les 4 bits de poids faible, j’obtiens 0001 (1). En clair, un résultat très grand est transformé en un résultat très petit. Cela peut poser problèmes si on travaille uniquement avec des nombres positifs, mais c'est aussi utilisé pour coder des nombres en complément à deux.
D'autres circuits utilisent ce qu'on appelle l''''arithmétique saturée''' : si un calcul génère un débordement, on arrondi le résultat au plus grand entier supporté par le circuit. Les circuits capables de calculer en arithmétique saturée sont un peu plus complexes, vu qu'il faut rajouter des circuits pour corriger le résultat en cas de débordement. Il suffit généralement de rajouter un ''circuit de saturation'', qui prend en entrée le résultat et le corrige en cas de débordement. Ce circuit de saturation met la valeur maximale en sortie si un débordement survient, mais se contente de recopier le résultat du calcul sur sa sortie s'il n'y a pas de débordement. Typiquement, il est composé d'une couche de multiplexeurs, qui sélectionnent quelle valeur mettre sur la sortie : soit le résultat du calcul, soit le plus grand nombre entier géré par le processeur, soit le plus petit (pour les nombres négatifs/soustractions).
L'arithmétique saturée est utilisée pour les additions et soustractions, mais c'est plus rare pour les multiplications/divisions. Une des raisons est que le résultat d'une addition/soustraction prend un bit de plus que le résultat, là où les multiplications doublent le nombre de bits. Quand une addition déborde, le résultat réel est proche de la valeur maximale codable. mais quand une multiplication déborde, le résultat peut parfois valoir 200 à 60000 fois plus que la valeur maximale codable. Les calculs avec une valeur saturée/corrigée sont donc crédibles pour une suite d'additions, mais pas pour une suite de multiplications.
===La détection des débordements entiers===
Quand un débordement d'entier a eu lieu, il vaut mieux que l'additionneur prévienne ! Pour cela, l'additionneur a une '''sortie de débordement''', parfois nommée ''Overflow'', dont la valeur indique si l'addition a généré un débordement d'entier ou non. Reste que détecter un débordement ne se fait pas de la même manière selon que l'on parle d'un additionneur non-signé ou signé.
Pour les additionneur non-signés, l'additionneur calcule un bit de plus que ce qui est supporté par l'ordinateur. Par exemple, un additionneur 32 bits fournit un résultat sur 33 bits, un débordement d'entier a lieu quand le 33ème bit est à 1. Précisément, la sortie de débordement n'est autre que la retenue finale, celle fournie par le dernier additionneur complet. Le seul type de débordement possible est un débordement par le haut, où le résultat dépasse la valeur maximale. Avec l'arithmétique saturée, le circuit de saturation consiste en une seule couche de multiplexeurs, voire en un circuit de mise à la valeur maximale tel que vu dans le chapitre sur les opérations bits à bits.
[[File:Gestion des débordements d'entiers.png|centre|vignette|upright=2.5|Gestion des débordements d'entiers lors d'une addition non-signée.]]
Pour les additionneurs non-signés, la gestion des débordements d'entiers dépend fortement de la représentation signée. Nous allons étudier le cas du complément à deux. Si vous vous rappelez le chapitre 1, les calculs sur des nombres en complètement à deux utilisent les règles de l'arithmétique modulaire, c'est une condition nécessaire. À priori, on peut penser que dans ces conditions, les débordements d'entiers sont une chose parfaitement normale, qui nous permet d'avoir des résultats corrects. Néanmoins, certains débordements d'entiers peuvent survenir malgré tout et produire des bugs assez ennuyeux.
Si l'on tient en compte les règles du complément à deux, on sait que le bit de poids fort (le plus à gauche) permet de déterminer si le nombre est positif ou négatif : il indique le signe du nombre. Tout se passe comme si les entiers en complément à deux étaient codés sur un bit de moins, et avaient leur longueur amputé du bit de poids fort. Si le résultat d'un calcul écrase le bit de poids fort, il y a un débordement d'entiers. Il existe une règle simple qui permet de détecter ces débordements d'entiers. L'addition de deux nombres positifs ne peut pas être un nombre négatif. Si on additionne deux nombres dont le bit de signe est à 0 et que le bit de signe du résultat est à 1, on est en face d'un débordement d'entiers. Même chose pour deux nombres négatifs : le résultat de l'addition ne peut pas être positif. On peut résumer cela en une phrase : si deux nombres de même signe sont ajoutés, un débordement a lieu quand le bit du signe du résultat a le signe opposé.
Modifier les circuits d'au-dessus pour qu'ils détectent les débordements en complément à deux est simple comme bonjour : il suffit créer un petit circuit combinatoire qui prenne en entrée les bits de signe des opérandes et du résultat, et qui fasse le calcul de l'indicateur de débordements. Si l'on rédige sa table de vérité, on doit se retrouver avec la table suivante :
{|class="wikitable"
|-
!Entrées
!Sortie
|-
|000||0
|-
|001||1
|-
|010||0
|-
|011||0
|-
|100||0
|-
|101||0
|-
|110||1
|-
|111||0
|}
L'équation de ce circuit est la suivante, avec <math>S_a</math> et <math>S_b</math> les signes des deux opérandes, et <math>C_i</math> la retenue de la colonne précédente :
: <math> ( S_a . S_b . \overline{C_i} ) + ( \overline{S_a} . \overline{S_b} . C_i )</math>
En simplifiant, on obtient alors :
: <math>( S_a . S_b ) \oplus C_i</math>
Or, il se trouve que <math>S_a . S_b</math> est tout simplement la retenue en sortie du dernier additionneur, que nous noterons <math>C_o</math>. On trouve donc :
: <math>C_o \oplus C_i</math>
Il suffit donc de faire un XOR entre la dernière retenue et la précédente pour obtenir le bit de débordement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de décalage et de rotation
| prevText=Les circuits de décalage et de rotation
| next=Les circuits de comparaison
| nextText=Les circuits de comparaison
}}
</noinclude>
pt82zegl5olnyxlhghlrpr38s0sljcc
768664
768663
2026-06-25T19:57:56Z
Mewtow
31375
/* L'additionneur série */
768664
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir les circuits capables de faire une addition ou une soustraction, ainsi que quelques circuits spécialisés. Précisons cependant que les fabricants de processeurs travaillent d'arrache-pied pour trouver des moyens de rendre ces circuits de calcul plus rapides et plus économes en énergie. Autant vous dire que les circuits que vous allez voir sont vraiment des circuits qui font pâle figure comparé à ce que l'on peut trouver dans un vrai processeur commercial !
==Les circuits pour additionner 2 ou 3 bits==
L'addition se fait en binaire de la même manière qu'en décimal. On additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. La soustraction fonctionne sur le même principe, sur le même modèle qu'en décimal.
[[File:Binary Addition Demonstration.svg|centre|vignette|Exemple d'addition en binaire.]]
En clair, additionner deux nombres demande d'additionner 2 bits et une retenue sur chaque colonne, et de propager les retenues d'une colonne à l'autre. La propagation des retenues est quelque chose de simple en apparence, mais qui est sujet à des optimisations extraordinairement nombreuses. Aussi, pour simplifier l'exposition, nous allons voir comment gérer une colonne avant de voir comment sont propagées les retenues.
[[File:Full Adder Block.svg|vignette|upright=1|class=transparent|Additionneur complet.]]
Si on effectue une addition en colonne, on doit additionner les deux bits sur la colonne, mais aussi additionner une éventuelle retenue. Il faut donc créer un circuit qui additionne trois bits : deux bits de données, plus une retenue. Ce circuit qui additionne trois bits est appelé un '''additionneur complet'''. Il fournit en sortie deux bits : un bit de somme et une retenue sortante. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|-
||0||0||0|| ||0||0
|-
||0||0||1|| ||0||1
|-
||0||1||0|| ||0||1
|-
||0||1||1|| ||1||0
|-
||1||0||0|| ||0||1
|-
||1||0||1|| ||1||0
|-
||1||1||0|| ||1||0
|-
||1||1||1|| ||1||1
|}
Il est possible d'utiliser un tableau de Karnaugh pour traduire la table de vérité, mais elle donne un résultat assez compliqué. La retenue et le bit de somme sont calculés à part. La retenue est calculée avec trois portes ET et une porte OU. Le bit de somme est calculé lui avec 4 portes ET, une porte OU, et trois portes NON.
[[File:Full Adler with 3 entries.png|centre|vignette|upright=2|Additionneur complet, fabriqué à partir de la table de vérité.]]
D'autres méthodes donnent des résultats plus compréhensibles. Nous allons les voir dans la suite de cette section.
===L'additionneur complet basé sur des demi-additionneurs===
[[File:1-bit half-adder.svg|class=transparent|vignette|Demi-addtionneur.]]
Nous avons déjà vu comment additionner deux bits dans le chapitre sur les incrémenteurs, mais quelques rappels ne seront pas de trop. Le '''demi-additionneur''' est un circuit qui additionne deux bits. Il implémente la table d'addition, qui est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Mais un demi-additionneur n'additionne que deux bits, il manque de quoi additionner la retenue. Une solution pour cela est d'enchaîner deux demi-additionneurs : un qui additionne les deux bits de données, et un second qui additionne la retenue au résultat du précédent. La retenue finale se calcule en combinant les sorties de retenue des deux demi-additionneurs, avec une porte OU. Le résultat est un additionneur complet fabriqué avec deux demi-additionneurs. Pour vous en convaincre, établissez la table de vérité de ce circuit, vous verrez que ça marche.
[[File:Full Adder Blocks.svg|centre|vignette|upright=2|Additionneur complet fabriqué avec deux demi-additionneurs.]]
[[File:Half-adder.svg|vignette|upright=1|Circuit d'un demi-additionneur naïf.]]
L'implémentation naïve d'un demi-additionneur utilise une porte XOR et une porte ET, comme illustré ci-contre. Le circuit s'obtient en faisant uen traduction littérale de la table de vérité du circuit. Si je dis qu'elle est naïve, c'est car il est possible de l'optimiser, de manière à éliminer des redondances cachées. Mais nous avions déjà vu cela dans le chapitre sur les incrémenteurs, aussi laissons cela pour plus tard.
En combinant deux demi-additionneurs de ce type, on obtient l'additionneur complet suivant. Les deux sorties de retenue des demi-additionneurs sont combinées avec une porte OU, pour calculer la retenue finale.
{| class="flexible"
|[[File:Full Adder Modules.svg|class=transparent|Composition d'un additionneur complet. On voit bien que celui-ci est composé de deux demi-additionneurs, en rouge et en bleu, auxquels on a ajouté une porte OU pour calculer la retenue finale.]]
|[[File:Full-adder.svg|300px|class=transparent|Circuit d'un additionneur complet.]]
|}
Le fait de combiner les deux retenues avec une porte OU n'est pas ce qu'il y a de plus intuitif. La table de vérité nous dit que ça fonctionne, mais on comprend mal pourquoi. Mais tout devient plus clair quand on sait que les deux retenues ne peuvent pas être à 1 en même temps. Même dans l'addition 1 + 1 + 1, seule une retenue est à 1. Soit l'addition des deux bits d'opérande donne naissance à une retenue, soit c'est l'addition ''résultat + retenue entrante''. La retenue sortant est donc à 1 quand une des deux addition donne une retenue. D'où l'usage d'une porte OU. Remarquez qu'une porte XOR donne le même résultat, vu que le cas où les deux retenues sont à 1 n'est jamais rencontré.
===La propagation et la génération des retenues===
L'additionneur complet que nous allons voir dans cette section sert à introduire les concepts de propagation, génération et absorption de retenue. Il s'agit de concepts très importants quand on étudie les additionneurs. l'idée part d'un principe très simple : la retenue sortante dépend de la retenue d'entrée. La relation entre les deux se résume à trois cas, qui dépendent de la valeur des deux bits additionnés, nommés A et B.
* Dans le premier cas, la retenue entrante est égale à la retenue sortante. On dit que la retenue entrante est propagée par l'additionneur.
* Dans le second cas, la retenue est forcée à 0 : la retenue sortante vaut 0, peu importe la valeur de la retenue entrante.
* Dans le troisième cas, la retenue est forcée à 1 : la retenue entrante vaut 1, peu importe la valeur de la retenue entrante.
[[File:Signaux P et G fournis par un demi-additionneur.png|vignette|Signaux P et G fournis par un demi-additionneur]]
Maintenant, créons un circuit qui nous dise si une retenue est propagée ou générée. Il a deux bits de sortie, nommés P et G : P pour ''Propagate'', G pour ''Generate''. Le bit P est à 1 si la retenue entrante est propagée, il est à 0 sinon. Le bit G est à 1 si une retenue est générée, à 0 sinon. Une retenue est considérée comme absorbée si elle n'est pas ni propagée ni générée, pas besoin d'un troisième bit pour gérer ce cas.
Il se trouve que ce circuit n'est autre qu'un demi-additionneur ! Pour vous en rendre compte, regardez la table de vérité d'un additionneur complet, illustrée ci-dessous. Lorsque les deux bits d'opérande sont à 0, la retenue sortante vaut toujours 0. Si ils sont tous deux à 1, alors la retenue sortante vaut 1. S'ils sont différents, alors retenues sortante et entrante sont égales. Le bit P est donc généré par une simple porte XOR. Quant au bit G, il est à 1 si les deux bits d'opérandes sont à 1, ce qui correspond à une porte ET. Il se trouve que ces deux portes forment un demi-additionneur !
{|class="wikitable"
|-
! Opérande 1 !! Opérande 2 !! Retenue entrante !! !! Retenue sortante
|- class="f_rouge"
||0||0||0|| ||0
|- class="f_rouge"
||0||0||1|| ||0
|- class="f_bleu"
||0||1||0|| ||0
|- class="f_bleu"
||0||1||1|| ||1
|- class="f_bleu"
||1||0||0|| ||0
|- class="f_bleu"
||1||0||1|| ||1
|- class="f_vert"
||1||1||0|| ||1
|- class="f_vert"
||1||1||1|| ||1
|}
Ensuite, créons un circuit qui prend ces deux sorties P et G, et calcule la retenue sortante en fonction. Le circuit en question a trois entrées : la retenue entrante, les deux bits P et G. La retenue sortante vaut 1 soit si une retenue est générée, soit si la retenue entrante est propagée et qu'elle vaut 1. En notant <math>{C_{in}}</math> et <math>{C_{out}}</math> les retenues entrantes et sortante, on a :
: <math>{C_{out}} = G + \left( P . C_{in} \right)</math>
Le circuit est donc composé d'une porte OU et d'une porte ET. En combinant un demi-additionneur avec le circuit de calcul de retenue sortante vu plus haut, on a :
[[File:Additionneur complet avec propagation et génération de retenue.png|class=transparent|centre|vignette|upright=2|Additionneur complet avec propagation et génération de retenue.]]
Certes, on retombe sur le circuit vu plus haut. Mais il est possible de remplacer le circuit en orange par un autre. Par exemple, il est possible de le remplacer par multiplexeur, qui choisit entre la retenue générée et la retenue entrante (propagée). Le choix se fait selon la valeur du bit P, qui chosiit entre propager la retenue et la générer.
[[File:Additionneur crée avec un multiplexeur.png|centre|vignette|upright=1.5|Additionneur crée avec un multiplexeur]]
Le circuit exact est illustré ci-dessous. Il semble utiliser plus de portes logiques que l'additionneur complet précédent. Cependant, nous verrons dans quelques chapitres qu'il est possible d'implémenter un multiplexeur avec seulement 6 transistors, voire moins ! L'implémentation utilise des portes à transmission, mais nous en reparlerons dans le chapitre sur les transistors, quand nous verrons les additionneurs à ''Manchester Carry Chain''. Au passage, une variante de ce circuit a été utilisée dans le processeur processeur 8086 d'Intel, comme on le verra dans le chapitre suivant.
[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
[[File:FulladderPG.png|vignette|upright=1|Additionneur complet avec deux sorties P et G.]]
Pour finir, sachez qu'il existe des additionneurs qui fournissent : le bit de résultat, deux sorties P et G qui indiquent si l'addition propage ou génère une retenue. Il ne s'agit pas d'additionneurs complets, car il manque de quoi calculer la retenue sortante. De tels additionneurs seront appelés des '''additionneurs partiels'''. De tels additionneurs sont utilisés dans certains additionneurs pour gagner en performance.
Enfin, il existe aussi ce que j'ai décidé d'appeler des '''additionneurs P/G''', qui sont des additionneurs complets auxquels on a ajouté deux sorties P et G, en plus de la sortie de retenue. Leur circuit est le même que celui d'un additionneur complet, auquel on a ajouté deux fils. Vu que ces deux sorties sont fournies par le premier demi-additionneur, ajouter ces deux sorties demande d'ajouter des fils, pas de portes logiques. De tels additionneurs sont aussi utilisés dans certains additionneurs pour gagner en performance.
===L'additionneur complet basé sur une porte à majorité===
Maintenant, voyons une dernière implémentation possible de l'additionneur complet. Mais avant de voir comment l'additionneur est implémenté, nous allons devoir faire un petit focus sur la retenue sortante. Reprenons la table de vérité, et regardons ce qu'il en est pour la retenue sortante uniquement.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante
|-
||0||0||0|| ||0
|-
||0||0||1|| ||0
|-
||0||1||0|| ||0
|-
||0||1||1|| ||1
|-
||1||0||0|| ||0
|-
||1||0||1|| ||1
|-
||1||1||0|| ||1
|-
||1||1||1|| ||1
|}
Vous remarquerez que la retenue sortante est égale au bit majoritaire, parmi les trois bits d'opérande. Si deux ou trois bits sont à 1, la retenue sortante vaut 1. Et réciproquement avec 0. Le circuit de calcul de la retenue peut donc être remplacé par une '''porte à majorité'''.
[[File:Additionneur crée avec une porte à majorité.png|centre|vignette|upright=2|Additionneur crée avec une porte à majorité]]
Mais une porte à majorité est assez gourmande en circuit, ce qui fait qu'un additionneur pareil ne serait pas pratique, ni utile. Du moins, ce serait le cas s'il n'y avait pas une possibilité d'optimisation extrêmement intéressante. Il est possible de calculer le 'bit de somme' à partir de la retenue sortante ! En effet, le bit de somme est l'inverse de la retenue sortante, sauf dans deux cas : les trois bits d'entrée sont à 0, où ils sont tous à 1. Le bit de somme vaut 0 dans le premier cas, 1 dans le second cas.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|- class="f_bleu"
||0||0||0|| ||0||0
|- class="f_vert"
||0||0||1|| ||0||1
|- class="f_vert"
||0||1||0|| ||0||1
|- class="f_vert"
||0||1||1|| ||1||0
|- class="f_vert"
||1||0||0|| ||0||1
|- class="f_vert"
||1||0||1|| ||1||0
|- class="f_vert"
||1||1||0|| ||1||0
|- class="f_rouge"
||1||1||1|| ||1||1
|}
En clair, 6 lignes de la table de vérité sur 8 peuvent se calculer avec une porte à majorité, le reste demandant quelques portes logiques pour faire la correction. L'implémentation en circuit demande donc trois choses :
* de calculer la retenue sortante et de quoi l'inverser ;
* un circuit qui vérifie si tous les bits opérande valent 0 : une porte NOR ou OU fait l'affaire ;
* un circuit qui vérifie s'ils valent tous 1, à savoir une porte ET.
Ensuite, on combine le résultat des trois circuits précédents pour obtenir le résultat final. La combinaison est le fait du circuit en jaune dans le schéma ci-dessous.
[[File:Full adder basé sur une modification de la retenue.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue]]
Le circuit en jaune est assez simple. Quand la porte ET sort un 1, la sortie doit être forcée à 1. Par contre, quand la porte OU sort un zéro, la sortie doit être forcée à 0. Il suffit donc d'enchainer un circuit de mise à 1 et un circuit de mise à 0. Nous avons vu dans le chapitre sur les opérations de masquage qu'il s'agit respectivement d'une porte OU et d'une porte ET.
[[File:Full adder basé sur une modification de la retenue interne, au complet.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue interne, au complet]]
Divers processeurs utilisaient des additionneurs complets de ce type : le fameux Z80, l'Intel 4004 et de l'Intel 8008 sont les exemples les plus notables. Pourtant, on pourrait s'étonner qu'un tel circuit ait existé. Il utilise beaucoup de portes logiques, a une profondeur logique supérieure : il n'a rien d'avantageux. Sauf qu'il était utilisé sur d'anciens processeurs, qui utilisaient la technologie dite TTL, différente de la technologie CMOS des transistors modernes. Et avec la technologie TTL, il est possible de fusionner plusieurs portes logiques ET et NOR en une seule porte logique ET/OU/NON ! Un additionneur complet construit ainsi ne prenait que deux portes logiques : une pour le calcul de la retenue sortante, une autre pour le reste du circuit.
==L'addition non signée==
Voyons maintenant un circuit capable d'additionner deux nombres entiers: l''''additionneur'''. Dans la version qu'on va voir, ce circuit manipulera des nombres strictement positifs. L'addition des nombres codés en complètement à deux sera vu dans une section ultérieure.
===L'additionneur série===
Il est possible d'additionner deux nombres bit par bit, grâce à un additionneur complet associé à plusieurs registres à décalages. Les opérandes sont placées chacune dans un registre à décalage, et l'additionneur complet utilise les bits sortants de ces registres à décalage. Le bit du résultat est envoyé au registre à décalage pour le résultat. La retenue de l'addition est stockée dans une bascule de 1 bit, en attente du prochain cycle d'horloge. Un tel additionneur est appelé un '''additionneur série'''. Il a été utilisé sur d'anciens ordinateurs dans les années 50-60, aussi bien des prototypes que des ordinateurs commerciaux.
[[File:Additionneur série.jpg|centre|vignette|upright=2.5|Additionneur série.]]
===L'additionneur à propagation de retenue===
L''''additionneur à propagation de retenue''' est aussi appelé l'additionneur ''ripple carry'', c'est son nom anglais. J'utiliserais occasionnellement le nom anglais dans ce qui suit, car celui-ci est plus court. Ilutilise un additionneur complet pour chaque colonne de l'addition. Pour gérer les retenues, il suffit ainsi de câbler des additionneurs complets les uns à la suite des autres, chacun envoyant sa retenue à la colonne suivante.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Notons la présence de la retenue sortante, qui est utilisée pour détecter les débordements d'entier, ainsi que pour d'autres opérations. Le bit de retenue final est souvent stocké dans un registre spécial du processeur (généralement appelé ''carry flag'').
Notez aussi, à droite du schéma précédent, l'entrée de retenue <math>\text{R}_\text{Entrée}</math>. Elle est très utile pour l'implémentation de certaines opérations comme l'inversion de signe, la soustraction, l'incrémentation, etc. Certains processeurs sont capables de faire une opération appelée ADC, ADDC ou autre nom signifiant ''Addition with Carry'', qui permet de faire le calcul A + B + Retenue (la retenue en question est la retenue sortante de l'addition précédente, stockée dans le registre ''carry flag''). Son utilité principale est de permettre des additions d'entiers plus grands que ceux supportés par le processeur. Par exemple, cela permet de faire des additions d'entiers 32 bits sur un processeur 16 bits.
[[File:Adder Network 3 Bit Sum Delay 003.svg|vignette|upright=0.5|Propagation de retenue dans l'additionneur.]]
Cet additionneur utilise très peu de portes logiques, ce qui fait qu'il était utilisé sur certains processeurs 8 et 16 bits, dont le budget en portes logiques était limité. Bien que très simple, cet additionneur est cependant peu performant. Son temps de calcul est proportionnel à la taille des opérandes. Par exemple, additionner deux opérandes de 32 bits prendra deux fois plus de temps que pour des opérandes de 16 bits. La raison est que les retenues doivent se propager à travers le circuit, du premier additionneur jusqu'au dernier.
L'addition étant une opération fréquente, d'autres additionneurs ont été inventés. Ils utilisent des optimisations qui utilisent plus de circuits pour gagner en rapidité. Mais avant de voir ces optimisations, nous allons voir s'il est possible d'optimiser les additionneurs à propagation de retenue.
===L'additionneur à saut de retenue===
L'additionneur à propagation de retenue peut être optimisé de deux manières. La première accélère la propagation de la retenue, en agissant au niveau des additionneurs complets. La ''Manchester carry chain'' est une optimisation de ce type, mais on ne peut pas encore expliquer à ce stade du cours, car elle optimisait les additionneurs complets au niveau des transistors eux-mêmes. Elle a été utilisée sur de nombreux processeurs connus, comme le 8086 d'Intel ou le Z80.
La seconde manière rend l'addition plus rapide dans certains cas. Le résultat est un additionneur dont le temps de calcul est variable. Le calcul prendra quelques cycles d'horloges avec certains opérandes, mais d'autres opérandes ne verront aucune amélioration. Il n'améliore pas le pire des cas, dans lequel la retenue doit être propagée du début à la fin, du bit de poids faible au bit de poids fort. Mais dans d'autres cas, il permet d'avoir le résultat en avance. L'optimisation en question donne l''''additionneur à saut de retenue''' (''carry-skip adder'').
Il est composé de blocs qui additionnent 4/5 bits, rarement plus. La taille d'un bloc varie d'un additionneur à l'autre, elle peut même varier dans l'additionneur, mais nous allons prendre un exemple où les blocs font 4 bits. L'idée est de détecter précocement si la retenue entrante est propagée à travers tout le bloc. Si la retenue est propagée dans tous le bloc, on peut directement l'envoyer sur la sortie de retenue, la retenue saute le bloc entier. Dans le cas contraire, on doit calculer la retenue normalement. Le choix entre les deux est le fait d'un multiplexeur. Toute la difficulté est de savoir comment commander le multiplexeur.
[[File:Carry skip adder, principe de base.png|centre|vignette|upright=2.5|Carry skip adder : principe de base]]
Pour savoir si une retenue est propagée dans un bloc entier, il faut utiliser des additionneurs complets, qui ont en plus une sortie P indiquant que l'additionneur propage la retenue. Le bloc propage une retenue si chaque additionneur complet propage la retenue, donc s'ils ont tous leur sortie P à 1. Un simple ET logique suffit à déterminer si c'est le cas. Le signal de commande du multiplexeur est donc un vulgaire ET entre toutes les sorties P des additionneurs complets du bloc.
[[File:Additionneur à saut de retenue.png|centre|vignette|upright=2.5|Calcul de la commande du MUX.]]
Voici ce que ça donne pour un bloc de 4 bits :
[[File:CSAdder4Bit.svg|centre|vignette|upright=1.5|Additionneur ''carry skip'' : bloc de 4 bits.]]
L'additionneur à saut de retenue est construit en assemblant plusieurs blocs de ce type.
[[File:BCSAdder16Bit.svg|centre|vignette|upright=3|Additionneur à saut de retenue.]]
===L'additionneur à sélection de retenue===
L''''additionneur à sélection de retenue''' utilise aussi des blocs, comme les additionneurs précédents. L'addition se fait en deux versions : une avec la retenue du bloc précédent valant zéro, et une autre version avec la retenue du bloc précédent valant 1. Il suffira alors de choisir le bon résultat avec un multiplexeur, une fois cette retenue connue. On gagne ainsi du temps en calculant à l'avance les valeurs de certains bits du résultat, sans connaître la valeur de la retenue. Petit détail : sur certains additionneurs à sélection de retenue, les blocs de base n'ont pas la même taille. Cela permet de tenir compte des temps de propagation des retenues entre les blocs.
[[File:Additionneur à sélection de retenue.png|centre|vignette|upright=1.5|Additionneur à sélection de retenue avec seulement deux blocs.]]
Dans les exemples du dessus, chaque sous-additionneur étaient des additionneurs à propagation de retenue. Mais ce n'est pas une obligation, et tout autre type d’additionneur peut être utilisé. Par exemple, on peut faire en sorte que les sous-additionneurs soient eux-mêmes des additionneurs à sélection de retenue, et poursuivre ainsi de suite, récursivement. On obtient alors un '''additionneur à somme conditionnelle''', plus rapide que l'additionneur à sélection de retenue, mais qui utilise beaucoup plus de portes logiques.
===Les additionneurs à anticipation de retenue===
Les '''additionneurs à anticipation de retenue''' calculent chaque retenue sans avoir à propager les retenues précédentes. Au lieu de calculer les retenues une par une, ils calculent toutes les retenues en parallèle, à partir des bits des opérandes. Une fois les retenues pré-calculées, il suffit de les additionner avec les deux bits adéquats, pour obtenir le résultat. Ces additionneurs sont composés de deux parties :
* un circuit qui pré-calcule les retenues, sans les propager, directement à partir des opérandes ;
* d'une couche d'additionneurs complets, qui additionnent chacun deux bits d'opérande et la retenue pré-calculée.
[[File:Additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Additionneur à anticipation de retenue.]]
Le calcul des retenues se fait en deux étapes. La première détermine si les retenues sont propagées ou générées, pour chaque colonne. Elle détermine, pour chaque colonne, les bits P et G qui indiquent respectivement : qu'une retenue a été générée sur cette colonne, que la colonne propage la retenue précédente. La seconde étape utilise ces signaux P et G pour déterminer la retenue sur chaque colonne. Le circuit de calcul des retenues est donc composé de deux parties : une couche de demi-additionneurs pour générer les signaux P et G, un circuit de calcul des retenues proprement dit.
[[File:Circuit complet d'un additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Circuit complet d'un additionneur à anticipation de retenue.]]
Pour rappel, la retenue de la colonne i, notée <math>C_i</math> est égale à :
: <math>G_i + (P_i . C_{i-1})</math>, avec G le signal de génération de retenue, P le signal de propagation de retenue.
Si on utilisait cette formule sans trop réfléchir, on retomberait sur un additionneur à propagation de retenue inutilement compliqué. L'astuce des additionneurs à anticipation de retenue consiste à remplacer le terme Ci−1 par sa valeur calculée avant. Par exemple, je prends un additionneur 4 bits. Je dispose de deux nombres A et B, contenant chacun 4 bits : A3, A2, A1, et A0 pour le nombre A, et B3, B2, B1, et B0 pour le nombre B. Si j'effectue les remplacements, j'obtiens les formules suivantes :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Ces formules nous permettent de déduire la valeur d'une retenue directement : il reste alors à créer un circuit qui implémente ces formules, et le tour est joué.
[[File:Four bit adder with carry lookahead.svg|centre|vignette|upright=1.5|Additionneur à anticipation de retenue de 4 bits.]]
Plus haut, j'ai dit que le circuit était composé de trois couches : une couche de demi-additionneur pour les signaux P et G ; un circuit d'anticipation de retenue, puis l'addition finale. Mais les deux bits d'opérandes sont déjà additionnés dans la couche de demi-additionneur, autant réutiliser le résultat pour l'additionner à la retenue. Pour éliminer toute redondance de ce type, il est possible d'utiliser des additionneurs partiels (des additionneurs où la sortie de retenue a été remplacée par deux sorties P et G). L'additionneur final est alors composé de plusieurs additionneurs complets de ce type, couplé à un circuit d'anticipation de retenue.
[[File:Cla4bits.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue de 4 bits.]]
Les additionneurs à anticipation de retenue sont plus rapides que les additionneurs à propagation de retenue. La raison est que les retenues sont calculées rapidement, dans un temps plus petit qu'en propageant la retenue. Pour rentrer dans les détails mathématiques, le temps de calcul n'est pas proportionnel au nombre de bits, mais proportionnel au logarithme de ce nombre de bits. Par contre, le gain en temps de calcul s'accompagne d'un cout en portes logiques conséquent. Le circuit qui calcule les retenues anticipées utilise beaucoup de portes logiques.
===Les additionneurs à calcul parallèle de préfixe===
Du fait du cout en portes logiques, utiliser un additionneur à anticipation de retenue est une solution pratique pour additionner 4 ou 8 bits. Mais pour des opérandes plus grands, de 16 ou 32 bits, cela utiliserait trop de portes logiques. Une partie de ce cout en portes logiques est cependant lié à des redondances. Si on analyse deux retenues différentes, à des colonnes différentes. Les circuits qui calculent ces deux retenues seront différents, mais ils contiendront des sous-circuits identiques. Pour vous en rendre compte, reprenons les quatre équations obtenues précédemment et essayez de trouver des redondances :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Vous devriez trouver :
* le terme ''P0 · C0'' dans toutes les lignes ;
* les termes ''P1 · P0 · C0'' et ''P1 · P0'' dans les trois dernières lignes ;
* le terme ''P2 · P1 · P0 · C0'' ; ''P2 · P1 · G0'' et ''P2 · G1'' dans les deux dernières lignes.
Il existe plusieurs manières d'exploiter de telles redondances, exploitées dans les '''additionneurs à calcul parallèle de préfixe'''. L'optimisation apportée est de générer des signaux ''propagate'' et ''generate'' pour des groupes de 2, 3, 4, ..., N colonnes. Par exemple, il est possible de générer un signal ''P 0 vers 3'', qui indique si la retenue de la première colonne est propagée jusqu'à la 4ème colonne. Un autre exemple est un signal qui indique qu'une retenue a été générée entre les colonnes 4 à 7 génèrent une retenue ou non. En clair, les signaux P et G ont maintenant un intervalle, qui précise de quelle colonne vers quelle colonne se fait la propagation, ou entre quelles colonnes se fait la génération.
Il est possible de combiner les signaux P et G de deux groupes de colonne, s'ils sont contiguës (ils peuvent aussi se recouvrir). Par exemple, il est possible de calculer les bits P et G pour les colonnes 0 à 10, à partir des deux signaux P/G des colonnes 0-4 et 5-10. Pour cela, les équations sont assez simples. Si je prends deux groupes nommés A et B, avec A pour les colonnes de poids fort et B celles de poids faible, on a :
: <math>P_{sortie} = P_A . P_B</math>
: <math>G_{sortie} = G_A . P_A . G_B</math>
Pour les bits des colonnes 2, 4, 8, 16, 32, 64, et autres, le calcul est simple. L'idée est de grouper les colonnes par groupes de 2. Puis, on calcule chaque groupe de 4 bits à partir de deux groupes de 2. Puis les groupes de 8 bits sont calculés à partir de deux groupes de 4 bits, et ainsi de suite pour toute puissance de deux.
[[File:Squelette d'un additionneur à calcul parallèle de préfixe.jpg|centre|vignette|upright=2|Squelette d'un additionneur à calcul parallèle de préfixe]]
Les seules difficultés surviennent pour les colonnes dont le nombre n'est pas une puissance de deux. Il y a plusieurs manières pour subdiviser ces colonnes en intervalles plus petits et combiner le tout. Et elles donnent chacune des additionneurs différents. Ils portent les noms d'''additionneur de Ladner-Fisher'', d'''additionneur de Brent-Kung'', d'''additionneur de Kogge-Stone'', etc. Dans le cas général, l'additionneur de Kogge-Stone est un des plus rapide, mais aussi un des plus gourmand en circuits, alors que c'est l'inverse pour l'additionneur de Brent-Kung. Les deux additionneurs sont illustrés ci-dessous, pour des opérandes de 8 bits. Dans ceux-ci, chaque losange calculent les bits P et G, à partir des signaux P et G de deux groupes précédents.
{|
|[[File:Brent-kung-8-bit.png|vignette|Brent-kung-8-bit]]
|[[File:Kogge-stone-8-bit.png|vignette|upright=1.2|Kogge-stone-8-bit]]
|}
===Le calcul parallèle de la retenue===
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
L'anticipation de retenue est très rapide, mais utilise beaucoup de circuits, même en utilisant le calcul parallèle de préfixe. Les additionneurs à propagation de retenue font eux le compromis inverse. Mais il existe des intermédiaires, qui visent à obtenir un compromis entre performance et cout en portes logiques.
Un de ces compromis découpe un additionneur de 16/32/64 bits en additionneurs de 4/5 bits, qui sont enchainés en utilisant la propagation de retenue. Par contre, les petits additionneurs utilisent l'anticipation de retenue à l'intérieur. Ces additionneurs simples seront nommés ''blocs'' dans ce qui suit, et l'un d'entre eux est illustré ci-contre. Vous remarquerez qu'un bloc a une retenue entrante et une retenue sortante. Par exemple, on peut utiliser 4 additionneurs à anticipation de retenue de 4 bits, et propager les retenues entre eux. Le premier bloc calcule une retenue, qui est propagée au second bloc. Le second bloc calcule lui aussi une retenue, qui est propagée au troisième bloc, etc.
[[File:4008 Functional Diagram.svg|vignette|upright=1|4008 Functional Diagram]]
Le gain en performance est significatif, mais il est possible de faire presque aussi rapide, tout en économisant beaucoup de circuits. L'idée est de simplifier les additionneurs 4 bits, afin de ne pas utiliser une anticipation de retenue complète. L'anticipation de retenue n'est utilisée que pour la retenue sortante, alors que les bits de somme sont calculées avec propagation de retenue. La retenue sortante est donc calculée en parallèle de l'addition. Chaque bloc contient, à côté d'un additionneur proprement dit, un circuit qui calcule la retenue sortante. Il existe de nombreuses manières de calculer la retenue sortante.
Le gain en portes logiques est assez significatif. En effet, on se passe de circuits d'anticipation de retenue pour les 4 bits de somme, on ne garde que celui pour la retenue sortante. Le fait d'utiliser la propagation de retenue pour calculer les 4 bits de somme est censé avoir un léger cout en performance, mais il se trouve que ce cout est très faible. En effet, le résultat n'est connu qu'une fois les retenues propagées jusqu'au dernier bloc. Et malgré l'anticipation des retenues, la propagation est assez lente. les additionneurs à propagation de retenue auront terminé leur travail bien avant.
Un exemple est celui de l'additionneur CMOS 4008, un additionneur de 4 bit. Il est composé en trois sections. Une première couche de demi-additionneurs calcule les signaux P et G utilisés à la fois pour l'anticipation de la retenue sortante, que pour calculer les bits de somme. Le circuit de calcul de la retenue sortante utilise l'anticipation de retenue. Le reste du circuit fait l'addition en propageant les retenues.
[[File:CMOS 4008, circuit découpé en sections.png|centre|vignette|upright=1.5|CMOS 4008, circuit découpé en sections]]
===Les additionneurs à anticipation de retenue multi-niveau===
Dans cette section, nous allons voir des additionneurs conçus en assemblant des additionneurs plus simples, qui additionnent environ 4 à 5 bits, parfois plus, parfois moins.
Les blocs sont tous identiques dans le cas le plus simple, mais il est possible d'utiliser des blocs de taille variable. Par exemple, le premier bloc peut avoir des opérandes de 6 bits, le second des opérandes de 7 bits, etc. Faire ainsi permet de gagner un petit peu en performances, si la taille de chaque bloc est bien choisie. La raison est une question de temps de propagation des retenues. La retenue met plus de temps à se propager à travers 8 blocs qu'à travers 4, ce qui prend plus de temps qu'à travers 2 blocs, etc. En tenir compte fait que la taille des blocs tend à augmenter ou diminuer quand on se rapproche des bits de poids fort.
Il est possible d'utiliser l'anticipation de retenue entre deux blocs. Par exemple, il est possible de créer un additionneur 16 bits en utilisant 4 additionneurs/blocs de 4 bits. L'additionneur utilisé peut être un additionneur à propagation de retenue, ou à anticipation de retenue, peu importe.
Pour cela, les additionneurs de 4 bits doivent fournir deux sorties P et G. Les deux sorties indiquent que le bloc soit a généré une retenue, soit a propagé la retenue entrante. Par propagé, on veut dire que la retenue entrante du bloc s'est retrouvée sur la sortie de retenue du bloc. C'est le cas si les 4 colonnes propagent la retenue, ce qui signifie que :
: <math>P_\text{4 bits} = P_0 \cdot P_1 \cdot P_2 \cdot P_3</math>
Pour la génération, cela veut dire qu'une colonne a générée une retenue, qui a été propagée jusqu'à la sortie. En clair, pour chaque colonne, il faut que le G = 1, et que les signaux P des colonnes soient à 1. Pour les 4 colonnes, cela donne :
: <math>G_\text{4 bits} = G_3 + G_2 \cdot P_3 + G_1 \cdot P_3 \cdot P_2 + G_0 \cdot P_3 \cdot P_2 \cdot P_1</math>
[[File:Cla4bitsPG.png|centre|vignette|upright=2|Additionneur à anticipation de retenue de 4 bits.]]
Pour combiner 4 blocs de 4 bits, on devra donc combiner 4 bits P et G, avec un circuit d'anticipation de retenue.
[[File:16-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 16 bits.]]
Il est même possible d'aller plus loin et de combiner 4 additionneurs précédents pour obtenir un additionneur 64 bits.
[[File:64-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 64 bits.]]
Le résultat est un additionneur à plusieurs niveaux d’anticipation de retenue. Une anticipation de retenue dans un blocs de 4 bits (facultative), entre blocs de 4 bits, entre blocs de 16 bits. La même logique peut être utilisée avec des blocs de taille différente de 4, 16 et 64 bits.
==L'addition signée et la soustraction==
Après avoir vu l'addition, il est logique de passer à la soustraction, les deux opérations étant très proches. Si on sait câbler une addition entre entiers positifs, câbler une soustraction n'est pas très compliqué. De plus, la soustraction permet de faire des additions de nombres signés.
===Le soustracteur pour opérandes entiers===
Pour soustraire deux nombres entiers, on peut adapter l'algorithme de soustraction utilisé en décimal, celui que vous avez appris à l'école. Celui-ci ressemble fortement à l'algorithme d'addition : on soustrait les bits de même poids, et on propage éventuellement une retenue sur la colonne suivante. À la différence de l'addition, la retenue est soustraite, et non ajoutée. La table de soustraction nous dit quel est le résultat de la soustraction de deux bits. La voici :
* 0 - 0 = 0 ;
* 0 - 1 = 1 et une retenue ;
* 1 - 0 = 1 ;
* 1 - 1 = 0.
[[File:Algebra1 05 fig014.svg|centre|vignette|Soustraction en binaire, avec les retenues en rouge.]]
La table de soustraction peut servir de table de vérité pour construire un circuit qui soustrait deux bits. Celui-ci est appelé un '''demi-soustracteur'''. Il ressemble beaucoup à un demi-additionneur, les différences se résumant à une porte NON ajoutée pour le calcul de la retenue.
[[File:Half Subtractor Vektor.svg|centre|vignette|Demi-soustracteur.]]
Comme pour l'additionneur, deux demi-soustracteurs peuvent être combinés pour donner un '''soustracteur complet'''. Le calcul de la retenue se fait en combinant les deux retenues des demi-soustracteurs avec une porte OU. Les soustracteurs complets sont utilisés pour créer des soustracteurs à propagation de retenue ou tout autre circuit soustracteur, sur le même modèle que les additionneurs.
[[File:FullSubtractor.svg|centre|vignette|upright=2|Soustracteur complet.]]
Vous l'aurez sans doute remarqué, mais le demi-soustracteur ressemble beaucoup au demi-additionneur. La seule différence est une porte NON ajoutée au bon endroit dans le demi-soustracteur.
{|
|[[File:Half-adder.svg|vignette|Demi-additionneur]]
|[[File:Half Subtractor Vektor.svg|vignette|upright=1.3|Demi-soustracteur]]
|}
La conséquence est qu'il est possible de créer un circuit capable de faire à la fois des additions et des soustractions. L'idée est de remplacer la porte NON du demi-soustracteur par un inverseur commandable. Si on envoie un zéro sur l'entrée de commande, l'inverseur commandable se comportera comme une porte OUI. Le circuit sera alors identique à un demi-additionneur. Si on envoie un 1 sur l'entrée de commande, l'inverseur commandable se comportera comme une porte NON. le circuit sera alors un demi-soustracteur. Il faut faire cela pour chaque demi-soustracteur, ce qui donne ceci pour un soustracteur complet. le résultat est alors appelé un '''additionneur-soustracteur complet'''.
[[File:Additionneur-soustracteur complet.png|centre|vignette|upright=2|Additionneur-soustracteur complet]]
===L'additionneur-soustracteur pour opérandes codées en complément à deux===
Étudions le cas de la soustraction en complément à deux, dans l'objectif de créer un circuit soustracteur. Vous savez sûrement que a−b et a+(−b) sont deux expressions équivalentes. Et en complément à deux, − b = not(b) + 1. Dit autrement, a − b = a + not(b) + 1. On pourrait se dire qu'il faut deux additionneurs pour faire le calcul, mais la majorité des additionneurs possède une entrée de retenue pour incrémenter le résultat de l'addition. Un soustracteur en complément à deux est donc simplement composé d'un additionneur et d'un inverseur.
[[File:Additionneur soustracteur.png|centre|vignette|upright=2|Soustracteur en complément à deux.]]
Il est possible de créer un circuit capable d'effectuer soit une addition, soit une soustraction : il suffit de remplacer l'inverseur par un inverseur commandable, qui peut être désactivé. On a vu comment créer un tel inverseur commandable dans le chapitre sur les circuits combinatoires. On peut remarquer que l'entrée de retenue et l'entrée de commande de l'inverseur sont activées en même temps : on peut fusionner les deux signaux en un seul.
[[File:Additionneur-soustracteur en complément à deux.png|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux.]]
Une implémentation alternative est la suivante. Elle remplace l'inverseur commandable par un multiplexeur.
[[File:4-bit ripple carry adder-subtracter.svg|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux, version alternative.]]
===L'additionneur-soustracteur pour opérandes codées en signe-magnitude===
Passons maintenant aux nombres codés en signe-valeur absolue, les deux opérandes étant notées A et B. Suivant les signes des deux opérandes, on a quatre cas possibles : A + B, A − B (B négatif), −A + B (A négatif) et −A − B (A et B négatifs). Une astuce est que le circuit n'a besoin que de calculer A + B et A − B : il peut les inverser pour obtenir − A − B ou B − A. A + B et A − B peuvent se calculer avec un additionneur-soustracteur, reste à corriger le résultat. Il suffit de lui ajouter un inverseur commandable pour obtenir le circuit d'addition finale.
[[File:Additionneur en signe-valeur absolue.png|centre|vignette|upright=2|Additionneur en signe-valeur absolue.]]
Toute la difficulté tient dans le calcul du bit de signe du résultat, quand interviennent des soustractions. Autant l'addition de deux nombres de même signe (A + B et −A − B) ne pose aucun problème, autant les soustractions posent problème (A − B et −A + B). Suivant que <math>A<B</math> ou que <math>A>B</math>, le signe du résultat ne sera pas le même. Déterminer le signe du résultat se fait en regardant les bits de débordement d'entier, comme on le verra plus bas.
===L'additionneur-soustracteur pour opérandes codées en représentation par excès===
Passons maintenant aux nombres codés en représentation par excès. On pourrait croire que ces nombres s'additionnent comme des nombres non-signés, mais ce serait oublier la présence du biais, qui pose problème. Dans les cas de nombres signés gérés avec un biais, voyons ce que donne l'addition de deux nombres :
: <math>( n_1 + biais ) + ( n_2 + biais ) = n_1 + n_2 + 2 \times biais</math>
Or, le résultat correct serait :
: <math>n_1 + n_2 + biais</math>
En effectuant l'addition telle quelle, le biais est compté deux fois. On doit donc le soustraire après l'addition pour obtenir le résultat correct.
Même chose pour la soustraction qui donne ceci :
: <math>(n_1 + biais) - (n_2 + biais) = n_1 - n_2</math>
Or, le résultat correct serait :
: <math> ( n_1 - n_2 ) + biais </math>
Il faut rajouter le biais pour obtenir l'exposant correct.
On a donc besoin de deux additionneurs/soustracteurs : un pour additionner/soustraire les représentations binaires des opérandes, et un autre pour ajouter/retirer le biais en trop/manquant.
==L'additionneur BCD==
Maintenant, voyons un additionneur qui additionne deux entiers au format BCD. Pour cela, nous allons devoir passer par deux étapes. La première est de créer un circuit capable d'additionneur deux chiffres BCD. Ensuite, nous allons voir comment enchaîner ces circuits pour créer un additionneur BCD complet.
===L'additionneur BCD qui fait l'opération chiffre par chiffre===
Nous allons commencer par voir un additionneur qui additionne deux chiffres en BCD, une sorte d'équivalent BCD de l'additionneur complet. Il fournit un résultat sur 4 bits et une retenue qui est mise à 1 si le résultat dépasse 10 (la limite d'un chiffre BCD). Les deux opérandes sont des chiffres BCD codés sur 4 bits et sont additionnés en binaire par un additionneur des plus normaux, similaire à ceux vus plus haut. Le résultat est alors un entier codé en binaire, sur 5 bits, qu'on corrige/convertit pour obtenir un chiffre BCD et une retenue sortante.
Pour corriger le résultat, une idée intuitive serait de prendre le résultat et de faire une division par 10. Le quotient donne la retenue, alors que le reste est le résultat, le chiffre BCD. Mais un circuit diviseur par 10 utilise beaucoup de portes logiques, ce qui ne vaut pas le coup. Une autre méthode détecte si le résultat est égal ou supérieur à 10, ce qui correspond à un "débordement" (on dépasse les limites d'un chiffre BCD). Si le résultat est plus petit que 10, il n'y a rien à faire : le résultat est bon et la retenue est de zéro. Par contre, si le résultat vaut 10 ou plus, il faut corriger le résultat et générer une retenue à 1.
Il faut donc ajouter un circuit qui détecte si le résultat est supérieur à 9, qui calcule directement la retenue. Ce circuit peut se fabriquer simplement à partir de sa table de vérité, ou en utilisant les techniques que nous verrons dans un chapitre ultérieur sur les comparateurs. La solution la plus simple est clairement d'utiliser la table de vérité, ce qui est très simple, assez pour être laissé en exercice au lecteur. Pour comprendre comment corriger le résultat, établissons une table de vérité qui associe le résultat et le résultat corrigé. L'entrée vaut au minimum 10 et au maximum 9 + 9 = 18. On considère la sortie comme un tout, la retenue étant un 5ème bit, le bit de poids fort.
{|class="wikitable"
|-
! colspan="5" | Entrée
!
! rowspan="10" |
! Retenue
! Résultat corrigé (sans retenue)
! interprétation de la sortie en binaire (retenue inclue)
|-
| 0 || 1 || 0 || 1 || 0
| (10)
| 1 || 0000
| (16)
|-
| 0 || 1 || 0 || 1 || 1
| (11)
| 1 || 0001
| (17)
|-
| 0 || 1 || 1 || 0 || 0
| (12)
| 1 || 0010
| (18)
|-
| 0 || 1 || 1 || 0 || 1
| (13)
| 1 || 0011
| (19)
|-
| 0 || 1 || 1 || 1 || 0
| (14)
| 1 || 0100
| (20)
|-
| 0 || 1 || 1 || 1 || 1
| (15)
| 1 || 0101
| (21)
|-
| 1 || 0 || 0 || 0 || 0
| (16)
| 1 || 0110
| (22)
|-
| 1 || 0 || 0 || 0 || 1
| (17)
| 1 || 0111
| (23)
|-
| 1 || 0 || 0 || 1 || 0
| (18)
| 1 || 1000
| (24)
|}
En analysant le tableau, on voit que pour corriger le résultat, il suffit d'ajouter 6. La raison est que le résultat déborde d'un nibble à 16 en binaire, mais à 10 en décimal : il suffit d'ajouter la différence entre les deux, à savoir 6, et le débordement binaire fait son travail. Donc, la correction après une addition est très simple : si le résultat dépasse 9, on ajoute 6.
On peut maintenant implémenter l'additionneur BCD, en combinant le comparateur avec 10, le circuit de correction, et l'additionneur. La première solution calcule deux versions du résultat : la version corrigée, la version normale. Le choix entre les deux est réalisée par un multiplexeur, commandé par le comparateur.
[[File:Additionneur BCD.png|centre|vignette|upright=1.5|Additionneur BCD]]
L'autre solution utilise un circuit commandable qui soit additionne 6, soit ne fait rien. Le choix entre les deux est commandé par le bit calculé par le comparateur.
[[File:Additionneur BCD, seconde version.png|centre|vignette|upright=1.5|Additionneur BCD, seconde version.]]
Une version alternative du circuit précédent est la suivante. Il contient deux additionneurs : un pour additionner les deux chiffres BCD, un autre pour additionner 6 si besoin. Le résultat du comparateur est directement utilisé pour générer l'opérande du second additionneur : 0 ou 6. Le circuit est simple à concevoir, mais gaspille beaucoup de circuit. Idéalement, il vaudrait mieux utiliser un circuit combinatoire d'addition avec une constante.
[[File:Adder 4 Bit BCD.svg|centre|vignette|upright=2|Additionneur BCD, circuit complet.]]
Pour obtenir un additionneur BCD complet, il suffit d’enchaîner les additionneurs précédents, comme on le ferait avec les additionneurs complets dans un additionneur à propagation de retenue. Au final, l'additionneur BCD est beaucoup plus compliqué qu'un additionneur normal, car il rajoute un comparateur ">9", un petit additionneur pour ajouter 6 et éventuellement d'autres circuits. De plus, il est difficile d'appliquer les optimisations disponibles sur les additionneurs non-BCD. Notamment, les circuits d'anticipation de retenue sont totalement à refaire et le résultat est relativement compliqué. C'est ce qui explique pourquoi le BCD a progressivement été abandonné au profit du binaire simple.
La soustraction en BCD se fait comme en binaire : le nombre à soustraire est remplacé par son complément, le circuit additionne le complément et l'autre opérande, le débordement d'entier fait que le résultat marche. Sauf qu'ici, le complément est un complément à 9. Il se calcule chiffre par chiffre : chaque chiffre est remplacé par (9 - le chiffre en question).
===L'additionneur BCD par ajustement décimal===
L'additionneur BCD précédent effectuait son travail chiffre BCD par chiffre BCD, mais il existe des additionneurs BCD qui font autrement. Sur les premiers processeurs x86, il n'y avait pas d'opération d'addition BCD proprement dit, seulement une addition binaire normale de 8, 16 ou 32 bits. Par contre, elle était secondée par une opération dite d''''ajustement décimal''' qui transformait un nombre binaire en nombre codé en BCD. L'opération d'ajustement décimal prenait un opérande de 8 bits codé en binaire et fournissait un résultat de la même taille, c'est à dire deux chiffres BCD. Effectuer une addition BCD demandait donc de faire deux opérations à la suite : une addition binaire simple, suivie par l'opération d'ajustement décimal. Cela permettait de gérer des nombres entiers en binaire usuel et des entiers BCD sans avoir deux instructions d'addition séparées pour les deux, sans compter que cela simplifiait aussi les circuits d'addition.
L'ajustement décimal s'effectue en ajoutant une constante bien précise à l'opérande à convertir en BCD. L'idée est que la constante est découpée en morceaux de 4 bits, correspondant chacun à un chiffre BCD de l'opérande, chaque morceau contenant soit un 0, soit 6. Cela permet d'ajouter soit 0, soit 6, à chaque chiffre BCD, et donc de le corriger. La propagation des retenues d'un chiffre à l'autre est effectuée automatiquement par l'addition binaire de la constante. L'opération d'ajustement décimal calcule automatiquement la constante. Elle découpe l'opérande en ''nibbles'', vérifie si chaque ''nibble'' est supérieur ou égal à 10, puis détermine la valeur de chaque ''nibble'' de la constante finale. Par exemple, si je prends l'opérande 1001 1110, le ''nibble'' de poids faible déborde, alors que celui de poids fort non. La constante sera donc 0000 0110 : 0x06. Inversement, si le ''nibble'' de poids fort déborde et pas celui de poids faible, la constante sera alors 0x60. Et la constante est de 0x66 si les deux ''nibbles'' débordent, de 0x00 si aucun ne déborde.
Le circuit d’ajustement décimal est donc composé de trois étapes : deux étapes pour calculer la constante, et un circuit d'addition pour additionner cette constante au nombre de départ. La première étape découpe l'opérande en morceaux de 4 bits, en chiffres BCD, et vérifie si chacun d'entre eux vaut 10 ou plus. La seconde étape prend les résultats de la première étape, et les combine pour calculer la constante. Enfin, on trouve l'addition finale, qui était réalisée par un circuit d'addition utilisé à la fois pour l'ajustement décimal et l'addition binaire. La différence entre une addition normale et une opération d'ajustement décimal tient dans le fait que les deux premières étapes sont désactivées dans une addition normale.
[[File:Additionneur BCD parallèle.png|centre|vignette|upright=1.5|Additionneur BCD parallèle]]
===L'additionneur biquinaire===
Les entiers BCD ne sont qu'un des encodages hybrides entre décimal et binaire. L'encodage biquinaire est l'un d'entre eux et nous allons faire un rappel rapide à ce sujet. Pour simplifier, un chiffre encodé en biquinaire est composé de deux parties : un bit, couplé à une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits (0, 1, 2, 3 et 4). Le bit indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9.
Additionner deux nombres de biquinaire demande donc d'additionner deux parties quinaires encodées en ''one-hot'' et d'additionner deux bits. Mais attention : il faut tenir compte de la retenue de l'addition des parties quinaires. Et idéalement, il faut aussi tenir compte d'une retenue entrante, provenant de l'addition de la colonne de chiffres précédente. Toute la difficulté vient de la création de l'additionneur ''one-hot''. Heureusement, vu qu'il n'y a que 4-5 bits à additionner, il est souvent fabriqué à partir de sa table de vérité.
[[File:Additionneur bi-quinaire.png|centre|vignette|upright=2|Additionneur bi-quinaire]]
Un avantage du biquinaire est que le calcul du complément à 9 est très simple. Il faut pour cela : inverser la partie binaire avec une porte NON, puis inverser l'ordre des bits de la partie quinaire. Concrètement, le bit de poids faible devient le bit de poids fort, et ainsi de suite. Par exemple, une partie quinaire 01000 devient 00010, 10000 devient 00001, 00100 ne change pas, etc. Le tout peut se calculer avec une porte NON et 5 multiplexeurs.
===L'additionneur BCD avec calculs intermédiaires en biquinaire===
L'ordinateur IBM 1401, un ancien ''mainframe'' des années 60, utilisait un additionneur BCD un peu particulier. Les nombres étaient encodés en BCD dans la mémoire de l'ordinateur, mais les circuits de calcul utilisaient la représentation biquinaire. Lors d'un calcul, le processeur de l'ordinateur traduisait les chiffres BCD en représentation biquinaire, faisait une addition en biquinaire, avant de traduire le résultat en BCD normal.
Pour être précis, l'IBM 1401 utilisait une variante du biquinaire. L'encodage biquinaire de l'IBM 1401 est le suivant : la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre pair encodé par la partie quinaire. Si l'IBM 1401 utilisait cette variante du biquinaire, c'est car elle donnait des circuits de conversion BCD-biquinaire plus économes en portes logiques et plus rapides.
La partie binaire est le bit de poids faible du chiffre BCD, la partie biquinaire est calculée par un simple décodeur qui prend en entrée le chiffre BCD, amputé de son bit de poids faible. La traduction inverse demande d'utiliser un encodeur, à la place du décodeur. Par contre, le circuit d'addition biquinaire était plus compliqué du fait de la gestion des retenues. L'addition des parties binaires et quinaires se faisait en parallèle, dans deux additionneurs séparés. Cependant, l'addition des parties binaire fournit une retenue, qu'il faut prendre en compte. Pour cela, l'IBM 1401 disposait d'un troisième additionneur qui fournissait le résultat final, encodé en biquinaire.
[[File:Additionneur bi-quinaire de l'IBM 1401.png|centre|vignette|upright=2|Additionneur biquinaire de l'IBM 1401]]
Une implémentation moderne demanderait d'utiliser des portes ET combinées à des portes OU, le circuit pouvant être construit simplement à partir de sa table de vérité. Sur l'IBM 1401, le circuit était cependant différent, en raison de l'utilisation de OU câblés, des croisements de fils qui fonctionnent comme des portes OU, que nous n'avons pas encore vu pour le moment, mais qui seront détaillés dans quelques chapitres. Les OU câblés étaient utilisés pour simplifier le design du circuit, mais demandaient des portes logiques spécifiques, ce qui collait avec le fait que ce ''mainframe'' utilisait des transistors en Germanium. L'implémentation exacte est décrite dans cet article de blog, mais je ne recommande sa lecture qu'à ceux qui savent ce qu'est un OU câblé :
* [https://www.righto.com/2015/10/qui-binary-arithmetic-how-1960s-ibm.html Qui-binary arithmetic: how a 1960s IBM mainframe does math].
==Les débordements d'entier lors d'une addition/soustraction==
Les instructions arithmétiques manipulent des entiers codés sur un nombre fixe de bits, qui ne peuvent prendre leurs valeurs que dans un intervalle. Pour les nombres positifs, un ordinateur qui code ses entiers sur n bits pourra coder tous les entiers allant de 0 à <math>2^n -1</math>. Pour les nombres négatifs, l'intervalle est différent et dépend de la représentation utilisée. Dans le cas général, l'ordinateur peut coder les valeurs comprises de <math>N_\text{min}</math> à <math>N_\text{max}</math>. Si le résultat d'un calcul sort de cet intervalle, il ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math> , alors que la valeur basse vaut <math>N_\text{min} - 1</math> (avec <math>N_\text{max}</math> et <math>N_\text{min}</math> respectivement la plus grande et la plus petite valeur codable par l'ordinateur).
===La correction des débordements d'entier : l'arithmétique saturée===
Quand un débordement d'entier survient, tous les circuits de calcul ne procèdent pas de la même manière. Dans les grandes lignes, il y a deux réactions possibles : soit on corrige automatiquement le résultat du débordement, soit on ne fait rien et on se contente de détecter le débordement.
Si le débordement n'est pas corrigé automatiquement, le circuit ne conserve que les bits de poids faibles du résultat. Les bits en trop sont simplement ignorés. On dit qu'on utilise l''''arithmétique modulaire'''. Le problème avec ce genre d'arithmétique, c'est qu'une opération entre deux grands nombres peut donner un résultat très petit. Par exemple, si je dispose de registres 4 bits et que je souhaite faire l'addition 1111 + 0010 (ce qui donne 15 + 2), le résultat est censé être 10001 (17), ce qui est un résultat plus grand que la taille d'un registre. En conservant les 4 bits de poids faible, j’obtiens 0001 (1). En clair, un résultat très grand est transformé en un résultat très petit. Cela peut poser problèmes si on travaille uniquement avec des nombres positifs, mais c'est aussi utilisé pour coder des nombres en complément à deux.
D'autres circuits utilisent ce qu'on appelle l''''arithmétique saturée''' : si un calcul génère un débordement, on arrondi le résultat au plus grand entier supporté par le circuit. Les circuits capables de calculer en arithmétique saturée sont un peu plus complexes, vu qu'il faut rajouter des circuits pour corriger le résultat en cas de débordement. Il suffit généralement de rajouter un ''circuit de saturation'', qui prend en entrée le résultat et le corrige en cas de débordement. Ce circuit de saturation met la valeur maximale en sortie si un débordement survient, mais se contente de recopier le résultat du calcul sur sa sortie s'il n'y a pas de débordement. Typiquement, il est composé d'une couche de multiplexeurs, qui sélectionnent quelle valeur mettre sur la sortie : soit le résultat du calcul, soit le plus grand nombre entier géré par le processeur, soit le plus petit (pour les nombres négatifs/soustractions).
L'arithmétique saturée est utilisée pour les additions et soustractions, mais c'est plus rare pour les multiplications/divisions. Une des raisons est que le résultat d'une addition/soustraction prend un bit de plus que le résultat, là où les multiplications doublent le nombre de bits. Quand une addition déborde, le résultat réel est proche de la valeur maximale codable. mais quand une multiplication déborde, le résultat peut parfois valoir 200 à 60000 fois plus que la valeur maximale codable. Les calculs avec une valeur saturée/corrigée sont donc crédibles pour une suite d'additions, mais pas pour une suite de multiplications.
===La détection des débordements entiers===
Quand un débordement d'entier a eu lieu, il vaut mieux que l'additionneur prévienne ! Pour cela, l'additionneur a une '''sortie de débordement''', parfois nommée ''Overflow'', dont la valeur indique si l'addition a généré un débordement d'entier ou non. Reste que détecter un débordement ne se fait pas de la même manière selon que l'on parle d'un additionneur non-signé ou signé.
Pour les additionneur non-signés, l'additionneur calcule un bit de plus que ce qui est supporté par l'ordinateur. Par exemple, un additionneur 32 bits fournit un résultat sur 33 bits, un débordement d'entier a lieu quand le 33ème bit est à 1. Précisément, la sortie de débordement n'est autre que la retenue finale, celle fournie par le dernier additionneur complet. Le seul type de débordement possible est un débordement par le haut, où le résultat dépasse la valeur maximale. Avec l'arithmétique saturée, le circuit de saturation consiste en une seule couche de multiplexeurs, voire en un circuit de mise à la valeur maximale tel que vu dans le chapitre sur les opérations bits à bits.
[[File:Gestion des débordements d'entiers.png|centre|vignette|upright=2.5|Gestion des débordements d'entiers lors d'une addition non-signée.]]
Pour les additionneurs non-signés, la gestion des débordements d'entiers dépend fortement de la représentation signée. Nous allons étudier le cas du complément à deux. Si vous vous rappelez le chapitre 1, les calculs sur des nombres en complètement à deux utilisent les règles de l'arithmétique modulaire, c'est une condition nécessaire. À priori, on peut penser que dans ces conditions, les débordements d'entiers sont une chose parfaitement normale, qui nous permet d'avoir des résultats corrects. Néanmoins, certains débordements d'entiers peuvent survenir malgré tout et produire des bugs assez ennuyeux.
Si l'on tient en compte les règles du complément à deux, on sait que le bit de poids fort (le plus à gauche) permet de déterminer si le nombre est positif ou négatif : il indique le signe du nombre. Tout se passe comme si les entiers en complément à deux étaient codés sur un bit de moins, et avaient leur longueur amputé du bit de poids fort. Si le résultat d'un calcul écrase le bit de poids fort, il y a un débordement d'entiers. Il existe une règle simple qui permet de détecter ces débordements d'entiers. L'addition de deux nombres positifs ne peut pas être un nombre négatif. Si on additionne deux nombres dont le bit de signe est à 0 et que le bit de signe du résultat est à 1, on est en face d'un débordement d'entiers. Même chose pour deux nombres négatifs : le résultat de l'addition ne peut pas être positif. On peut résumer cela en une phrase : si deux nombres de même signe sont ajoutés, un débordement a lieu quand le bit du signe du résultat a le signe opposé.
Modifier les circuits d'au-dessus pour qu'ils détectent les débordements en complément à deux est simple comme bonjour : il suffit créer un petit circuit combinatoire qui prenne en entrée les bits de signe des opérandes et du résultat, et qui fasse le calcul de l'indicateur de débordements. Si l'on rédige sa table de vérité, on doit se retrouver avec la table suivante :
{|class="wikitable"
|-
!Entrées
!Sortie
|-
|000||0
|-
|001||1
|-
|010||0
|-
|011||0
|-
|100||0
|-
|101||0
|-
|110||1
|-
|111||0
|}
L'équation de ce circuit est la suivante, avec <math>S_a</math> et <math>S_b</math> les signes des deux opérandes, et <math>C_i</math> la retenue de la colonne précédente :
: <math> ( S_a . S_b . \overline{C_i} ) + ( \overline{S_a} . \overline{S_b} . C_i )</math>
En simplifiant, on obtient alors :
: <math>( S_a . S_b ) \oplus C_i</math>
Or, il se trouve que <math>S_a . S_b</math> est tout simplement la retenue en sortie du dernier additionneur, que nous noterons <math>C_o</math>. On trouve donc :
: <math>C_o \oplus C_i</math>
Il suffit donc de faire un XOR entre la dernière retenue et la précédente pour obtenir le bit de débordement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de décalage et de rotation
| prevText=Les circuits de décalage et de rotation
| next=Les circuits de comparaison
| nextText=Les circuits de comparaison
}}
</noinclude>
0c79muw402of9q7dpz7cvttgspfxb47
768665
768664
2026-06-25T19:58:22Z
Mewtow
31375
/* Le calcul parallèle de la retenue */
768665
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir les circuits capables de faire une addition ou une soustraction, ainsi que quelques circuits spécialisés. Précisons cependant que les fabricants de processeurs travaillent d'arrache-pied pour trouver des moyens de rendre ces circuits de calcul plus rapides et plus économes en énergie. Autant vous dire que les circuits que vous allez voir sont vraiment des circuits qui font pâle figure comparé à ce que l'on peut trouver dans un vrai processeur commercial !
==Les circuits pour additionner 2 ou 3 bits==
L'addition se fait en binaire de la même manière qu'en décimal. On additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. La soustraction fonctionne sur le même principe, sur le même modèle qu'en décimal.
[[File:Binary Addition Demonstration.svg|centre|vignette|Exemple d'addition en binaire.]]
En clair, additionner deux nombres demande d'additionner 2 bits et une retenue sur chaque colonne, et de propager les retenues d'une colonne à l'autre. La propagation des retenues est quelque chose de simple en apparence, mais qui est sujet à des optimisations extraordinairement nombreuses. Aussi, pour simplifier l'exposition, nous allons voir comment gérer une colonne avant de voir comment sont propagées les retenues.
[[File:Full Adder Block.svg|vignette|upright=1|class=transparent|Additionneur complet.]]
Si on effectue une addition en colonne, on doit additionner les deux bits sur la colonne, mais aussi additionner une éventuelle retenue. Il faut donc créer un circuit qui additionne trois bits : deux bits de données, plus une retenue. Ce circuit qui additionne trois bits est appelé un '''additionneur complet'''. Il fournit en sortie deux bits : un bit de somme et une retenue sortante. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|-
||0||0||0|| ||0||0
|-
||0||0||1|| ||0||1
|-
||0||1||0|| ||0||1
|-
||0||1||1|| ||1||0
|-
||1||0||0|| ||0||1
|-
||1||0||1|| ||1||0
|-
||1||1||0|| ||1||0
|-
||1||1||1|| ||1||1
|}
Il est possible d'utiliser un tableau de Karnaugh pour traduire la table de vérité, mais elle donne un résultat assez compliqué. La retenue et le bit de somme sont calculés à part. La retenue est calculée avec trois portes ET et une porte OU. Le bit de somme est calculé lui avec 4 portes ET, une porte OU, et trois portes NON.
[[File:Full Adler with 3 entries.png|centre|vignette|upright=2|Additionneur complet, fabriqué à partir de la table de vérité.]]
D'autres méthodes donnent des résultats plus compréhensibles. Nous allons les voir dans la suite de cette section.
===L'additionneur complet basé sur des demi-additionneurs===
[[File:1-bit half-adder.svg|class=transparent|vignette|Demi-addtionneur.]]
Nous avons déjà vu comment additionner deux bits dans le chapitre sur les incrémenteurs, mais quelques rappels ne seront pas de trop. Le '''demi-additionneur''' est un circuit qui additionne deux bits. Il implémente la table d'addition, qui est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Mais un demi-additionneur n'additionne que deux bits, il manque de quoi additionner la retenue. Une solution pour cela est d'enchaîner deux demi-additionneurs : un qui additionne les deux bits de données, et un second qui additionne la retenue au résultat du précédent. La retenue finale se calcule en combinant les sorties de retenue des deux demi-additionneurs, avec une porte OU. Le résultat est un additionneur complet fabriqué avec deux demi-additionneurs. Pour vous en convaincre, établissez la table de vérité de ce circuit, vous verrez que ça marche.
[[File:Full Adder Blocks.svg|centre|vignette|upright=2|Additionneur complet fabriqué avec deux demi-additionneurs.]]
[[File:Half-adder.svg|vignette|upright=1|Circuit d'un demi-additionneur naïf.]]
L'implémentation naïve d'un demi-additionneur utilise une porte XOR et une porte ET, comme illustré ci-contre. Le circuit s'obtient en faisant uen traduction littérale de la table de vérité du circuit. Si je dis qu'elle est naïve, c'est car il est possible de l'optimiser, de manière à éliminer des redondances cachées. Mais nous avions déjà vu cela dans le chapitre sur les incrémenteurs, aussi laissons cela pour plus tard.
En combinant deux demi-additionneurs de ce type, on obtient l'additionneur complet suivant. Les deux sorties de retenue des demi-additionneurs sont combinées avec une porte OU, pour calculer la retenue finale.
{| class="flexible"
|[[File:Full Adder Modules.svg|class=transparent|Composition d'un additionneur complet. On voit bien que celui-ci est composé de deux demi-additionneurs, en rouge et en bleu, auxquels on a ajouté une porte OU pour calculer la retenue finale.]]
|[[File:Full-adder.svg|300px|class=transparent|Circuit d'un additionneur complet.]]
|}
Le fait de combiner les deux retenues avec une porte OU n'est pas ce qu'il y a de plus intuitif. La table de vérité nous dit que ça fonctionne, mais on comprend mal pourquoi. Mais tout devient plus clair quand on sait que les deux retenues ne peuvent pas être à 1 en même temps. Même dans l'addition 1 + 1 + 1, seule une retenue est à 1. Soit l'addition des deux bits d'opérande donne naissance à une retenue, soit c'est l'addition ''résultat + retenue entrante''. La retenue sortant est donc à 1 quand une des deux addition donne une retenue. D'où l'usage d'une porte OU. Remarquez qu'une porte XOR donne le même résultat, vu que le cas où les deux retenues sont à 1 n'est jamais rencontré.
===La propagation et la génération des retenues===
L'additionneur complet que nous allons voir dans cette section sert à introduire les concepts de propagation, génération et absorption de retenue. Il s'agit de concepts très importants quand on étudie les additionneurs. l'idée part d'un principe très simple : la retenue sortante dépend de la retenue d'entrée. La relation entre les deux se résume à trois cas, qui dépendent de la valeur des deux bits additionnés, nommés A et B.
* Dans le premier cas, la retenue entrante est égale à la retenue sortante. On dit que la retenue entrante est propagée par l'additionneur.
* Dans le second cas, la retenue est forcée à 0 : la retenue sortante vaut 0, peu importe la valeur de la retenue entrante.
* Dans le troisième cas, la retenue est forcée à 1 : la retenue entrante vaut 1, peu importe la valeur de la retenue entrante.
[[File:Signaux P et G fournis par un demi-additionneur.png|vignette|Signaux P et G fournis par un demi-additionneur]]
Maintenant, créons un circuit qui nous dise si une retenue est propagée ou générée. Il a deux bits de sortie, nommés P et G : P pour ''Propagate'', G pour ''Generate''. Le bit P est à 1 si la retenue entrante est propagée, il est à 0 sinon. Le bit G est à 1 si une retenue est générée, à 0 sinon. Une retenue est considérée comme absorbée si elle n'est pas ni propagée ni générée, pas besoin d'un troisième bit pour gérer ce cas.
Il se trouve que ce circuit n'est autre qu'un demi-additionneur ! Pour vous en rendre compte, regardez la table de vérité d'un additionneur complet, illustrée ci-dessous. Lorsque les deux bits d'opérande sont à 0, la retenue sortante vaut toujours 0. Si ils sont tous deux à 1, alors la retenue sortante vaut 1. S'ils sont différents, alors retenues sortante et entrante sont égales. Le bit P est donc généré par une simple porte XOR. Quant au bit G, il est à 1 si les deux bits d'opérandes sont à 1, ce qui correspond à une porte ET. Il se trouve que ces deux portes forment un demi-additionneur !
{|class="wikitable"
|-
! Opérande 1 !! Opérande 2 !! Retenue entrante !! !! Retenue sortante
|- class="f_rouge"
||0||0||0|| ||0
|- class="f_rouge"
||0||0||1|| ||0
|- class="f_bleu"
||0||1||0|| ||0
|- class="f_bleu"
||0||1||1|| ||1
|- class="f_bleu"
||1||0||0|| ||0
|- class="f_bleu"
||1||0||1|| ||1
|- class="f_vert"
||1||1||0|| ||1
|- class="f_vert"
||1||1||1|| ||1
|}
Ensuite, créons un circuit qui prend ces deux sorties P et G, et calcule la retenue sortante en fonction. Le circuit en question a trois entrées : la retenue entrante, les deux bits P et G. La retenue sortante vaut 1 soit si une retenue est générée, soit si la retenue entrante est propagée et qu'elle vaut 1. En notant <math>{C_{in}}</math> et <math>{C_{out}}</math> les retenues entrantes et sortante, on a :
: <math>{C_{out}} = G + \left( P . C_{in} \right)</math>
Le circuit est donc composé d'une porte OU et d'une porte ET. En combinant un demi-additionneur avec le circuit de calcul de retenue sortante vu plus haut, on a :
[[File:Additionneur complet avec propagation et génération de retenue.png|class=transparent|centre|vignette|upright=2|Additionneur complet avec propagation et génération de retenue.]]
Certes, on retombe sur le circuit vu plus haut. Mais il est possible de remplacer le circuit en orange par un autre. Par exemple, il est possible de le remplacer par multiplexeur, qui choisit entre la retenue générée et la retenue entrante (propagée). Le choix se fait selon la valeur du bit P, qui chosiit entre propager la retenue et la générer.
[[File:Additionneur crée avec un multiplexeur.png|centre|vignette|upright=1.5|Additionneur crée avec un multiplexeur]]
Le circuit exact est illustré ci-dessous. Il semble utiliser plus de portes logiques que l'additionneur complet précédent. Cependant, nous verrons dans quelques chapitres qu'il est possible d'implémenter un multiplexeur avec seulement 6 transistors, voire moins ! L'implémentation utilise des portes à transmission, mais nous en reparlerons dans le chapitre sur les transistors, quand nous verrons les additionneurs à ''Manchester Carry Chain''. Au passage, une variante de ce circuit a été utilisée dans le processeur processeur 8086 d'Intel, comme on le verra dans le chapitre suivant.
[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
[[File:FulladderPG.png|vignette|upright=1|Additionneur complet avec deux sorties P et G.]]
Pour finir, sachez qu'il existe des additionneurs qui fournissent : le bit de résultat, deux sorties P et G qui indiquent si l'addition propage ou génère une retenue. Il ne s'agit pas d'additionneurs complets, car il manque de quoi calculer la retenue sortante. De tels additionneurs seront appelés des '''additionneurs partiels'''. De tels additionneurs sont utilisés dans certains additionneurs pour gagner en performance.
Enfin, il existe aussi ce que j'ai décidé d'appeler des '''additionneurs P/G''', qui sont des additionneurs complets auxquels on a ajouté deux sorties P et G, en plus de la sortie de retenue. Leur circuit est le même que celui d'un additionneur complet, auquel on a ajouté deux fils. Vu que ces deux sorties sont fournies par le premier demi-additionneur, ajouter ces deux sorties demande d'ajouter des fils, pas de portes logiques. De tels additionneurs sont aussi utilisés dans certains additionneurs pour gagner en performance.
===L'additionneur complet basé sur une porte à majorité===
Maintenant, voyons une dernière implémentation possible de l'additionneur complet. Mais avant de voir comment l'additionneur est implémenté, nous allons devoir faire un petit focus sur la retenue sortante. Reprenons la table de vérité, et regardons ce qu'il en est pour la retenue sortante uniquement.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante
|-
||0||0||0|| ||0
|-
||0||0||1|| ||0
|-
||0||1||0|| ||0
|-
||0||1||1|| ||1
|-
||1||0||0|| ||0
|-
||1||0||1|| ||1
|-
||1||1||0|| ||1
|-
||1||1||1|| ||1
|}
Vous remarquerez que la retenue sortante est égale au bit majoritaire, parmi les trois bits d'opérande. Si deux ou trois bits sont à 1, la retenue sortante vaut 1. Et réciproquement avec 0. Le circuit de calcul de la retenue peut donc être remplacé par une '''porte à majorité'''.
[[File:Additionneur crée avec une porte à majorité.png|centre|vignette|upright=2|Additionneur crée avec une porte à majorité]]
Mais une porte à majorité est assez gourmande en circuit, ce qui fait qu'un additionneur pareil ne serait pas pratique, ni utile. Du moins, ce serait le cas s'il n'y avait pas une possibilité d'optimisation extrêmement intéressante. Il est possible de calculer le 'bit de somme' à partir de la retenue sortante ! En effet, le bit de somme est l'inverse de la retenue sortante, sauf dans deux cas : les trois bits d'entrée sont à 0, où ils sont tous à 1. Le bit de somme vaut 0 dans le premier cas, 1 dans le second cas.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|- class="f_bleu"
||0||0||0|| ||0||0
|- class="f_vert"
||0||0||1|| ||0||1
|- class="f_vert"
||0||1||0|| ||0||1
|- class="f_vert"
||0||1||1|| ||1||0
|- class="f_vert"
||1||0||0|| ||0||1
|- class="f_vert"
||1||0||1|| ||1||0
|- class="f_vert"
||1||1||0|| ||1||0
|- class="f_rouge"
||1||1||1|| ||1||1
|}
En clair, 6 lignes de la table de vérité sur 8 peuvent se calculer avec une porte à majorité, le reste demandant quelques portes logiques pour faire la correction. L'implémentation en circuit demande donc trois choses :
* de calculer la retenue sortante et de quoi l'inverser ;
* un circuit qui vérifie si tous les bits opérande valent 0 : une porte NOR ou OU fait l'affaire ;
* un circuit qui vérifie s'ils valent tous 1, à savoir une porte ET.
Ensuite, on combine le résultat des trois circuits précédents pour obtenir le résultat final. La combinaison est le fait du circuit en jaune dans le schéma ci-dessous.
[[File:Full adder basé sur une modification de la retenue.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue]]
Le circuit en jaune est assez simple. Quand la porte ET sort un 1, la sortie doit être forcée à 1. Par contre, quand la porte OU sort un zéro, la sortie doit être forcée à 0. Il suffit donc d'enchainer un circuit de mise à 1 et un circuit de mise à 0. Nous avons vu dans le chapitre sur les opérations de masquage qu'il s'agit respectivement d'une porte OU et d'une porte ET.
[[File:Full adder basé sur une modification de la retenue interne, au complet.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue interne, au complet]]
Divers processeurs utilisaient des additionneurs complets de ce type : le fameux Z80, l'Intel 4004 et de l'Intel 8008 sont les exemples les plus notables. Pourtant, on pourrait s'étonner qu'un tel circuit ait existé. Il utilise beaucoup de portes logiques, a une profondeur logique supérieure : il n'a rien d'avantageux. Sauf qu'il était utilisé sur d'anciens processeurs, qui utilisaient la technologie dite TTL, différente de la technologie CMOS des transistors modernes. Et avec la technologie TTL, il est possible de fusionner plusieurs portes logiques ET et NOR en une seule porte logique ET/OU/NON ! Un additionneur complet construit ainsi ne prenait que deux portes logiques : une pour le calcul de la retenue sortante, une autre pour le reste du circuit.
==L'addition non signée==
Voyons maintenant un circuit capable d'additionner deux nombres entiers: l''''additionneur'''. Dans la version qu'on va voir, ce circuit manipulera des nombres strictement positifs. L'addition des nombres codés en complètement à deux sera vu dans une section ultérieure.
===L'additionneur série===
Il est possible d'additionner deux nombres bit par bit, grâce à un additionneur complet associé à plusieurs registres à décalages. Les opérandes sont placées chacune dans un registre à décalage, et l'additionneur complet utilise les bits sortants de ces registres à décalage. Le bit du résultat est envoyé au registre à décalage pour le résultat. La retenue de l'addition est stockée dans une bascule de 1 bit, en attente du prochain cycle d'horloge. Un tel additionneur est appelé un '''additionneur série'''. Il a été utilisé sur d'anciens ordinateurs dans les années 50-60, aussi bien des prototypes que des ordinateurs commerciaux.
[[File:Additionneur série.jpg|centre|vignette|upright=2.5|Additionneur série.]]
===L'additionneur à propagation de retenue===
L''''additionneur à propagation de retenue''' est aussi appelé l'additionneur ''ripple carry'', c'est son nom anglais. J'utiliserais occasionnellement le nom anglais dans ce qui suit, car celui-ci est plus court. Ilutilise un additionneur complet pour chaque colonne de l'addition. Pour gérer les retenues, il suffit ainsi de câbler des additionneurs complets les uns à la suite des autres, chacun envoyant sa retenue à la colonne suivante.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Notons la présence de la retenue sortante, qui est utilisée pour détecter les débordements d'entier, ainsi que pour d'autres opérations. Le bit de retenue final est souvent stocké dans un registre spécial du processeur (généralement appelé ''carry flag'').
Notez aussi, à droite du schéma précédent, l'entrée de retenue <math>\text{R}_\text{Entrée}</math>. Elle est très utile pour l'implémentation de certaines opérations comme l'inversion de signe, la soustraction, l'incrémentation, etc. Certains processeurs sont capables de faire une opération appelée ADC, ADDC ou autre nom signifiant ''Addition with Carry'', qui permet de faire le calcul A + B + Retenue (la retenue en question est la retenue sortante de l'addition précédente, stockée dans le registre ''carry flag''). Son utilité principale est de permettre des additions d'entiers plus grands que ceux supportés par le processeur. Par exemple, cela permet de faire des additions d'entiers 32 bits sur un processeur 16 bits.
[[File:Adder Network 3 Bit Sum Delay 003.svg|vignette|upright=0.5|Propagation de retenue dans l'additionneur.]]
Cet additionneur utilise très peu de portes logiques, ce qui fait qu'il était utilisé sur certains processeurs 8 et 16 bits, dont le budget en portes logiques était limité. Bien que très simple, cet additionneur est cependant peu performant. Son temps de calcul est proportionnel à la taille des opérandes. Par exemple, additionner deux opérandes de 32 bits prendra deux fois plus de temps que pour des opérandes de 16 bits. La raison est que les retenues doivent se propager à travers le circuit, du premier additionneur jusqu'au dernier.
L'addition étant une opération fréquente, d'autres additionneurs ont été inventés. Ils utilisent des optimisations qui utilisent plus de circuits pour gagner en rapidité. Mais avant de voir ces optimisations, nous allons voir s'il est possible d'optimiser les additionneurs à propagation de retenue.
===L'additionneur à saut de retenue===
L'additionneur à propagation de retenue peut être optimisé de deux manières. La première accélère la propagation de la retenue, en agissant au niveau des additionneurs complets. La ''Manchester carry chain'' est une optimisation de ce type, mais on ne peut pas encore expliquer à ce stade du cours, car elle optimisait les additionneurs complets au niveau des transistors eux-mêmes. Elle a été utilisée sur de nombreux processeurs connus, comme le 8086 d'Intel ou le Z80.
La seconde manière rend l'addition plus rapide dans certains cas. Le résultat est un additionneur dont le temps de calcul est variable. Le calcul prendra quelques cycles d'horloges avec certains opérandes, mais d'autres opérandes ne verront aucune amélioration. Il n'améliore pas le pire des cas, dans lequel la retenue doit être propagée du début à la fin, du bit de poids faible au bit de poids fort. Mais dans d'autres cas, il permet d'avoir le résultat en avance. L'optimisation en question donne l''''additionneur à saut de retenue''' (''carry-skip adder'').
Il est composé de blocs qui additionnent 4/5 bits, rarement plus. La taille d'un bloc varie d'un additionneur à l'autre, elle peut même varier dans l'additionneur, mais nous allons prendre un exemple où les blocs font 4 bits. L'idée est de détecter précocement si la retenue entrante est propagée à travers tout le bloc. Si la retenue est propagée dans tous le bloc, on peut directement l'envoyer sur la sortie de retenue, la retenue saute le bloc entier. Dans le cas contraire, on doit calculer la retenue normalement. Le choix entre les deux est le fait d'un multiplexeur. Toute la difficulté est de savoir comment commander le multiplexeur.
[[File:Carry skip adder, principe de base.png|centre|vignette|upright=2.5|Carry skip adder : principe de base]]
Pour savoir si une retenue est propagée dans un bloc entier, il faut utiliser des additionneurs complets, qui ont en plus une sortie P indiquant que l'additionneur propage la retenue. Le bloc propage une retenue si chaque additionneur complet propage la retenue, donc s'ils ont tous leur sortie P à 1. Un simple ET logique suffit à déterminer si c'est le cas. Le signal de commande du multiplexeur est donc un vulgaire ET entre toutes les sorties P des additionneurs complets du bloc.
[[File:Additionneur à saut de retenue.png|centre|vignette|upright=2.5|Calcul de la commande du MUX.]]
Voici ce que ça donne pour un bloc de 4 bits :
[[File:CSAdder4Bit.svg|centre|vignette|upright=1.5|Additionneur ''carry skip'' : bloc de 4 bits.]]
L'additionneur à saut de retenue est construit en assemblant plusieurs blocs de ce type.
[[File:BCSAdder16Bit.svg|centre|vignette|upright=3|Additionneur à saut de retenue.]]
===L'additionneur à sélection de retenue===
L''''additionneur à sélection de retenue''' utilise aussi des blocs, comme les additionneurs précédents. L'addition se fait en deux versions : une avec la retenue du bloc précédent valant zéro, et une autre version avec la retenue du bloc précédent valant 1. Il suffira alors de choisir le bon résultat avec un multiplexeur, une fois cette retenue connue. On gagne ainsi du temps en calculant à l'avance les valeurs de certains bits du résultat, sans connaître la valeur de la retenue. Petit détail : sur certains additionneurs à sélection de retenue, les blocs de base n'ont pas la même taille. Cela permet de tenir compte des temps de propagation des retenues entre les blocs.
[[File:Additionneur à sélection de retenue.png|centre|vignette|upright=1.5|Additionneur à sélection de retenue avec seulement deux blocs.]]
Dans les exemples du dessus, chaque sous-additionneur étaient des additionneurs à propagation de retenue. Mais ce n'est pas une obligation, et tout autre type d’additionneur peut être utilisé. Par exemple, on peut faire en sorte que les sous-additionneurs soient eux-mêmes des additionneurs à sélection de retenue, et poursuivre ainsi de suite, récursivement. On obtient alors un '''additionneur à somme conditionnelle''', plus rapide que l'additionneur à sélection de retenue, mais qui utilise beaucoup plus de portes logiques.
===Les additionneurs à anticipation de retenue===
Les '''additionneurs à anticipation de retenue''' calculent chaque retenue sans avoir à propager les retenues précédentes. Au lieu de calculer les retenues une par une, ils calculent toutes les retenues en parallèle, à partir des bits des opérandes. Une fois les retenues pré-calculées, il suffit de les additionner avec les deux bits adéquats, pour obtenir le résultat. Ces additionneurs sont composés de deux parties :
* un circuit qui pré-calcule les retenues, sans les propager, directement à partir des opérandes ;
* d'une couche d'additionneurs complets, qui additionnent chacun deux bits d'opérande et la retenue pré-calculée.
[[File:Additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Additionneur à anticipation de retenue.]]
Le calcul des retenues se fait en deux étapes. La première détermine si les retenues sont propagées ou générées, pour chaque colonne. Elle détermine, pour chaque colonne, les bits P et G qui indiquent respectivement : qu'une retenue a été générée sur cette colonne, que la colonne propage la retenue précédente. La seconde étape utilise ces signaux P et G pour déterminer la retenue sur chaque colonne. Le circuit de calcul des retenues est donc composé de deux parties : une couche de demi-additionneurs pour générer les signaux P et G, un circuit de calcul des retenues proprement dit.
[[File:Circuit complet d'un additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Circuit complet d'un additionneur à anticipation de retenue.]]
Pour rappel, la retenue de la colonne i, notée <math>C_i</math> est égale à :
: <math>G_i + (P_i . C_{i-1})</math>, avec G le signal de génération de retenue, P le signal de propagation de retenue.
Si on utilisait cette formule sans trop réfléchir, on retomberait sur un additionneur à propagation de retenue inutilement compliqué. L'astuce des additionneurs à anticipation de retenue consiste à remplacer le terme Ci−1 par sa valeur calculée avant. Par exemple, je prends un additionneur 4 bits. Je dispose de deux nombres A et B, contenant chacun 4 bits : A3, A2, A1, et A0 pour le nombre A, et B3, B2, B1, et B0 pour le nombre B. Si j'effectue les remplacements, j'obtiens les formules suivantes :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Ces formules nous permettent de déduire la valeur d'une retenue directement : il reste alors à créer un circuit qui implémente ces formules, et le tour est joué.
[[File:Four bit adder with carry lookahead.svg|centre|vignette|upright=1.5|Additionneur à anticipation de retenue de 4 bits.]]
Plus haut, j'ai dit que le circuit était composé de trois couches : une couche de demi-additionneur pour les signaux P et G ; un circuit d'anticipation de retenue, puis l'addition finale. Mais les deux bits d'opérandes sont déjà additionnés dans la couche de demi-additionneur, autant réutiliser le résultat pour l'additionner à la retenue. Pour éliminer toute redondance de ce type, il est possible d'utiliser des additionneurs partiels (des additionneurs où la sortie de retenue a été remplacée par deux sorties P et G). L'additionneur final est alors composé de plusieurs additionneurs complets de ce type, couplé à un circuit d'anticipation de retenue.
[[File:Cla4bits.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue de 4 bits.]]
Les additionneurs à anticipation de retenue sont plus rapides que les additionneurs à propagation de retenue. La raison est que les retenues sont calculées rapidement, dans un temps plus petit qu'en propageant la retenue. Pour rentrer dans les détails mathématiques, le temps de calcul n'est pas proportionnel au nombre de bits, mais proportionnel au logarithme de ce nombre de bits. Par contre, le gain en temps de calcul s'accompagne d'un cout en portes logiques conséquent. Le circuit qui calcule les retenues anticipées utilise beaucoup de portes logiques.
===Les additionneurs à calcul parallèle de préfixe===
Du fait du cout en portes logiques, utiliser un additionneur à anticipation de retenue est une solution pratique pour additionner 4 ou 8 bits. Mais pour des opérandes plus grands, de 16 ou 32 bits, cela utiliserait trop de portes logiques. Une partie de ce cout en portes logiques est cependant lié à des redondances. Si on analyse deux retenues différentes, à des colonnes différentes. Les circuits qui calculent ces deux retenues seront différents, mais ils contiendront des sous-circuits identiques. Pour vous en rendre compte, reprenons les quatre équations obtenues précédemment et essayez de trouver des redondances :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Vous devriez trouver :
* le terme ''P0 · C0'' dans toutes les lignes ;
* les termes ''P1 · P0 · C0'' et ''P1 · P0'' dans les trois dernières lignes ;
* le terme ''P2 · P1 · P0 · C0'' ; ''P2 · P1 · G0'' et ''P2 · G1'' dans les deux dernières lignes.
Il existe plusieurs manières d'exploiter de telles redondances, exploitées dans les '''additionneurs à calcul parallèle de préfixe'''. L'optimisation apportée est de générer des signaux ''propagate'' et ''generate'' pour des groupes de 2, 3, 4, ..., N colonnes. Par exemple, il est possible de générer un signal ''P 0 vers 3'', qui indique si la retenue de la première colonne est propagée jusqu'à la 4ème colonne. Un autre exemple est un signal qui indique qu'une retenue a été générée entre les colonnes 4 à 7 génèrent une retenue ou non. En clair, les signaux P et G ont maintenant un intervalle, qui précise de quelle colonne vers quelle colonne se fait la propagation, ou entre quelles colonnes se fait la génération.
Il est possible de combiner les signaux P et G de deux groupes de colonne, s'ils sont contiguës (ils peuvent aussi se recouvrir). Par exemple, il est possible de calculer les bits P et G pour les colonnes 0 à 10, à partir des deux signaux P/G des colonnes 0-4 et 5-10. Pour cela, les équations sont assez simples. Si je prends deux groupes nommés A et B, avec A pour les colonnes de poids fort et B celles de poids faible, on a :
: <math>P_{sortie} = P_A . P_B</math>
: <math>G_{sortie} = G_A . P_A . G_B</math>
Pour les bits des colonnes 2, 4, 8, 16, 32, 64, et autres, le calcul est simple. L'idée est de grouper les colonnes par groupes de 2. Puis, on calcule chaque groupe de 4 bits à partir de deux groupes de 2. Puis les groupes de 8 bits sont calculés à partir de deux groupes de 4 bits, et ainsi de suite pour toute puissance de deux.
[[File:Squelette d'un additionneur à calcul parallèle de préfixe.jpg|centre|vignette|upright=2|Squelette d'un additionneur à calcul parallèle de préfixe]]
Les seules difficultés surviennent pour les colonnes dont le nombre n'est pas une puissance de deux. Il y a plusieurs manières pour subdiviser ces colonnes en intervalles plus petits et combiner le tout. Et elles donnent chacune des additionneurs différents. Ils portent les noms d'''additionneur de Ladner-Fisher'', d'''additionneur de Brent-Kung'', d'''additionneur de Kogge-Stone'', etc. Dans le cas général, l'additionneur de Kogge-Stone est un des plus rapide, mais aussi un des plus gourmand en circuits, alors que c'est l'inverse pour l'additionneur de Brent-Kung. Les deux additionneurs sont illustrés ci-dessous, pour des opérandes de 8 bits. Dans ceux-ci, chaque losange calculent les bits P et G, à partir des signaux P et G de deux groupes précédents.
{|
|[[File:Brent-kung-8-bit.png|vignette|Brent-kung-8-bit]]
|[[File:Kogge-stone-8-bit.png|vignette|upright=1.2|Kogge-stone-8-bit]]
|}
===Le calcul parallèle de la retenue===
L'anticipation de retenue est très rapide, mais utilise beaucoup de circuits, même en utilisant le calcul parallèle de préfixe. Les additionneurs à propagation de retenue font eux le compromis inverse. Mais il existe des intermédiaires, qui visent à obtenir un compromis entre performance et cout en portes logiques.
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Un de ces compromis découpe un additionneur de 16/32/64 bits en additionneurs de 4/5 bits, qui sont enchainés en utilisant la propagation de retenue. Par contre, ces petits additionneurs de 4/5 bits utilisent l'anticipation de retenue. Ces additionneurs simples seront nommés ''blocs'' dans ce qui suit, et l'un d'entre eux est illustré ci-contre. Vous remarquerez qu'un bloc a une retenue entrante et une retenue sortante. Par exemple, on peut utiliser 4 additionneurs à anticipation de retenue de 4 bits, et propager les retenues entre eux. Le premier bloc calcule une retenue, qui est propagée au second bloc. Le second bloc calcule lui aussi une retenue, qui est propagée au troisième bloc, etc.
[[File:4008 Functional Diagram.svg|vignette|upright=1|4008 Functional Diagram]]
Le gain en performance est significatif, mais il est possible de faire presque aussi rapide, tout en économisant beaucoup de circuits. L'idée est de simplifier les additionneurs 4 bits, afin de ne pas utiliser une anticipation de retenue complète. L'anticipation de retenue n'est utilisée que pour la retenue sortante, alors que les bits de somme sont calculées avec propagation de retenue. La retenue sortante est donc calculée en parallèle de l'addition. Chaque bloc contient, à côté d'un additionneur proprement dit, un circuit qui calcule la retenue sortante. Il existe de nombreuses manières de calculer la retenue sortante.
Le gain en portes logiques est assez significatif. En effet, on se passe de circuits d'anticipation de retenue pour les 4 bits de somme, on ne garde que celui pour la retenue sortante. Le fait d'utiliser la propagation de retenue pour calculer les 4 bits de somme est censé avoir un léger cout en performance, mais il se trouve que ce cout est très faible. En effet, le résultat n'est connu qu'une fois les retenues propagées jusqu'au dernier bloc. Et malgré l'anticipation des retenues, la propagation est assez lente. les additionneurs à propagation de retenue auront terminé leur travail bien avant.
Un exemple est celui de l'additionneur CMOS 4008, un additionneur de 4 bit. Il est composé en trois sections. Une première couche de demi-additionneurs calcule les signaux P et G utilisés à la fois pour l'anticipation de la retenue sortante, que pour calculer les bits de somme. Le circuit de calcul de la retenue sortante utilise l'anticipation de retenue. Le reste du circuit fait l'addition en propageant les retenues.
[[File:CMOS 4008, circuit découpé en sections.png|centre|vignette|upright=1.5|CMOS 4008, circuit découpé en sections]]
===Les additionneurs à anticipation de retenue multi-niveau===
Dans cette section, nous allons voir des additionneurs conçus en assemblant des additionneurs plus simples, qui additionnent environ 4 à 5 bits, parfois plus, parfois moins.
Les blocs sont tous identiques dans le cas le plus simple, mais il est possible d'utiliser des blocs de taille variable. Par exemple, le premier bloc peut avoir des opérandes de 6 bits, le second des opérandes de 7 bits, etc. Faire ainsi permet de gagner un petit peu en performances, si la taille de chaque bloc est bien choisie. La raison est une question de temps de propagation des retenues. La retenue met plus de temps à se propager à travers 8 blocs qu'à travers 4, ce qui prend plus de temps qu'à travers 2 blocs, etc. En tenir compte fait que la taille des blocs tend à augmenter ou diminuer quand on se rapproche des bits de poids fort.
Il est possible d'utiliser l'anticipation de retenue entre deux blocs. Par exemple, il est possible de créer un additionneur 16 bits en utilisant 4 additionneurs/blocs de 4 bits. L'additionneur utilisé peut être un additionneur à propagation de retenue, ou à anticipation de retenue, peu importe.
Pour cela, les additionneurs de 4 bits doivent fournir deux sorties P et G. Les deux sorties indiquent que le bloc soit a généré une retenue, soit a propagé la retenue entrante. Par propagé, on veut dire que la retenue entrante du bloc s'est retrouvée sur la sortie de retenue du bloc. C'est le cas si les 4 colonnes propagent la retenue, ce qui signifie que :
: <math>P_\text{4 bits} = P_0 \cdot P_1 \cdot P_2 \cdot P_3</math>
Pour la génération, cela veut dire qu'une colonne a générée une retenue, qui a été propagée jusqu'à la sortie. En clair, pour chaque colonne, il faut que le G = 1, et que les signaux P des colonnes soient à 1. Pour les 4 colonnes, cela donne :
: <math>G_\text{4 bits} = G_3 + G_2 \cdot P_3 + G_1 \cdot P_3 \cdot P_2 + G_0 \cdot P_3 \cdot P_2 \cdot P_1</math>
[[File:Cla4bitsPG.png|centre|vignette|upright=2|Additionneur à anticipation de retenue de 4 bits.]]
Pour combiner 4 blocs de 4 bits, on devra donc combiner 4 bits P et G, avec un circuit d'anticipation de retenue.
[[File:16-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 16 bits.]]
Il est même possible d'aller plus loin et de combiner 4 additionneurs précédents pour obtenir un additionneur 64 bits.
[[File:64-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 64 bits.]]
Le résultat est un additionneur à plusieurs niveaux d’anticipation de retenue. Une anticipation de retenue dans un blocs de 4 bits (facultative), entre blocs de 4 bits, entre blocs de 16 bits. La même logique peut être utilisée avec des blocs de taille différente de 4, 16 et 64 bits.
==L'addition signée et la soustraction==
Après avoir vu l'addition, il est logique de passer à la soustraction, les deux opérations étant très proches. Si on sait câbler une addition entre entiers positifs, câbler une soustraction n'est pas très compliqué. De plus, la soustraction permet de faire des additions de nombres signés.
===Le soustracteur pour opérandes entiers===
Pour soustraire deux nombres entiers, on peut adapter l'algorithme de soustraction utilisé en décimal, celui que vous avez appris à l'école. Celui-ci ressemble fortement à l'algorithme d'addition : on soustrait les bits de même poids, et on propage éventuellement une retenue sur la colonne suivante. À la différence de l'addition, la retenue est soustraite, et non ajoutée. La table de soustraction nous dit quel est le résultat de la soustraction de deux bits. La voici :
* 0 - 0 = 0 ;
* 0 - 1 = 1 et une retenue ;
* 1 - 0 = 1 ;
* 1 - 1 = 0.
[[File:Algebra1 05 fig014.svg|centre|vignette|Soustraction en binaire, avec les retenues en rouge.]]
La table de soustraction peut servir de table de vérité pour construire un circuit qui soustrait deux bits. Celui-ci est appelé un '''demi-soustracteur'''. Il ressemble beaucoup à un demi-additionneur, les différences se résumant à une porte NON ajoutée pour le calcul de la retenue.
[[File:Half Subtractor Vektor.svg|centre|vignette|Demi-soustracteur.]]
Comme pour l'additionneur, deux demi-soustracteurs peuvent être combinés pour donner un '''soustracteur complet'''. Le calcul de la retenue se fait en combinant les deux retenues des demi-soustracteurs avec une porte OU. Les soustracteurs complets sont utilisés pour créer des soustracteurs à propagation de retenue ou tout autre circuit soustracteur, sur le même modèle que les additionneurs.
[[File:FullSubtractor.svg|centre|vignette|upright=2|Soustracteur complet.]]
Vous l'aurez sans doute remarqué, mais le demi-soustracteur ressemble beaucoup au demi-additionneur. La seule différence est une porte NON ajoutée au bon endroit dans le demi-soustracteur.
{|
|[[File:Half-adder.svg|vignette|Demi-additionneur]]
|[[File:Half Subtractor Vektor.svg|vignette|upright=1.3|Demi-soustracteur]]
|}
La conséquence est qu'il est possible de créer un circuit capable de faire à la fois des additions et des soustractions. L'idée est de remplacer la porte NON du demi-soustracteur par un inverseur commandable. Si on envoie un zéro sur l'entrée de commande, l'inverseur commandable se comportera comme une porte OUI. Le circuit sera alors identique à un demi-additionneur. Si on envoie un 1 sur l'entrée de commande, l'inverseur commandable se comportera comme une porte NON. le circuit sera alors un demi-soustracteur. Il faut faire cela pour chaque demi-soustracteur, ce qui donne ceci pour un soustracteur complet. le résultat est alors appelé un '''additionneur-soustracteur complet'''.
[[File:Additionneur-soustracteur complet.png|centre|vignette|upright=2|Additionneur-soustracteur complet]]
===L'additionneur-soustracteur pour opérandes codées en complément à deux===
Étudions le cas de la soustraction en complément à deux, dans l'objectif de créer un circuit soustracteur. Vous savez sûrement que a−b et a+(−b) sont deux expressions équivalentes. Et en complément à deux, − b = not(b) + 1. Dit autrement, a − b = a + not(b) + 1. On pourrait se dire qu'il faut deux additionneurs pour faire le calcul, mais la majorité des additionneurs possède une entrée de retenue pour incrémenter le résultat de l'addition. Un soustracteur en complément à deux est donc simplement composé d'un additionneur et d'un inverseur.
[[File:Additionneur soustracteur.png|centre|vignette|upright=2|Soustracteur en complément à deux.]]
Il est possible de créer un circuit capable d'effectuer soit une addition, soit une soustraction : il suffit de remplacer l'inverseur par un inverseur commandable, qui peut être désactivé. On a vu comment créer un tel inverseur commandable dans le chapitre sur les circuits combinatoires. On peut remarquer que l'entrée de retenue et l'entrée de commande de l'inverseur sont activées en même temps : on peut fusionner les deux signaux en un seul.
[[File:Additionneur-soustracteur en complément à deux.png|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux.]]
Une implémentation alternative est la suivante. Elle remplace l'inverseur commandable par un multiplexeur.
[[File:4-bit ripple carry adder-subtracter.svg|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux, version alternative.]]
===L'additionneur-soustracteur pour opérandes codées en signe-magnitude===
Passons maintenant aux nombres codés en signe-valeur absolue, les deux opérandes étant notées A et B. Suivant les signes des deux opérandes, on a quatre cas possibles : A + B, A − B (B négatif), −A + B (A négatif) et −A − B (A et B négatifs). Une astuce est que le circuit n'a besoin que de calculer A + B et A − B : il peut les inverser pour obtenir − A − B ou B − A. A + B et A − B peuvent se calculer avec un additionneur-soustracteur, reste à corriger le résultat. Il suffit de lui ajouter un inverseur commandable pour obtenir le circuit d'addition finale.
[[File:Additionneur en signe-valeur absolue.png|centre|vignette|upright=2|Additionneur en signe-valeur absolue.]]
Toute la difficulté tient dans le calcul du bit de signe du résultat, quand interviennent des soustractions. Autant l'addition de deux nombres de même signe (A + B et −A − B) ne pose aucun problème, autant les soustractions posent problème (A − B et −A + B). Suivant que <math>A<B</math> ou que <math>A>B</math>, le signe du résultat ne sera pas le même. Déterminer le signe du résultat se fait en regardant les bits de débordement d'entier, comme on le verra plus bas.
===L'additionneur-soustracteur pour opérandes codées en représentation par excès===
Passons maintenant aux nombres codés en représentation par excès. On pourrait croire que ces nombres s'additionnent comme des nombres non-signés, mais ce serait oublier la présence du biais, qui pose problème. Dans les cas de nombres signés gérés avec un biais, voyons ce que donne l'addition de deux nombres :
: <math>( n_1 + biais ) + ( n_2 + biais ) = n_1 + n_2 + 2 \times biais</math>
Or, le résultat correct serait :
: <math>n_1 + n_2 + biais</math>
En effectuant l'addition telle quelle, le biais est compté deux fois. On doit donc le soustraire après l'addition pour obtenir le résultat correct.
Même chose pour la soustraction qui donne ceci :
: <math>(n_1 + biais) - (n_2 + biais) = n_1 - n_2</math>
Or, le résultat correct serait :
: <math> ( n_1 - n_2 ) + biais </math>
Il faut rajouter le biais pour obtenir l'exposant correct.
On a donc besoin de deux additionneurs/soustracteurs : un pour additionner/soustraire les représentations binaires des opérandes, et un autre pour ajouter/retirer le biais en trop/manquant.
==L'additionneur BCD==
Maintenant, voyons un additionneur qui additionne deux entiers au format BCD. Pour cela, nous allons devoir passer par deux étapes. La première est de créer un circuit capable d'additionneur deux chiffres BCD. Ensuite, nous allons voir comment enchaîner ces circuits pour créer un additionneur BCD complet.
===L'additionneur BCD qui fait l'opération chiffre par chiffre===
Nous allons commencer par voir un additionneur qui additionne deux chiffres en BCD, une sorte d'équivalent BCD de l'additionneur complet. Il fournit un résultat sur 4 bits et une retenue qui est mise à 1 si le résultat dépasse 10 (la limite d'un chiffre BCD). Les deux opérandes sont des chiffres BCD codés sur 4 bits et sont additionnés en binaire par un additionneur des plus normaux, similaire à ceux vus plus haut. Le résultat est alors un entier codé en binaire, sur 5 bits, qu'on corrige/convertit pour obtenir un chiffre BCD et une retenue sortante.
Pour corriger le résultat, une idée intuitive serait de prendre le résultat et de faire une division par 10. Le quotient donne la retenue, alors que le reste est le résultat, le chiffre BCD. Mais un circuit diviseur par 10 utilise beaucoup de portes logiques, ce qui ne vaut pas le coup. Une autre méthode détecte si le résultat est égal ou supérieur à 10, ce qui correspond à un "débordement" (on dépasse les limites d'un chiffre BCD). Si le résultat est plus petit que 10, il n'y a rien à faire : le résultat est bon et la retenue est de zéro. Par contre, si le résultat vaut 10 ou plus, il faut corriger le résultat et générer une retenue à 1.
Il faut donc ajouter un circuit qui détecte si le résultat est supérieur à 9, qui calcule directement la retenue. Ce circuit peut se fabriquer simplement à partir de sa table de vérité, ou en utilisant les techniques que nous verrons dans un chapitre ultérieur sur les comparateurs. La solution la plus simple est clairement d'utiliser la table de vérité, ce qui est très simple, assez pour être laissé en exercice au lecteur. Pour comprendre comment corriger le résultat, établissons une table de vérité qui associe le résultat et le résultat corrigé. L'entrée vaut au minimum 10 et au maximum 9 + 9 = 18. On considère la sortie comme un tout, la retenue étant un 5ème bit, le bit de poids fort.
{|class="wikitable"
|-
! colspan="5" | Entrée
!
! rowspan="10" |
! Retenue
! Résultat corrigé (sans retenue)
! interprétation de la sortie en binaire (retenue inclue)
|-
| 0 || 1 || 0 || 1 || 0
| (10)
| 1 || 0000
| (16)
|-
| 0 || 1 || 0 || 1 || 1
| (11)
| 1 || 0001
| (17)
|-
| 0 || 1 || 1 || 0 || 0
| (12)
| 1 || 0010
| (18)
|-
| 0 || 1 || 1 || 0 || 1
| (13)
| 1 || 0011
| (19)
|-
| 0 || 1 || 1 || 1 || 0
| (14)
| 1 || 0100
| (20)
|-
| 0 || 1 || 1 || 1 || 1
| (15)
| 1 || 0101
| (21)
|-
| 1 || 0 || 0 || 0 || 0
| (16)
| 1 || 0110
| (22)
|-
| 1 || 0 || 0 || 0 || 1
| (17)
| 1 || 0111
| (23)
|-
| 1 || 0 || 0 || 1 || 0
| (18)
| 1 || 1000
| (24)
|}
En analysant le tableau, on voit que pour corriger le résultat, il suffit d'ajouter 6. La raison est que le résultat déborde d'un nibble à 16 en binaire, mais à 10 en décimal : il suffit d'ajouter la différence entre les deux, à savoir 6, et le débordement binaire fait son travail. Donc, la correction après une addition est très simple : si le résultat dépasse 9, on ajoute 6.
On peut maintenant implémenter l'additionneur BCD, en combinant le comparateur avec 10, le circuit de correction, et l'additionneur. La première solution calcule deux versions du résultat : la version corrigée, la version normale. Le choix entre les deux est réalisée par un multiplexeur, commandé par le comparateur.
[[File:Additionneur BCD.png|centre|vignette|upright=1.5|Additionneur BCD]]
L'autre solution utilise un circuit commandable qui soit additionne 6, soit ne fait rien. Le choix entre les deux est commandé par le bit calculé par le comparateur.
[[File:Additionneur BCD, seconde version.png|centre|vignette|upright=1.5|Additionneur BCD, seconde version.]]
Une version alternative du circuit précédent est la suivante. Il contient deux additionneurs : un pour additionner les deux chiffres BCD, un autre pour additionner 6 si besoin. Le résultat du comparateur est directement utilisé pour générer l'opérande du second additionneur : 0 ou 6. Le circuit est simple à concevoir, mais gaspille beaucoup de circuit. Idéalement, il vaudrait mieux utiliser un circuit combinatoire d'addition avec une constante.
[[File:Adder 4 Bit BCD.svg|centre|vignette|upright=2|Additionneur BCD, circuit complet.]]
Pour obtenir un additionneur BCD complet, il suffit d’enchaîner les additionneurs précédents, comme on le ferait avec les additionneurs complets dans un additionneur à propagation de retenue. Au final, l'additionneur BCD est beaucoup plus compliqué qu'un additionneur normal, car il rajoute un comparateur ">9", un petit additionneur pour ajouter 6 et éventuellement d'autres circuits. De plus, il est difficile d'appliquer les optimisations disponibles sur les additionneurs non-BCD. Notamment, les circuits d'anticipation de retenue sont totalement à refaire et le résultat est relativement compliqué. C'est ce qui explique pourquoi le BCD a progressivement été abandonné au profit du binaire simple.
La soustraction en BCD se fait comme en binaire : le nombre à soustraire est remplacé par son complément, le circuit additionne le complément et l'autre opérande, le débordement d'entier fait que le résultat marche. Sauf qu'ici, le complément est un complément à 9. Il se calcule chiffre par chiffre : chaque chiffre est remplacé par (9 - le chiffre en question).
===L'additionneur BCD par ajustement décimal===
L'additionneur BCD précédent effectuait son travail chiffre BCD par chiffre BCD, mais il existe des additionneurs BCD qui font autrement. Sur les premiers processeurs x86, il n'y avait pas d'opération d'addition BCD proprement dit, seulement une addition binaire normale de 8, 16 ou 32 bits. Par contre, elle était secondée par une opération dite d''''ajustement décimal''' qui transformait un nombre binaire en nombre codé en BCD. L'opération d'ajustement décimal prenait un opérande de 8 bits codé en binaire et fournissait un résultat de la même taille, c'est à dire deux chiffres BCD. Effectuer une addition BCD demandait donc de faire deux opérations à la suite : une addition binaire simple, suivie par l'opération d'ajustement décimal. Cela permettait de gérer des nombres entiers en binaire usuel et des entiers BCD sans avoir deux instructions d'addition séparées pour les deux, sans compter que cela simplifiait aussi les circuits d'addition.
L'ajustement décimal s'effectue en ajoutant une constante bien précise à l'opérande à convertir en BCD. L'idée est que la constante est découpée en morceaux de 4 bits, correspondant chacun à un chiffre BCD de l'opérande, chaque morceau contenant soit un 0, soit 6. Cela permet d'ajouter soit 0, soit 6, à chaque chiffre BCD, et donc de le corriger. La propagation des retenues d'un chiffre à l'autre est effectuée automatiquement par l'addition binaire de la constante. L'opération d'ajustement décimal calcule automatiquement la constante. Elle découpe l'opérande en ''nibbles'', vérifie si chaque ''nibble'' est supérieur ou égal à 10, puis détermine la valeur de chaque ''nibble'' de la constante finale. Par exemple, si je prends l'opérande 1001 1110, le ''nibble'' de poids faible déborde, alors que celui de poids fort non. La constante sera donc 0000 0110 : 0x06. Inversement, si le ''nibble'' de poids fort déborde et pas celui de poids faible, la constante sera alors 0x60. Et la constante est de 0x66 si les deux ''nibbles'' débordent, de 0x00 si aucun ne déborde.
Le circuit d’ajustement décimal est donc composé de trois étapes : deux étapes pour calculer la constante, et un circuit d'addition pour additionner cette constante au nombre de départ. La première étape découpe l'opérande en morceaux de 4 bits, en chiffres BCD, et vérifie si chacun d'entre eux vaut 10 ou plus. La seconde étape prend les résultats de la première étape, et les combine pour calculer la constante. Enfin, on trouve l'addition finale, qui était réalisée par un circuit d'addition utilisé à la fois pour l'ajustement décimal et l'addition binaire. La différence entre une addition normale et une opération d'ajustement décimal tient dans le fait que les deux premières étapes sont désactivées dans une addition normale.
[[File:Additionneur BCD parallèle.png|centre|vignette|upright=1.5|Additionneur BCD parallèle]]
===L'additionneur biquinaire===
Les entiers BCD ne sont qu'un des encodages hybrides entre décimal et binaire. L'encodage biquinaire est l'un d'entre eux et nous allons faire un rappel rapide à ce sujet. Pour simplifier, un chiffre encodé en biquinaire est composé de deux parties : un bit, couplé à une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits (0, 1, 2, 3 et 4). Le bit indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9.
Additionner deux nombres de biquinaire demande donc d'additionner deux parties quinaires encodées en ''one-hot'' et d'additionner deux bits. Mais attention : il faut tenir compte de la retenue de l'addition des parties quinaires. Et idéalement, il faut aussi tenir compte d'une retenue entrante, provenant de l'addition de la colonne de chiffres précédente. Toute la difficulté vient de la création de l'additionneur ''one-hot''. Heureusement, vu qu'il n'y a que 4-5 bits à additionner, il est souvent fabriqué à partir de sa table de vérité.
[[File:Additionneur bi-quinaire.png|centre|vignette|upright=2|Additionneur bi-quinaire]]
Un avantage du biquinaire est que le calcul du complément à 9 est très simple. Il faut pour cela : inverser la partie binaire avec une porte NON, puis inverser l'ordre des bits de la partie quinaire. Concrètement, le bit de poids faible devient le bit de poids fort, et ainsi de suite. Par exemple, une partie quinaire 01000 devient 00010, 10000 devient 00001, 00100 ne change pas, etc. Le tout peut se calculer avec une porte NON et 5 multiplexeurs.
===L'additionneur BCD avec calculs intermédiaires en biquinaire===
L'ordinateur IBM 1401, un ancien ''mainframe'' des années 60, utilisait un additionneur BCD un peu particulier. Les nombres étaient encodés en BCD dans la mémoire de l'ordinateur, mais les circuits de calcul utilisaient la représentation biquinaire. Lors d'un calcul, le processeur de l'ordinateur traduisait les chiffres BCD en représentation biquinaire, faisait une addition en biquinaire, avant de traduire le résultat en BCD normal.
Pour être précis, l'IBM 1401 utilisait une variante du biquinaire. L'encodage biquinaire de l'IBM 1401 est le suivant : la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre pair encodé par la partie quinaire. Si l'IBM 1401 utilisait cette variante du biquinaire, c'est car elle donnait des circuits de conversion BCD-biquinaire plus économes en portes logiques et plus rapides.
La partie binaire est le bit de poids faible du chiffre BCD, la partie biquinaire est calculée par un simple décodeur qui prend en entrée le chiffre BCD, amputé de son bit de poids faible. La traduction inverse demande d'utiliser un encodeur, à la place du décodeur. Par contre, le circuit d'addition biquinaire était plus compliqué du fait de la gestion des retenues. L'addition des parties binaires et quinaires se faisait en parallèle, dans deux additionneurs séparés. Cependant, l'addition des parties binaire fournit une retenue, qu'il faut prendre en compte. Pour cela, l'IBM 1401 disposait d'un troisième additionneur qui fournissait le résultat final, encodé en biquinaire.
[[File:Additionneur bi-quinaire de l'IBM 1401.png|centre|vignette|upright=2|Additionneur biquinaire de l'IBM 1401]]
Une implémentation moderne demanderait d'utiliser des portes ET combinées à des portes OU, le circuit pouvant être construit simplement à partir de sa table de vérité. Sur l'IBM 1401, le circuit était cependant différent, en raison de l'utilisation de OU câblés, des croisements de fils qui fonctionnent comme des portes OU, que nous n'avons pas encore vu pour le moment, mais qui seront détaillés dans quelques chapitres. Les OU câblés étaient utilisés pour simplifier le design du circuit, mais demandaient des portes logiques spécifiques, ce qui collait avec le fait que ce ''mainframe'' utilisait des transistors en Germanium. L'implémentation exacte est décrite dans cet article de blog, mais je ne recommande sa lecture qu'à ceux qui savent ce qu'est un OU câblé :
* [https://www.righto.com/2015/10/qui-binary-arithmetic-how-1960s-ibm.html Qui-binary arithmetic: how a 1960s IBM mainframe does math].
==Les débordements d'entier lors d'une addition/soustraction==
Les instructions arithmétiques manipulent des entiers codés sur un nombre fixe de bits, qui ne peuvent prendre leurs valeurs que dans un intervalle. Pour les nombres positifs, un ordinateur qui code ses entiers sur n bits pourra coder tous les entiers allant de 0 à <math>2^n -1</math>. Pour les nombres négatifs, l'intervalle est différent et dépend de la représentation utilisée. Dans le cas général, l'ordinateur peut coder les valeurs comprises de <math>N_\text{min}</math> à <math>N_\text{max}</math>. Si le résultat d'un calcul sort de cet intervalle, il ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math> , alors que la valeur basse vaut <math>N_\text{min} - 1</math> (avec <math>N_\text{max}</math> et <math>N_\text{min}</math> respectivement la plus grande et la plus petite valeur codable par l'ordinateur).
===La correction des débordements d'entier : l'arithmétique saturée===
Quand un débordement d'entier survient, tous les circuits de calcul ne procèdent pas de la même manière. Dans les grandes lignes, il y a deux réactions possibles : soit on corrige automatiquement le résultat du débordement, soit on ne fait rien et on se contente de détecter le débordement.
Si le débordement n'est pas corrigé automatiquement, le circuit ne conserve que les bits de poids faibles du résultat. Les bits en trop sont simplement ignorés. On dit qu'on utilise l''''arithmétique modulaire'''. Le problème avec ce genre d'arithmétique, c'est qu'une opération entre deux grands nombres peut donner un résultat très petit. Par exemple, si je dispose de registres 4 bits et que je souhaite faire l'addition 1111 + 0010 (ce qui donne 15 + 2), le résultat est censé être 10001 (17), ce qui est un résultat plus grand que la taille d'un registre. En conservant les 4 bits de poids faible, j’obtiens 0001 (1). En clair, un résultat très grand est transformé en un résultat très petit. Cela peut poser problèmes si on travaille uniquement avec des nombres positifs, mais c'est aussi utilisé pour coder des nombres en complément à deux.
D'autres circuits utilisent ce qu'on appelle l''''arithmétique saturée''' : si un calcul génère un débordement, on arrondi le résultat au plus grand entier supporté par le circuit. Les circuits capables de calculer en arithmétique saturée sont un peu plus complexes, vu qu'il faut rajouter des circuits pour corriger le résultat en cas de débordement. Il suffit généralement de rajouter un ''circuit de saturation'', qui prend en entrée le résultat et le corrige en cas de débordement. Ce circuit de saturation met la valeur maximale en sortie si un débordement survient, mais se contente de recopier le résultat du calcul sur sa sortie s'il n'y a pas de débordement. Typiquement, il est composé d'une couche de multiplexeurs, qui sélectionnent quelle valeur mettre sur la sortie : soit le résultat du calcul, soit le plus grand nombre entier géré par le processeur, soit le plus petit (pour les nombres négatifs/soustractions).
L'arithmétique saturée est utilisée pour les additions et soustractions, mais c'est plus rare pour les multiplications/divisions. Une des raisons est que le résultat d'une addition/soustraction prend un bit de plus que le résultat, là où les multiplications doublent le nombre de bits. Quand une addition déborde, le résultat réel est proche de la valeur maximale codable. mais quand une multiplication déborde, le résultat peut parfois valoir 200 à 60000 fois plus que la valeur maximale codable. Les calculs avec une valeur saturée/corrigée sont donc crédibles pour une suite d'additions, mais pas pour une suite de multiplications.
===La détection des débordements entiers===
Quand un débordement d'entier a eu lieu, il vaut mieux que l'additionneur prévienne ! Pour cela, l'additionneur a une '''sortie de débordement''', parfois nommée ''Overflow'', dont la valeur indique si l'addition a généré un débordement d'entier ou non. Reste que détecter un débordement ne se fait pas de la même manière selon que l'on parle d'un additionneur non-signé ou signé.
Pour les additionneur non-signés, l'additionneur calcule un bit de plus que ce qui est supporté par l'ordinateur. Par exemple, un additionneur 32 bits fournit un résultat sur 33 bits, un débordement d'entier a lieu quand le 33ème bit est à 1. Précisément, la sortie de débordement n'est autre que la retenue finale, celle fournie par le dernier additionneur complet. Le seul type de débordement possible est un débordement par le haut, où le résultat dépasse la valeur maximale. Avec l'arithmétique saturée, le circuit de saturation consiste en une seule couche de multiplexeurs, voire en un circuit de mise à la valeur maximale tel que vu dans le chapitre sur les opérations bits à bits.
[[File:Gestion des débordements d'entiers.png|centre|vignette|upright=2.5|Gestion des débordements d'entiers lors d'une addition non-signée.]]
Pour les additionneurs non-signés, la gestion des débordements d'entiers dépend fortement de la représentation signée. Nous allons étudier le cas du complément à deux. Si vous vous rappelez le chapitre 1, les calculs sur des nombres en complètement à deux utilisent les règles de l'arithmétique modulaire, c'est une condition nécessaire. À priori, on peut penser que dans ces conditions, les débordements d'entiers sont une chose parfaitement normale, qui nous permet d'avoir des résultats corrects. Néanmoins, certains débordements d'entiers peuvent survenir malgré tout et produire des bugs assez ennuyeux.
Si l'on tient en compte les règles du complément à deux, on sait que le bit de poids fort (le plus à gauche) permet de déterminer si le nombre est positif ou négatif : il indique le signe du nombre. Tout se passe comme si les entiers en complément à deux étaient codés sur un bit de moins, et avaient leur longueur amputé du bit de poids fort. Si le résultat d'un calcul écrase le bit de poids fort, il y a un débordement d'entiers. Il existe une règle simple qui permet de détecter ces débordements d'entiers. L'addition de deux nombres positifs ne peut pas être un nombre négatif. Si on additionne deux nombres dont le bit de signe est à 0 et que le bit de signe du résultat est à 1, on est en face d'un débordement d'entiers. Même chose pour deux nombres négatifs : le résultat de l'addition ne peut pas être positif. On peut résumer cela en une phrase : si deux nombres de même signe sont ajoutés, un débordement a lieu quand le bit du signe du résultat a le signe opposé.
Modifier les circuits d'au-dessus pour qu'ils détectent les débordements en complément à deux est simple comme bonjour : il suffit créer un petit circuit combinatoire qui prenne en entrée les bits de signe des opérandes et du résultat, et qui fasse le calcul de l'indicateur de débordements. Si l'on rédige sa table de vérité, on doit se retrouver avec la table suivante :
{|class="wikitable"
|-
!Entrées
!Sortie
|-
|000||0
|-
|001||1
|-
|010||0
|-
|011||0
|-
|100||0
|-
|101||0
|-
|110||1
|-
|111||0
|}
L'équation de ce circuit est la suivante, avec <math>S_a</math> et <math>S_b</math> les signes des deux opérandes, et <math>C_i</math> la retenue de la colonne précédente :
: <math> ( S_a . S_b . \overline{C_i} ) + ( \overline{S_a} . \overline{S_b} . C_i )</math>
En simplifiant, on obtient alors :
: <math>( S_a . S_b ) \oplus C_i</math>
Or, il se trouve que <math>S_a . S_b</math> est tout simplement la retenue en sortie du dernier additionneur, que nous noterons <math>C_o</math>. On trouve donc :
: <math>C_o \oplus C_i</math>
Il suffit donc de faire un XOR entre la dernière retenue et la précédente pour obtenir le bit de débordement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de décalage et de rotation
| prevText=Les circuits de décalage et de rotation
| next=Les circuits de comparaison
| nextText=Les circuits de comparaison
}}
</noinclude>
a2vt0slgedq0mzbf80ydspeb09efuq7
768666
768665
2026-06-25T20:00:43Z
Mewtow
31375
/* Les additionneurs à anticipation de retenue multi-niveau */
768666
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir les circuits capables de faire une addition ou une soustraction, ainsi que quelques circuits spécialisés. Précisons cependant que les fabricants de processeurs travaillent d'arrache-pied pour trouver des moyens de rendre ces circuits de calcul plus rapides et plus économes en énergie. Autant vous dire que les circuits que vous allez voir sont vraiment des circuits qui font pâle figure comparé à ce que l'on peut trouver dans un vrai processeur commercial !
==Les circuits pour additionner 2 ou 3 bits==
L'addition se fait en binaire de la même manière qu'en décimal. On additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. La soustraction fonctionne sur le même principe, sur le même modèle qu'en décimal.
[[File:Binary Addition Demonstration.svg|centre|vignette|Exemple d'addition en binaire.]]
En clair, additionner deux nombres demande d'additionner 2 bits et une retenue sur chaque colonne, et de propager les retenues d'une colonne à l'autre. La propagation des retenues est quelque chose de simple en apparence, mais qui est sujet à des optimisations extraordinairement nombreuses. Aussi, pour simplifier l'exposition, nous allons voir comment gérer une colonne avant de voir comment sont propagées les retenues.
[[File:Full Adder Block.svg|vignette|upright=1|class=transparent|Additionneur complet.]]
Si on effectue une addition en colonne, on doit additionner les deux bits sur la colonne, mais aussi additionner une éventuelle retenue. Il faut donc créer un circuit qui additionne trois bits : deux bits de données, plus une retenue. Ce circuit qui additionne trois bits est appelé un '''additionneur complet'''. Il fournit en sortie deux bits : un bit de somme et une retenue sortante. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|-
||0||0||0|| ||0||0
|-
||0||0||1|| ||0||1
|-
||0||1||0|| ||0||1
|-
||0||1||1|| ||1||0
|-
||1||0||0|| ||0||1
|-
||1||0||1|| ||1||0
|-
||1||1||0|| ||1||0
|-
||1||1||1|| ||1||1
|}
Il est possible d'utiliser un tableau de Karnaugh pour traduire la table de vérité, mais elle donne un résultat assez compliqué. La retenue et le bit de somme sont calculés à part. La retenue est calculée avec trois portes ET et une porte OU. Le bit de somme est calculé lui avec 4 portes ET, une porte OU, et trois portes NON.
[[File:Full Adler with 3 entries.png|centre|vignette|upright=2|Additionneur complet, fabriqué à partir de la table de vérité.]]
D'autres méthodes donnent des résultats plus compréhensibles. Nous allons les voir dans la suite de cette section.
===L'additionneur complet basé sur des demi-additionneurs===
[[File:1-bit half-adder.svg|class=transparent|vignette|Demi-addtionneur.]]
Nous avons déjà vu comment additionner deux bits dans le chapitre sur les incrémenteurs, mais quelques rappels ne seront pas de trop. Le '''demi-additionneur''' est un circuit qui additionne deux bits. Il implémente la table d'addition, qui est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Mais un demi-additionneur n'additionne que deux bits, il manque de quoi additionner la retenue. Une solution pour cela est d'enchaîner deux demi-additionneurs : un qui additionne les deux bits de données, et un second qui additionne la retenue au résultat du précédent. La retenue finale se calcule en combinant les sorties de retenue des deux demi-additionneurs, avec une porte OU. Le résultat est un additionneur complet fabriqué avec deux demi-additionneurs. Pour vous en convaincre, établissez la table de vérité de ce circuit, vous verrez que ça marche.
[[File:Full Adder Blocks.svg|centre|vignette|upright=2|Additionneur complet fabriqué avec deux demi-additionneurs.]]
[[File:Half-adder.svg|vignette|upright=1|Circuit d'un demi-additionneur naïf.]]
L'implémentation naïve d'un demi-additionneur utilise une porte XOR et une porte ET, comme illustré ci-contre. Le circuit s'obtient en faisant uen traduction littérale de la table de vérité du circuit. Si je dis qu'elle est naïve, c'est car il est possible de l'optimiser, de manière à éliminer des redondances cachées. Mais nous avions déjà vu cela dans le chapitre sur les incrémenteurs, aussi laissons cela pour plus tard.
En combinant deux demi-additionneurs de ce type, on obtient l'additionneur complet suivant. Les deux sorties de retenue des demi-additionneurs sont combinées avec une porte OU, pour calculer la retenue finale.
{| class="flexible"
|[[File:Full Adder Modules.svg|class=transparent|Composition d'un additionneur complet. On voit bien que celui-ci est composé de deux demi-additionneurs, en rouge et en bleu, auxquels on a ajouté une porte OU pour calculer la retenue finale.]]
|[[File:Full-adder.svg|300px|class=transparent|Circuit d'un additionneur complet.]]
|}
Le fait de combiner les deux retenues avec une porte OU n'est pas ce qu'il y a de plus intuitif. La table de vérité nous dit que ça fonctionne, mais on comprend mal pourquoi. Mais tout devient plus clair quand on sait que les deux retenues ne peuvent pas être à 1 en même temps. Même dans l'addition 1 + 1 + 1, seule une retenue est à 1. Soit l'addition des deux bits d'opérande donne naissance à une retenue, soit c'est l'addition ''résultat + retenue entrante''. La retenue sortant est donc à 1 quand une des deux addition donne une retenue. D'où l'usage d'une porte OU. Remarquez qu'une porte XOR donne le même résultat, vu que le cas où les deux retenues sont à 1 n'est jamais rencontré.
===La propagation et la génération des retenues===
L'additionneur complet que nous allons voir dans cette section sert à introduire les concepts de propagation, génération et absorption de retenue. Il s'agit de concepts très importants quand on étudie les additionneurs. l'idée part d'un principe très simple : la retenue sortante dépend de la retenue d'entrée. La relation entre les deux se résume à trois cas, qui dépendent de la valeur des deux bits additionnés, nommés A et B.
* Dans le premier cas, la retenue entrante est égale à la retenue sortante. On dit que la retenue entrante est propagée par l'additionneur.
* Dans le second cas, la retenue est forcée à 0 : la retenue sortante vaut 0, peu importe la valeur de la retenue entrante.
* Dans le troisième cas, la retenue est forcée à 1 : la retenue entrante vaut 1, peu importe la valeur de la retenue entrante.
[[File:Signaux P et G fournis par un demi-additionneur.png|vignette|Signaux P et G fournis par un demi-additionneur]]
Maintenant, créons un circuit qui nous dise si une retenue est propagée ou générée. Il a deux bits de sortie, nommés P et G : P pour ''Propagate'', G pour ''Generate''. Le bit P est à 1 si la retenue entrante est propagée, il est à 0 sinon. Le bit G est à 1 si une retenue est générée, à 0 sinon. Une retenue est considérée comme absorbée si elle n'est pas ni propagée ni générée, pas besoin d'un troisième bit pour gérer ce cas.
Il se trouve que ce circuit n'est autre qu'un demi-additionneur ! Pour vous en rendre compte, regardez la table de vérité d'un additionneur complet, illustrée ci-dessous. Lorsque les deux bits d'opérande sont à 0, la retenue sortante vaut toujours 0. Si ils sont tous deux à 1, alors la retenue sortante vaut 1. S'ils sont différents, alors retenues sortante et entrante sont égales. Le bit P est donc généré par une simple porte XOR. Quant au bit G, il est à 1 si les deux bits d'opérandes sont à 1, ce qui correspond à une porte ET. Il se trouve que ces deux portes forment un demi-additionneur !
{|class="wikitable"
|-
! Opérande 1 !! Opérande 2 !! Retenue entrante !! !! Retenue sortante
|- class="f_rouge"
||0||0||0|| ||0
|- class="f_rouge"
||0||0||1|| ||0
|- class="f_bleu"
||0||1||0|| ||0
|- class="f_bleu"
||0||1||1|| ||1
|- class="f_bleu"
||1||0||0|| ||0
|- class="f_bleu"
||1||0||1|| ||1
|- class="f_vert"
||1||1||0|| ||1
|- class="f_vert"
||1||1||1|| ||1
|}
Ensuite, créons un circuit qui prend ces deux sorties P et G, et calcule la retenue sortante en fonction. Le circuit en question a trois entrées : la retenue entrante, les deux bits P et G. La retenue sortante vaut 1 soit si une retenue est générée, soit si la retenue entrante est propagée et qu'elle vaut 1. En notant <math>{C_{in}}</math> et <math>{C_{out}}</math> les retenues entrantes et sortante, on a :
: <math>{C_{out}} = G + \left( P . C_{in} \right)</math>
Le circuit est donc composé d'une porte OU et d'une porte ET. En combinant un demi-additionneur avec le circuit de calcul de retenue sortante vu plus haut, on a :
[[File:Additionneur complet avec propagation et génération de retenue.png|class=transparent|centre|vignette|upright=2|Additionneur complet avec propagation et génération de retenue.]]
Certes, on retombe sur le circuit vu plus haut. Mais il est possible de remplacer le circuit en orange par un autre. Par exemple, il est possible de le remplacer par multiplexeur, qui choisit entre la retenue générée et la retenue entrante (propagée). Le choix se fait selon la valeur du bit P, qui chosiit entre propager la retenue et la générer.
[[File:Additionneur crée avec un multiplexeur.png|centre|vignette|upright=1.5|Additionneur crée avec un multiplexeur]]
Le circuit exact est illustré ci-dessous. Il semble utiliser plus de portes logiques que l'additionneur complet précédent. Cependant, nous verrons dans quelques chapitres qu'il est possible d'implémenter un multiplexeur avec seulement 6 transistors, voire moins ! L'implémentation utilise des portes à transmission, mais nous en reparlerons dans le chapitre sur les transistors, quand nous verrons les additionneurs à ''Manchester Carry Chain''. Au passage, une variante de ce circuit a été utilisée dans le processeur processeur 8086 d'Intel, comme on le verra dans le chapitre suivant.
[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
[[File:FulladderPG.png|vignette|upright=1|Additionneur complet avec deux sorties P et G.]]
Pour finir, sachez qu'il existe des additionneurs qui fournissent : le bit de résultat, deux sorties P et G qui indiquent si l'addition propage ou génère une retenue. Il ne s'agit pas d'additionneurs complets, car il manque de quoi calculer la retenue sortante. De tels additionneurs seront appelés des '''additionneurs partiels'''. De tels additionneurs sont utilisés dans certains additionneurs pour gagner en performance.
Enfin, il existe aussi ce que j'ai décidé d'appeler des '''additionneurs P/G''', qui sont des additionneurs complets auxquels on a ajouté deux sorties P et G, en plus de la sortie de retenue. Leur circuit est le même que celui d'un additionneur complet, auquel on a ajouté deux fils. Vu que ces deux sorties sont fournies par le premier demi-additionneur, ajouter ces deux sorties demande d'ajouter des fils, pas de portes logiques. De tels additionneurs sont aussi utilisés dans certains additionneurs pour gagner en performance.
===L'additionneur complet basé sur une porte à majorité===
Maintenant, voyons une dernière implémentation possible de l'additionneur complet. Mais avant de voir comment l'additionneur est implémenté, nous allons devoir faire un petit focus sur la retenue sortante. Reprenons la table de vérité, et regardons ce qu'il en est pour la retenue sortante uniquement.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante
|-
||0||0||0|| ||0
|-
||0||0||1|| ||0
|-
||0||1||0|| ||0
|-
||0||1||1|| ||1
|-
||1||0||0|| ||0
|-
||1||0||1|| ||1
|-
||1||1||0|| ||1
|-
||1||1||1|| ||1
|}
Vous remarquerez que la retenue sortante est égale au bit majoritaire, parmi les trois bits d'opérande. Si deux ou trois bits sont à 1, la retenue sortante vaut 1. Et réciproquement avec 0. Le circuit de calcul de la retenue peut donc être remplacé par une '''porte à majorité'''.
[[File:Additionneur crée avec une porte à majorité.png|centre|vignette|upright=2|Additionneur crée avec une porte à majorité]]
Mais une porte à majorité est assez gourmande en circuit, ce qui fait qu'un additionneur pareil ne serait pas pratique, ni utile. Du moins, ce serait le cas s'il n'y avait pas une possibilité d'optimisation extrêmement intéressante. Il est possible de calculer le 'bit de somme' à partir de la retenue sortante ! En effet, le bit de somme est l'inverse de la retenue sortante, sauf dans deux cas : les trois bits d'entrée sont à 0, où ils sont tous à 1. Le bit de somme vaut 0 dans le premier cas, 1 dans le second cas.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|- class="f_bleu"
||0||0||0|| ||0||0
|- class="f_vert"
||0||0||1|| ||0||1
|- class="f_vert"
||0||1||0|| ||0||1
|- class="f_vert"
||0||1||1|| ||1||0
|- class="f_vert"
||1||0||0|| ||0||1
|- class="f_vert"
||1||0||1|| ||1||0
|- class="f_vert"
||1||1||0|| ||1||0
|- class="f_rouge"
||1||1||1|| ||1||1
|}
En clair, 6 lignes de la table de vérité sur 8 peuvent se calculer avec une porte à majorité, le reste demandant quelques portes logiques pour faire la correction. L'implémentation en circuit demande donc trois choses :
* de calculer la retenue sortante et de quoi l'inverser ;
* un circuit qui vérifie si tous les bits opérande valent 0 : une porte NOR ou OU fait l'affaire ;
* un circuit qui vérifie s'ils valent tous 1, à savoir une porte ET.
Ensuite, on combine le résultat des trois circuits précédents pour obtenir le résultat final. La combinaison est le fait du circuit en jaune dans le schéma ci-dessous.
[[File:Full adder basé sur une modification de la retenue.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue]]
Le circuit en jaune est assez simple. Quand la porte ET sort un 1, la sortie doit être forcée à 1. Par contre, quand la porte OU sort un zéro, la sortie doit être forcée à 0. Il suffit donc d'enchainer un circuit de mise à 1 et un circuit de mise à 0. Nous avons vu dans le chapitre sur les opérations de masquage qu'il s'agit respectivement d'une porte OU et d'une porte ET.
[[File:Full adder basé sur une modification de la retenue interne, au complet.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue interne, au complet]]
Divers processeurs utilisaient des additionneurs complets de ce type : le fameux Z80, l'Intel 4004 et de l'Intel 8008 sont les exemples les plus notables. Pourtant, on pourrait s'étonner qu'un tel circuit ait existé. Il utilise beaucoup de portes logiques, a une profondeur logique supérieure : il n'a rien d'avantageux. Sauf qu'il était utilisé sur d'anciens processeurs, qui utilisaient la technologie dite TTL, différente de la technologie CMOS des transistors modernes. Et avec la technologie TTL, il est possible de fusionner plusieurs portes logiques ET et NOR en une seule porte logique ET/OU/NON ! Un additionneur complet construit ainsi ne prenait que deux portes logiques : une pour le calcul de la retenue sortante, une autre pour le reste du circuit.
==L'addition non signée==
Voyons maintenant un circuit capable d'additionner deux nombres entiers: l''''additionneur'''. Dans la version qu'on va voir, ce circuit manipulera des nombres strictement positifs. L'addition des nombres codés en complètement à deux sera vu dans une section ultérieure.
===L'additionneur série===
Il est possible d'additionner deux nombres bit par bit, grâce à un additionneur complet associé à plusieurs registres à décalages. Les opérandes sont placées chacune dans un registre à décalage, et l'additionneur complet utilise les bits sortants de ces registres à décalage. Le bit du résultat est envoyé au registre à décalage pour le résultat. La retenue de l'addition est stockée dans une bascule de 1 bit, en attente du prochain cycle d'horloge. Un tel additionneur est appelé un '''additionneur série'''. Il a été utilisé sur d'anciens ordinateurs dans les années 50-60, aussi bien des prototypes que des ordinateurs commerciaux.
[[File:Additionneur série.jpg|centre|vignette|upright=2.5|Additionneur série.]]
===L'additionneur à propagation de retenue===
L''''additionneur à propagation de retenue''' est aussi appelé l'additionneur ''ripple carry'', c'est son nom anglais. J'utiliserais occasionnellement le nom anglais dans ce qui suit, car celui-ci est plus court. Ilutilise un additionneur complet pour chaque colonne de l'addition. Pour gérer les retenues, il suffit ainsi de câbler des additionneurs complets les uns à la suite des autres, chacun envoyant sa retenue à la colonne suivante.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Notons la présence de la retenue sortante, qui est utilisée pour détecter les débordements d'entier, ainsi que pour d'autres opérations. Le bit de retenue final est souvent stocké dans un registre spécial du processeur (généralement appelé ''carry flag'').
Notez aussi, à droite du schéma précédent, l'entrée de retenue <math>\text{R}_\text{Entrée}</math>. Elle est très utile pour l'implémentation de certaines opérations comme l'inversion de signe, la soustraction, l'incrémentation, etc. Certains processeurs sont capables de faire une opération appelée ADC, ADDC ou autre nom signifiant ''Addition with Carry'', qui permet de faire le calcul A + B + Retenue (la retenue en question est la retenue sortante de l'addition précédente, stockée dans le registre ''carry flag''). Son utilité principale est de permettre des additions d'entiers plus grands que ceux supportés par le processeur. Par exemple, cela permet de faire des additions d'entiers 32 bits sur un processeur 16 bits.
[[File:Adder Network 3 Bit Sum Delay 003.svg|vignette|upright=0.5|Propagation de retenue dans l'additionneur.]]
Cet additionneur utilise très peu de portes logiques, ce qui fait qu'il était utilisé sur certains processeurs 8 et 16 bits, dont le budget en portes logiques était limité. Bien que très simple, cet additionneur est cependant peu performant. Son temps de calcul est proportionnel à la taille des opérandes. Par exemple, additionner deux opérandes de 32 bits prendra deux fois plus de temps que pour des opérandes de 16 bits. La raison est que les retenues doivent se propager à travers le circuit, du premier additionneur jusqu'au dernier.
L'addition étant une opération fréquente, d'autres additionneurs ont été inventés. Ils utilisent des optimisations qui utilisent plus de circuits pour gagner en rapidité. Mais avant de voir ces optimisations, nous allons voir s'il est possible d'optimiser les additionneurs à propagation de retenue.
===L'additionneur à saut de retenue===
L'additionneur à propagation de retenue peut être optimisé de deux manières. La première accélère la propagation de la retenue, en agissant au niveau des additionneurs complets. La ''Manchester carry chain'' est une optimisation de ce type, mais on ne peut pas encore expliquer à ce stade du cours, car elle optimisait les additionneurs complets au niveau des transistors eux-mêmes. Elle a été utilisée sur de nombreux processeurs connus, comme le 8086 d'Intel ou le Z80.
La seconde manière rend l'addition plus rapide dans certains cas. Le résultat est un additionneur dont le temps de calcul est variable. Le calcul prendra quelques cycles d'horloges avec certains opérandes, mais d'autres opérandes ne verront aucune amélioration. Il n'améliore pas le pire des cas, dans lequel la retenue doit être propagée du début à la fin, du bit de poids faible au bit de poids fort. Mais dans d'autres cas, il permet d'avoir le résultat en avance. L'optimisation en question donne l''''additionneur à saut de retenue''' (''carry-skip adder'').
Il est composé de blocs qui additionnent 4/5 bits, rarement plus. La taille d'un bloc varie d'un additionneur à l'autre, elle peut même varier dans l'additionneur, mais nous allons prendre un exemple où les blocs font 4 bits. L'idée est de détecter précocement si la retenue entrante est propagée à travers tout le bloc. Si la retenue est propagée dans tous le bloc, on peut directement l'envoyer sur la sortie de retenue, la retenue saute le bloc entier. Dans le cas contraire, on doit calculer la retenue normalement. Le choix entre les deux est le fait d'un multiplexeur. Toute la difficulté est de savoir comment commander le multiplexeur.
[[File:Carry skip adder, principe de base.png|centre|vignette|upright=2.5|Carry skip adder : principe de base]]
Pour savoir si une retenue est propagée dans un bloc entier, il faut utiliser des additionneurs complets, qui ont en plus une sortie P indiquant que l'additionneur propage la retenue. Le bloc propage une retenue si chaque additionneur complet propage la retenue, donc s'ils ont tous leur sortie P à 1. Un simple ET logique suffit à déterminer si c'est le cas. Le signal de commande du multiplexeur est donc un vulgaire ET entre toutes les sorties P des additionneurs complets du bloc.
[[File:Additionneur à saut de retenue.png|centre|vignette|upright=2.5|Calcul de la commande du MUX.]]
Voici ce que ça donne pour un bloc de 4 bits :
[[File:CSAdder4Bit.svg|centre|vignette|upright=1.5|Additionneur ''carry skip'' : bloc de 4 bits.]]
L'additionneur à saut de retenue est construit en assemblant plusieurs blocs de ce type.
[[File:BCSAdder16Bit.svg|centre|vignette|upright=3|Additionneur à saut de retenue.]]
===L'additionneur à sélection de retenue===
L''''additionneur à sélection de retenue''' utilise aussi des blocs, comme les additionneurs précédents. L'addition se fait en deux versions : une avec la retenue du bloc précédent valant zéro, et une autre version avec la retenue du bloc précédent valant 1. Il suffira alors de choisir le bon résultat avec un multiplexeur, une fois cette retenue connue. On gagne ainsi du temps en calculant à l'avance les valeurs de certains bits du résultat, sans connaître la valeur de la retenue. Petit détail : sur certains additionneurs à sélection de retenue, les blocs de base n'ont pas la même taille. Cela permet de tenir compte des temps de propagation des retenues entre les blocs.
[[File:Additionneur à sélection de retenue.png|centre|vignette|upright=1.5|Additionneur à sélection de retenue avec seulement deux blocs.]]
Dans les exemples du dessus, chaque sous-additionneur étaient des additionneurs à propagation de retenue. Mais ce n'est pas une obligation, et tout autre type d’additionneur peut être utilisé. Par exemple, on peut faire en sorte que les sous-additionneurs soient eux-mêmes des additionneurs à sélection de retenue, et poursuivre ainsi de suite, récursivement. On obtient alors un '''additionneur à somme conditionnelle''', plus rapide que l'additionneur à sélection de retenue, mais qui utilise beaucoup plus de portes logiques.
===Les additionneurs à anticipation de retenue===
Les '''additionneurs à anticipation de retenue''' calculent chaque retenue sans avoir à propager les retenues précédentes. Au lieu de calculer les retenues une par une, ils calculent toutes les retenues en parallèle, à partir des bits des opérandes. Une fois les retenues pré-calculées, il suffit de les additionner avec les deux bits adéquats, pour obtenir le résultat. Ces additionneurs sont composés de deux parties :
* un circuit qui pré-calcule les retenues, sans les propager, directement à partir des opérandes ;
* d'une couche d'additionneurs complets, qui additionnent chacun deux bits d'opérande et la retenue pré-calculée.
[[File:Additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Additionneur à anticipation de retenue.]]
Le calcul des retenues se fait en deux étapes. La première détermine si les retenues sont propagées ou générées, pour chaque colonne. Elle détermine, pour chaque colonne, les bits P et G qui indiquent respectivement : qu'une retenue a été générée sur cette colonne, que la colonne propage la retenue précédente. La seconde étape utilise ces signaux P et G pour déterminer la retenue sur chaque colonne. Le circuit de calcul des retenues est donc composé de deux parties : une couche de demi-additionneurs pour générer les signaux P et G, un circuit de calcul des retenues proprement dit.
[[File:Circuit complet d'un additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Circuit complet d'un additionneur à anticipation de retenue.]]
Pour rappel, la retenue de la colonne i, notée <math>C_i</math> est égale à :
: <math>G_i + (P_i . C_{i-1})</math>, avec G le signal de génération de retenue, P le signal de propagation de retenue.
Si on utilisait cette formule sans trop réfléchir, on retomberait sur un additionneur à propagation de retenue inutilement compliqué. L'astuce des additionneurs à anticipation de retenue consiste à remplacer le terme Ci−1 par sa valeur calculée avant. Par exemple, je prends un additionneur 4 bits. Je dispose de deux nombres A et B, contenant chacun 4 bits : A3, A2, A1, et A0 pour le nombre A, et B3, B2, B1, et B0 pour le nombre B. Si j'effectue les remplacements, j'obtiens les formules suivantes :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Ces formules nous permettent de déduire la valeur d'une retenue directement : il reste alors à créer un circuit qui implémente ces formules, et le tour est joué.
[[File:Four bit adder with carry lookahead.svg|centre|vignette|upright=1.5|Additionneur à anticipation de retenue de 4 bits.]]
Plus haut, j'ai dit que le circuit était composé de trois couches : une couche de demi-additionneur pour les signaux P et G ; un circuit d'anticipation de retenue, puis l'addition finale. Mais les deux bits d'opérandes sont déjà additionnés dans la couche de demi-additionneur, autant réutiliser le résultat pour l'additionner à la retenue. Pour éliminer toute redondance de ce type, il est possible d'utiliser des additionneurs partiels (des additionneurs où la sortie de retenue a été remplacée par deux sorties P et G). L'additionneur final est alors composé de plusieurs additionneurs complets de ce type, couplé à un circuit d'anticipation de retenue.
[[File:Cla4bits.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue de 4 bits.]]
Les additionneurs à anticipation de retenue sont plus rapides que les additionneurs à propagation de retenue. La raison est que les retenues sont calculées rapidement, dans un temps plus petit qu'en propageant la retenue. Pour rentrer dans les détails mathématiques, le temps de calcul n'est pas proportionnel au nombre de bits, mais proportionnel au logarithme de ce nombre de bits. Par contre, le gain en temps de calcul s'accompagne d'un cout en portes logiques conséquent. Le circuit qui calcule les retenues anticipées utilise beaucoup de portes logiques.
===Les additionneurs à calcul parallèle de préfixe===
Du fait du cout en portes logiques, utiliser un additionneur à anticipation de retenue est une solution pratique pour additionner 4 ou 8 bits. Mais pour des opérandes plus grands, de 16 ou 32 bits, cela utiliserait trop de portes logiques. Une partie de ce cout en portes logiques est cependant lié à des redondances. Si on analyse deux retenues différentes, à des colonnes différentes. Les circuits qui calculent ces deux retenues seront différents, mais ils contiendront des sous-circuits identiques. Pour vous en rendre compte, reprenons les quatre équations obtenues précédemment et essayez de trouver des redondances :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Vous devriez trouver :
* le terme ''P0 · C0'' dans toutes les lignes ;
* les termes ''P1 · P0 · C0'' et ''P1 · P0'' dans les trois dernières lignes ;
* le terme ''P2 · P1 · P0 · C0'' ; ''P2 · P1 · G0'' et ''P2 · G1'' dans les deux dernières lignes.
Il existe plusieurs manières d'exploiter de telles redondances, exploitées dans les '''additionneurs à calcul parallèle de préfixe'''. L'optimisation apportée est de générer des signaux ''propagate'' et ''generate'' pour des groupes de 2, 3, 4, ..., N colonnes. Par exemple, il est possible de générer un signal ''P 0 vers 3'', qui indique si la retenue de la première colonne est propagée jusqu'à la 4ème colonne. Un autre exemple est un signal qui indique qu'une retenue a été générée entre les colonnes 4 à 7 génèrent une retenue ou non. En clair, les signaux P et G ont maintenant un intervalle, qui précise de quelle colonne vers quelle colonne se fait la propagation, ou entre quelles colonnes se fait la génération.
Il est possible de combiner les signaux P et G de deux groupes de colonne, s'ils sont contiguës (ils peuvent aussi se recouvrir). Par exemple, il est possible de calculer les bits P et G pour les colonnes 0 à 10, à partir des deux signaux P/G des colonnes 0-4 et 5-10. Pour cela, les équations sont assez simples. Si je prends deux groupes nommés A et B, avec A pour les colonnes de poids fort et B celles de poids faible, on a :
: <math>P_{sortie} = P_A . P_B</math>
: <math>G_{sortie} = G_A . P_A . G_B</math>
Pour les bits des colonnes 2, 4, 8, 16, 32, 64, et autres, le calcul est simple. L'idée est de grouper les colonnes par groupes de 2. Puis, on calcule chaque groupe de 4 bits à partir de deux groupes de 2. Puis les groupes de 8 bits sont calculés à partir de deux groupes de 4 bits, et ainsi de suite pour toute puissance de deux.
[[File:Squelette d'un additionneur à calcul parallèle de préfixe.jpg|centre|vignette|upright=2|Squelette d'un additionneur à calcul parallèle de préfixe]]
Les seules difficultés surviennent pour les colonnes dont le nombre n'est pas une puissance de deux. Il y a plusieurs manières pour subdiviser ces colonnes en intervalles plus petits et combiner le tout. Et elles donnent chacune des additionneurs différents. Ils portent les noms d'''additionneur de Ladner-Fisher'', d'''additionneur de Brent-Kung'', d'''additionneur de Kogge-Stone'', etc. Dans le cas général, l'additionneur de Kogge-Stone est un des plus rapide, mais aussi un des plus gourmand en circuits, alors que c'est l'inverse pour l'additionneur de Brent-Kung. Les deux additionneurs sont illustrés ci-dessous, pour des opérandes de 8 bits. Dans ceux-ci, chaque losange calculent les bits P et G, à partir des signaux P et G de deux groupes précédents.
{|
|[[File:Brent-kung-8-bit.png|vignette|Brent-kung-8-bit]]
|[[File:Kogge-stone-8-bit.png|vignette|upright=1.2|Kogge-stone-8-bit]]
|}
===Le calcul parallèle de la retenue===
L'anticipation de retenue est très rapide, mais utilise beaucoup de circuits, même en utilisant le calcul parallèle de préfixe. Les additionneurs à propagation de retenue font eux le compromis inverse. Mais il existe des intermédiaires, qui visent à obtenir un compromis entre performance et cout en portes logiques.
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Un de ces compromis découpe un additionneur de 16/32/64 bits en additionneurs de 4/5 bits, qui sont enchainés en utilisant la propagation de retenue. Par contre, ces petits additionneurs de 4/5 bits utilisent l'anticipation de retenue. Ces additionneurs simples seront nommés ''blocs'' dans ce qui suit, et l'un d'entre eux est illustré ci-contre. Vous remarquerez qu'un bloc a une retenue entrante et une retenue sortante. Par exemple, on peut utiliser 4 additionneurs à anticipation de retenue de 4 bits, et propager les retenues entre eux. Le premier bloc calcule une retenue, qui est propagée au second bloc. Le second bloc calcule lui aussi une retenue, qui est propagée au troisième bloc, etc.
[[File:4008 Functional Diagram.svg|vignette|upright=1|4008 Functional Diagram]]
Le gain en performance est significatif, mais il est possible de faire presque aussi rapide, tout en économisant beaucoup de circuits. L'idée est de simplifier les additionneurs 4 bits, afin de ne pas utiliser une anticipation de retenue complète. L'anticipation de retenue n'est utilisée que pour la retenue sortante, alors que les bits de somme sont calculées avec propagation de retenue. La retenue sortante est donc calculée en parallèle de l'addition. Chaque bloc contient, à côté d'un additionneur proprement dit, un circuit qui calcule la retenue sortante. Il existe de nombreuses manières de calculer la retenue sortante.
Le gain en portes logiques est assez significatif. En effet, on se passe de circuits d'anticipation de retenue pour les 4 bits de somme, on ne garde que celui pour la retenue sortante. Le fait d'utiliser la propagation de retenue pour calculer les 4 bits de somme est censé avoir un léger cout en performance, mais il se trouve que ce cout est très faible. En effet, le résultat n'est connu qu'une fois les retenues propagées jusqu'au dernier bloc. Et malgré l'anticipation des retenues, la propagation est assez lente. les additionneurs à propagation de retenue auront terminé leur travail bien avant.
Un exemple est celui de l'additionneur CMOS 4008, un additionneur de 4 bit. Il est composé en trois sections. Une première couche de demi-additionneurs calcule les signaux P et G utilisés à la fois pour l'anticipation de la retenue sortante, que pour calculer les bits de somme. Le circuit de calcul de la retenue sortante utilise l'anticipation de retenue. Le reste du circuit fait l'addition en propageant les retenues.
[[File:CMOS 4008, circuit découpé en sections.png|centre|vignette|upright=1.5|CMOS 4008, circuit découpé en sections]]
===Les additionneurs à anticipation de retenue multi-niveau===
Les additionneurs précédents mixent anticipation et propagation de retenue. Ceux que nous allons voir utilisent des blocs, mais utilisent l'anticipation de retenue entre les blocs. Par exemple, il est possible de créer un additionneur 16 bits en utilisant 4 additionneurs/blocs de 4 bits, couplé à un circuit d’anticipation de retenue par bloc. Les blocs utilisés peuvent être un additionneur à propagation de retenue, ou à anticipation de retenue, peu importe. Ce qui compte est que la retenue entrante de chaque bloc est calculée par anticipation de retenue.
Pour cela, les additionneurs de 4 bits doivent fournir deux sorties P et G. Les deux sorties indiquent que le bloc soit a généré une retenue, soit a propagé la retenue entrante. Par propagé, on veut dire que la retenue entrante du bloc s'est retrouvée sur la sortie de retenue du bloc. C'est le cas si les 4 colonnes propagent la retenue, ce qui signifie que :
: <math>P_\text{4 bits} = P_0 \cdot P_1 \cdot P_2 \cdot P_3</math>
Pour la génération, cela veut dire qu'une colonne a générée une retenue, qui a été propagée jusqu'à la sortie. En clair, pour chaque colonne, il faut que le G = 1, et que les signaux P des colonnes soient à 1. Pour les 4 colonnes, cela donne :
: <math>G_\text{4 bits} = G_3 + G_2 \cdot P_3 + G_1 \cdot P_3 \cdot P_2 + G_0 \cdot P_3 \cdot P_2 \cdot P_1</math>
[[File:Cla4bitsPG.png|centre|vignette|upright=2|Additionneur à anticipation de retenue de 4 bits.]]
Pour combiner 4 blocs de 4 bits, on devra donc combiner 4 bits P et G, avec un circuit d'anticipation de retenue.
[[File:16-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 16 bits.]]
Il est même possible d'aller plus loin et de combiner 4 additionneurs précédents pour obtenir un additionneur 64 bits.
[[File:64-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 64 bits.]]
Le résultat est un additionneur à plusieurs niveaux d’anticipation de retenue. Une anticipation de retenue dans un blocs de 4 bits (facultative), entre blocs de 4 bits, entre blocs de 16 bits. La même logique peut être utilisée avec des blocs de taille différente de 4, 16 et 64 bits.
==L'addition signée et la soustraction==
Après avoir vu l'addition, il est logique de passer à la soustraction, les deux opérations étant très proches. Si on sait câbler une addition entre entiers positifs, câbler une soustraction n'est pas très compliqué. De plus, la soustraction permet de faire des additions de nombres signés.
===Le soustracteur pour opérandes entiers===
Pour soustraire deux nombres entiers, on peut adapter l'algorithme de soustraction utilisé en décimal, celui que vous avez appris à l'école. Celui-ci ressemble fortement à l'algorithme d'addition : on soustrait les bits de même poids, et on propage éventuellement une retenue sur la colonne suivante. À la différence de l'addition, la retenue est soustraite, et non ajoutée. La table de soustraction nous dit quel est le résultat de la soustraction de deux bits. La voici :
* 0 - 0 = 0 ;
* 0 - 1 = 1 et une retenue ;
* 1 - 0 = 1 ;
* 1 - 1 = 0.
[[File:Algebra1 05 fig014.svg|centre|vignette|Soustraction en binaire, avec les retenues en rouge.]]
La table de soustraction peut servir de table de vérité pour construire un circuit qui soustrait deux bits. Celui-ci est appelé un '''demi-soustracteur'''. Il ressemble beaucoup à un demi-additionneur, les différences se résumant à une porte NON ajoutée pour le calcul de la retenue.
[[File:Half Subtractor Vektor.svg|centre|vignette|Demi-soustracteur.]]
Comme pour l'additionneur, deux demi-soustracteurs peuvent être combinés pour donner un '''soustracteur complet'''. Le calcul de la retenue se fait en combinant les deux retenues des demi-soustracteurs avec une porte OU. Les soustracteurs complets sont utilisés pour créer des soustracteurs à propagation de retenue ou tout autre circuit soustracteur, sur le même modèle que les additionneurs.
[[File:FullSubtractor.svg|centre|vignette|upright=2|Soustracteur complet.]]
Vous l'aurez sans doute remarqué, mais le demi-soustracteur ressemble beaucoup au demi-additionneur. La seule différence est une porte NON ajoutée au bon endroit dans le demi-soustracteur.
{|
|[[File:Half-adder.svg|vignette|Demi-additionneur]]
|[[File:Half Subtractor Vektor.svg|vignette|upright=1.3|Demi-soustracteur]]
|}
La conséquence est qu'il est possible de créer un circuit capable de faire à la fois des additions et des soustractions. L'idée est de remplacer la porte NON du demi-soustracteur par un inverseur commandable. Si on envoie un zéro sur l'entrée de commande, l'inverseur commandable se comportera comme une porte OUI. Le circuit sera alors identique à un demi-additionneur. Si on envoie un 1 sur l'entrée de commande, l'inverseur commandable se comportera comme une porte NON. le circuit sera alors un demi-soustracteur. Il faut faire cela pour chaque demi-soustracteur, ce qui donne ceci pour un soustracteur complet. le résultat est alors appelé un '''additionneur-soustracteur complet'''.
[[File:Additionneur-soustracteur complet.png|centre|vignette|upright=2|Additionneur-soustracteur complet]]
===L'additionneur-soustracteur pour opérandes codées en complément à deux===
Étudions le cas de la soustraction en complément à deux, dans l'objectif de créer un circuit soustracteur. Vous savez sûrement que a−b et a+(−b) sont deux expressions équivalentes. Et en complément à deux, − b = not(b) + 1. Dit autrement, a − b = a + not(b) + 1. On pourrait se dire qu'il faut deux additionneurs pour faire le calcul, mais la majorité des additionneurs possède une entrée de retenue pour incrémenter le résultat de l'addition. Un soustracteur en complément à deux est donc simplement composé d'un additionneur et d'un inverseur.
[[File:Additionneur soustracteur.png|centre|vignette|upright=2|Soustracteur en complément à deux.]]
Il est possible de créer un circuit capable d'effectuer soit une addition, soit une soustraction : il suffit de remplacer l'inverseur par un inverseur commandable, qui peut être désactivé. On a vu comment créer un tel inverseur commandable dans le chapitre sur les circuits combinatoires. On peut remarquer que l'entrée de retenue et l'entrée de commande de l'inverseur sont activées en même temps : on peut fusionner les deux signaux en un seul.
[[File:Additionneur-soustracteur en complément à deux.png|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux.]]
Une implémentation alternative est la suivante. Elle remplace l'inverseur commandable par un multiplexeur.
[[File:4-bit ripple carry adder-subtracter.svg|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux, version alternative.]]
===L'additionneur-soustracteur pour opérandes codées en signe-magnitude===
Passons maintenant aux nombres codés en signe-valeur absolue, les deux opérandes étant notées A et B. Suivant les signes des deux opérandes, on a quatre cas possibles : A + B, A − B (B négatif), −A + B (A négatif) et −A − B (A et B négatifs). Une astuce est que le circuit n'a besoin que de calculer A + B et A − B : il peut les inverser pour obtenir − A − B ou B − A. A + B et A − B peuvent se calculer avec un additionneur-soustracteur, reste à corriger le résultat. Il suffit de lui ajouter un inverseur commandable pour obtenir le circuit d'addition finale.
[[File:Additionneur en signe-valeur absolue.png|centre|vignette|upright=2|Additionneur en signe-valeur absolue.]]
Toute la difficulté tient dans le calcul du bit de signe du résultat, quand interviennent des soustractions. Autant l'addition de deux nombres de même signe (A + B et −A − B) ne pose aucun problème, autant les soustractions posent problème (A − B et −A + B). Suivant que <math>A<B</math> ou que <math>A>B</math>, le signe du résultat ne sera pas le même. Déterminer le signe du résultat se fait en regardant les bits de débordement d'entier, comme on le verra plus bas.
===L'additionneur-soustracteur pour opérandes codées en représentation par excès===
Passons maintenant aux nombres codés en représentation par excès. On pourrait croire que ces nombres s'additionnent comme des nombres non-signés, mais ce serait oublier la présence du biais, qui pose problème. Dans les cas de nombres signés gérés avec un biais, voyons ce que donne l'addition de deux nombres :
: <math>( n_1 + biais ) + ( n_2 + biais ) = n_1 + n_2 + 2 \times biais</math>
Or, le résultat correct serait :
: <math>n_1 + n_2 + biais</math>
En effectuant l'addition telle quelle, le biais est compté deux fois. On doit donc le soustraire après l'addition pour obtenir le résultat correct.
Même chose pour la soustraction qui donne ceci :
: <math>(n_1 + biais) - (n_2 + biais) = n_1 - n_2</math>
Or, le résultat correct serait :
: <math> ( n_1 - n_2 ) + biais </math>
Il faut rajouter le biais pour obtenir l'exposant correct.
On a donc besoin de deux additionneurs/soustracteurs : un pour additionner/soustraire les représentations binaires des opérandes, et un autre pour ajouter/retirer le biais en trop/manquant.
==L'additionneur BCD==
Maintenant, voyons un additionneur qui additionne deux entiers au format BCD. Pour cela, nous allons devoir passer par deux étapes. La première est de créer un circuit capable d'additionneur deux chiffres BCD. Ensuite, nous allons voir comment enchaîner ces circuits pour créer un additionneur BCD complet.
===L'additionneur BCD qui fait l'opération chiffre par chiffre===
Nous allons commencer par voir un additionneur qui additionne deux chiffres en BCD, une sorte d'équivalent BCD de l'additionneur complet. Il fournit un résultat sur 4 bits et une retenue qui est mise à 1 si le résultat dépasse 10 (la limite d'un chiffre BCD). Les deux opérandes sont des chiffres BCD codés sur 4 bits et sont additionnés en binaire par un additionneur des plus normaux, similaire à ceux vus plus haut. Le résultat est alors un entier codé en binaire, sur 5 bits, qu'on corrige/convertit pour obtenir un chiffre BCD et une retenue sortante.
Pour corriger le résultat, une idée intuitive serait de prendre le résultat et de faire une division par 10. Le quotient donne la retenue, alors que le reste est le résultat, le chiffre BCD. Mais un circuit diviseur par 10 utilise beaucoup de portes logiques, ce qui ne vaut pas le coup. Une autre méthode détecte si le résultat est égal ou supérieur à 10, ce qui correspond à un "débordement" (on dépasse les limites d'un chiffre BCD). Si le résultat est plus petit que 10, il n'y a rien à faire : le résultat est bon et la retenue est de zéro. Par contre, si le résultat vaut 10 ou plus, il faut corriger le résultat et générer une retenue à 1.
Il faut donc ajouter un circuit qui détecte si le résultat est supérieur à 9, qui calcule directement la retenue. Ce circuit peut se fabriquer simplement à partir de sa table de vérité, ou en utilisant les techniques que nous verrons dans un chapitre ultérieur sur les comparateurs. La solution la plus simple est clairement d'utiliser la table de vérité, ce qui est très simple, assez pour être laissé en exercice au lecteur. Pour comprendre comment corriger le résultat, établissons une table de vérité qui associe le résultat et le résultat corrigé. L'entrée vaut au minimum 10 et au maximum 9 + 9 = 18. On considère la sortie comme un tout, la retenue étant un 5ème bit, le bit de poids fort.
{|class="wikitable"
|-
! colspan="5" | Entrée
!
! rowspan="10" |
! Retenue
! Résultat corrigé (sans retenue)
! interprétation de la sortie en binaire (retenue inclue)
|-
| 0 || 1 || 0 || 1 || 0
| (10)
| 1 || 0000
| (16)
|-
| 0 || 1 || 0 || 1 || 1
| (11)
| 1 || 0001
| (17)
|-
| 0 || 1 || 1 || 0 || 0
| (12)
| 1 || 0010
| (18)
|-
| 0 || 1 || 1 || 0 || 1
| (13)
| 1 || 0011
| (19)
|-
| 0 || 1 || 1 || 1 || 0
| (14)
| 1 || 0100
| (20)
|-
| 0 || 1 || 1 || 1 || 1
| (15)
| 1 || 0101
| (21)
|-
| 1 || 0 || 0 || 0 || 0
| (16)
| 1 || 0110
| (22)
|-
| 1 || 0 || 0 || 0 || 1
| (17)
| 1 || 0111
| (23)
|-
| 1 || 0 || 0 || 1 || 0
| (18)
| 1 || 1000
| (24)
|}
En analysant le tableau, on voit que pour corriger le résultat, il suffit d'ajouter 6. La raison est que le résultat déborde d'un nibble à 16 en binaire, mais à 10 en décimal : il suffit d'ajouter la différence entre les deux, à savoir 6, et le débordement binaire fait son travail. Donc, la correction après une addition est très simple : si le résultat dépasse 9, on ajoute 6.
On peut maintenant implémenter l'additionneur BCD, en combinant le comparateur avec 10, le circuit de correction, et l'additionneur. La première solution calcule deux versions du résultat : la version corrigée, la version normale. Le choix entre les deux est réalisée par un multiplexeur, commandé par le comparateur.
[[File:Additionneur BCD.png|centre|vignette|upright=1.5|Additionneur BCD]]
L'autre solution utilise un circuit commandable qui soit additionne 6, soit ne fait rien. Le choix entre les deux est commandé par le bit calculé par le comparateur.
[[File:Additionneur BCD, seconde version.png|centre|vignette|upright=1.5|Additionneur BCD, seconde version.]]
Une version alternative du circuit précédent est la suivante. Il contient deux additionneurs : un pour additionner les deux chiffres BCD, un autre pour additionner 6 si besoin. Le résultat du comparateur est directement utilisé pour générer l'opérande du second additionneur : 0 ou 6. Le circuit est simple à concevoir, mais gaspille beaucoup de circuit. Idéalement, il vaudrait mieux utiliser un circuit combinatoire d'addition avec une constante.
[[File:Adder 4 Bit BCD.svg|centre|vignette|upright=2|Additionneur BCD, circuit complet.]]
Pour obtenir un additionneur BCD complet, il suffit d’enchaîner les additionneurs précédents, comme on le ferait avec les additionneurs complets dans un additionneur à propagation de retenue. Au final, l'additionneur BCD est beaucoup plus compliqué qu'un additionneur normal, car il rajoute un comparateur ">9", un petit additionneur pour ajouter 6 et éventuellement d'autres circuits. De plus, il est difficile d'appliquer les optimisations disponibles sur les additionneurs non-BCD. Notamment, les circuits d'anticipation de retenue sont totalement à refaire et le résultat est relativement compliqué. C'est ce qui explique pourquoi le BCD a progressivement été abandonné au profit du binaire simple.
La soustraction en BCD se fait comme en binaire : le nombre à soustraire est remplacé par son complément, le circuit additionne le complément et l'autre opérande, le débordement d'entier fait que le résultat marche. Sauf qu'ici, le complément est un complément à 9. Il se calcule chiffre par chiffre : chaque chiffre est remplacé par (9 - le chiffre en question).
===L'additionneur BCD par ajustement décimal===
L'additionneur BCD précédent effectuait son travail chiffre BCD par chiffre BCD, mais il existe des additionneurs BCD qui font autrement. Sur les premiers processeurs x86, il n'y avait pas d'opération d'addition BCD proprement dit, seulement une addition binaire normale de 8, 16 ou 32 bits. Par contre, elle était secondée par une opération dite d''''ajustement décimal''' qui transformait un nombre binaire en nombre codé en BCD. L'opération d'ajustement décimal prenait un opérande de 8 bits codé en binaire et fournissait un résultat de la même taille, c'est à dire deux chiffres BCD. Effectuer une addition BCD demandait donc de faire deux opérations à la suite : une addition binaire simple, suivie par l'opération d'ajustement décimal. Cela permettait de gérer des nombres entiers en binaire usuel et des entiers BCD sans avoir deux instructions d'addition séparées pour les deux, sans compter que cela simplifiait aussi les circuits d'addition.
L'ajustement décimal s'effectue en ajoutant une constante bien précise à l'opérande à convertir en BCD. L'idée est que la constante est découpée en morceaux de 4 bits, correspondant chacun à un chiffre BCD de l'opérande, chaque morceau contenant soit un 0, soit 6. Cela permet d'ajouter soit 0, soit 6, à chaque chiffre BCD, et donc de le corriger. La propagation des retenues d'un chiffre à l'autre est effectuée automatiquement par l'addition binaire de la constante. L'opération d'ajustement décimal calcule automatiquement la constante. Elle découpe l'opérande en ''nibbles'', vérifie si chaque ''nibble'' est supérieur ou égal à 10, puis détermine la valeur de chaque ''nibble'' de la constante finale. Par exemple, si je prends l'opérande 1001 1110, le ''nibble'' de poids faible déborde, alors que celui de poids fort non. La constante sera donc 0000 0110 : 0x06. Inversement, si le ''nibble'' de poids fort déborde et pas celui de poids faible, la constante sera alors 0x60. Et la constante est de 0x66 si les deux ''nibbles'' débordent, de 0x00 si aucun ne déborde.
Le circuit d’ajustement décimal est donc composé de trois étapes : deux étapes pour calculer la constante, et un circuit d'addition pour additionner cette constante au nombre de départ. La première étape découpe l'opérande en morceaux de 4 bits, en chiffres BCD, et vérifie si chacun d'entre eux vaut 10 ou plus. La seconde étape prend les résultats de la première étape, et les combine pour calculer la constante. Enfin, on trouve l'addition finale, qui était réalisée par un circuit d'addition utilisé à la fois pour l'ajustement décimal et l'addition binaire. La différence entre une addition normale et une opération d'ajustement décimal tient dans le fait que les deux premières étapes sont désactivées dans une addition normale.
[[File:Additionneur BCD parallèle.png|centre|vignette|upright=1.5|Additionneur BCD parallèle]]
===L'additionneur biquinaire===
Les entiers BCD ne sont qu'un des encodages hybrides entre décimal et binaire. L'encodage biquinaire est l'un d'entre eux et nous allons faire un rappel rapide à ce sujet. Pour simplifier, un chiffre encodé en biquinaire est composé de deux parties : un bit, couplé à une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits (0, 1, 2, 3 et 4). Le bit indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9.
Additionner deux nombres de biquinaire demande donc d'additionner deux parties quinaires encodées en ''one-hot'' et d'additionner deux bits. Mais attention : il faut tenir compte de la retenue de l'addition des parties quinaires. Et idéalement, il faut aussi tenir compte d'une retenue entrante, provenant de l'addition de la colonne de chiffres précédente. Toute la difficulté vient de la création de l'additionneur ''one-hot''. Heureusement, vu qu'il n'y a que 4-5 bits à additionner, il est souvent fabriqué à partir de sa table de vérité.
[[File:Additionneur bi-quinaire.png|centre|vignette|upright=2|Additionneur bi-quinaire]]
Un avantage du biquinaire est que le calcul du complément à 9 est très simple. Il faut pour cela : inverser la partie binaire avec une porte NON, puis inverser l'ordre des bits de la partie quinaire. Concrètement, le bit de poids faible devient le bit de poids fort, et ainsi de suite. Par exemple, une partie quinaire 01000 devient 00010, 10000 devient 00001, 00100 ne change pas, etc. Le tout peut se calculer avec une porte NON et 5 multiplexeurs.
===L'additionneur BCD avec calculs intermédiaires en biquinaire===
L'ordinateur IBM 1401, un ancien ''mainframe'' des années 60, utilisait un additionneur BCD un peu particulier. Les nombres étaient encodés en BCD dans la mémoire de l'ordinateur, mais les circuits de calcul utilisaient la représentation biquinaire. Lors d'un calcul, le processeur de l'ordinateur traduisait les chiffres BCD en représentation biquinaire, faisait une addition en biquinaire, avant de traduire le résultat en BCD normal.
Pour être précis, l'IBM 1401 utilisait une variante du biquinaire. L'encodage biquinaire de l'IBM 1401 est le suivant : la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre pair encodé par la partie quinaire. Si l'IBM 1401 utilisait cette variante du biquinaire, c'est car elle donnait des circuits de conversion BCD-biquinaire plus économes en portes logiques et plus rapides.
La partie binaire est le bit de poids faible du chiffre BCD, la partie biquinaire est calculée par un simple décodeur qui prend en entrée le chiffre BCD, amputé de son bit de poids faible. La traduction inverse demande d'utiliser un encodeur, à la place du décodeur. Par contre, le circuit d'addition biquinaire était plus compliqué du fait de la gestion des retenues. L'addition des parties binaires et quinaires se faisait en parallèle, dans deux additionneurs séparés. Cependant, l'addition des parties binaire fournit une retenue, qu'il faut prendre en compte. Pour cela, l'IBM 1401 disposait d'un troisième additionneur qui fournissait le résultat final, encodé en biquinaire.
[[File:Additionneur bi-quinaire de l'IBM 1401.png|centre|vignette|upright=2|Additionneur biquinaire de l'IBM 1401]]
Une implémentation moderne demanderait d'utiliser des portes ET combinées à des portes OU, le circuit pouvant être construit simplement à partir de sa table de vérité. Sur l'IBM 1401, le circuit était cependant différent, en raison de l'utilisation de OU câblés, des croisements de fils qui fonctionnent comme des portes OU, que nous n'avons pas encore vu pour le moment, mais qui seront détaillés dans quelques chapitres. Les OU câblés étaient utilisés pour simplifier le design du circuit, mais demandaient des portes logiques spécifiques, ce qui collait avec le fait que ce ''mainframe'' utilisait des transistors en Germanium. L'implémentation exacte est décrite dans cet article de blog, mais je ne recommande sa lecture qu'à ceux qui savent ce qu'est un OU câblé :
* [https://www.righto.com/2015/10/qui-binary-arithmetic-how-1960s-ibm.html Qui-binary arithmetic: how a 1960s IBM mainframe does math].
==Les débordements d'entier lors d'une addition/soustraction==
Les instructions arithmétiques manipulent des entiers codés sur un nombre fixe de bits, qui ne peuvent prendre leurs valeurs que dans un intervalle. Pour les nombres positifs, un ordinateur qui code ses entiers sur n bits pourra coder tous les entiers allant de 0 à <math>2^n -1</math>. Pour les nombres négatifs, l'intervalle est différent et dépend de la représentation utilisée. Dans le cas général, l'ordinateur peut coder les valeurs comprises de <math>N_\text{min}</math> à <math>N_\text{max}</math>. Si le résultat d'un calcul sort de cet intervalle, il ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math> , alors que la valeur basse vaut <math>N_\text{min} - 1</math> (avec <math>N_\text{max}</math> et <math>N_\text{min}</math> respectivement la plus grande et la plus petite valeur codable par l'ordinateur).
===La correction des débordements d'entier : l'arithmétique saturée===
Quand un débordement d'entier survient, tous les circuits de calcul ne procèdent pas de la même manière. Dans les grandes lignes, il y a deux réactions possibles : soit on corrige automatiquement le résultat du débordement, soit on ne fait rien et on se contente de détecter le débordement.
Si le débordement n'est pas corrigé automatiquement, le circuit ne conserve que les bits de poids faibles du résultat. Les bits en trop sont simplement ignorés. On dit qu'on utilise l''''arithmétique modulaire'''. Le problème avec ce genre d'arithmétique, c'est qu'une opération entre deux grands nombres peut donner un résultat très petit. Par exemple, si je dispose de registres 4 bits et que je souhaite faire l'addition 1111 + 0010 (ce qui donne 15 + 2), le résultat est censé être 10001 (17), ce qui est un résultat plus grand que la taille d'un registre. En conservant les 4 bits de poids faible, j’obtiens 0001 (1). En clair, un résultat très grand est transformé en un résultat très petit. Cela peut poser problèmes si on travaille uniquement avec des nombres positifs, mais c'est aussi utilisé pour coder des nombres en complément à deux.
D'autres circuits utilisent ce qu'on appelle l''''arithmétique saturée''' : si un calcul génère un débordement, on arrondi le résultat au plus grand entier supporté par le circuit. Les circuits capables de calculer en arithmétique saturée sont un peu plus complexes, vu qu'il faut rajouter des circuits pour corriger le résultat en cas de débordement. Il suffit généralement de rajouter un ''circuit de saturation'', qui prend en entrée le résultat et le corrige en cas de débordement. Ce circuit de saturation met la valeur maximale en sortie si un débordement survient, mais se contente de recopier le résultat du calcul sur sa sortie s'il n'y a pas de débordement. Typiquement, il est composé d'une couche de multiplexeurs, qui sélectionnent quelle valeur mettre sur la sortie : soit le résultat du calcul, soit le plus grand nombre entier géré par le processeur, soit le plus petit (pour les nombres négatifs/soustractions).
L'arithmétique saturée est utilisée pour les additions et soustractions, mais c'est plus rare pour les multiplications/divisions. Une des raisons est que le résultat d'une addition/soustraction prend un bit de plus que le résultat, là où les multiplications doublent le nombre de bits. Quand une addition déborde, le résultat réel est proche de la valeur maximale codable. mais quand une multiplication déborde, le résultat peut parfois valoir 200 à 60000 fois plus que la valeur maximale codable. Les calculs avec une valeur saturée/corrigée sont donc crédibles pour une suite d'additions, mais pas pour une suite de multiplications.
===La détection des débordements entiers===
Quand un débordement d'entier a eu lieu, il vaut mieux que l'additionneur prévienne ! Pour cela, l'additionneur a une '''sortie de débordement''', parfois nommée ''Overflow'', dont la valeur indique si l'addition a généré un débordement d'entier ou non. Reste que détecter un débordement ne se fait pas de la même manière selon que l'on parle d'un additionneur non-signé ou signé.
Pour les additionneur non-signés, l'additionneur calcule un bit de plus que ce qui est supporté par l'ordinateur. Par exemple, un additionneur 32 bits fournit un résultat sur 33 bits, un débordement d'entier a lieu quand le 33ème bit est à 1. Précisément, la sortie de débordement n'est autre que la retenue finale, celle fournie par le dernier additionneur complet. Le seul type de débordement possible est un débordement par le haut, où le résultat dépasse la valeur maximale. Avec l'arithmétique saturée, le circuit de saturation consiste en une seule couche de multiplexeurs, voire en un circuit de mise à la valeur maximale tel que vu dans le chapitre sur les opérations bits à bits.
[[File:Gestion des débordements d'entiers.png|centre|vignette|upright=2.5|Gestion des débordements d'entiers lors d'une addition non-signée.]]
Pour les additionneurs non-signés, la gestion des débordements d'entiers dépend fortement de la représentation signée. Nous allons étudier le cas du complément à deux. Si vous vous rappelez le chapitre 1, les calculs sur des nombres en complètement à deux utilisent les règles de l'arithmétique modulaire, c'est une condition nécessaire. À priori, on peut penser que dans ces conditions, les débordements d'entiers sont une chose parfaitement normale, qui nous permet d'avoir des résultats corrects. Néanmoins, certains débordements d'entiers peuvent survenir malgré tout et produire des bugs assez ennuyeux.
Si l'on tient en compte les règles du complément à deux, on sait que le bit de poids fort (le plus à gauche) permet de déterminer si le nombre est positif ou négatif : il indique le signe du nombre. Tout se passe comme si les entiers en complément à deux étaient codés sur un bit de moins, et avaient leur longueur amputé du bit de poids fort. Si le résultat d'un calcul écrase le bit de poids fort, il y a un débordement d'entiers. Il existe une règle simple qui permet de détecter ces débordements d'entiers. L'addition de deux nombres positifs ne peut pas être un nombre négatif. Si on additionne deux nombres dont le bit de signe est à 0 et que le bit de signe du résultat est à 1, on est en face d'un débordement d'entiers. Même chose pour deux nombres négatifs : le résultat de l'addition ne peut pas être positif. On peut résumer cela en une phrase : si deux nombres de même signe sont ajoutés, un débordement a lieu quand le bit du signe du résultat a le signe opposé.
Modifier les circuits d'au-dessus pour qu'ils détectent les débordements en complément à deux est simple comme bonjour : il suffit créer un petit circuit combinatoire qui prenne en entrée les bits de signe des opérandes et du résultat, et qui fasse le calcul de l'indicateur de débordements. Si l'on rédige sa table de vérité, on doit se retrouver avec la table suivante :
{|class="wikitable"
|-
!Entrées
!Sortie
|-
|000||0
|-
|001||1
|-
|010||0
|-
|011||0
|-
|100||0
|-
|101||0
|-
|110||1
|-
|111||0
|}
L'équation de ce circuit est la suivante, avec <math>S_a</math> et <math>S_b</math> les signes des deux opérandes, et <math>C_i</math> la retenue de la colonne précédente :
: <math> ( S_a . S_b . \overline{C_i} ) + ( \overline{S_a} . \overline{S_b} . C_i )</math>
En simplifiant, on obtient alors :
: <math>( S_a . S_b ) \oplus C_i</math>
Or, il se trouve que <math>S_a . S_b</math> est tout simplement la retenue en sortie du dernier additionneur, que nous noterons <math>C_o</math>. On trouve donc :
: <math>C_o \oplus C_i</math>
Il suffit donc de faire un XOR entre la dernière retenue et la précédente pour obtenir le bit de débordement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de décalage et de rotation
| prevText=Les circuits de décalage et de rotation
| next=Les circuits de comparaison
| nextText=Les circuits de comparaison
}}
</noinclude>
rx3t3clkkey6hw8c9vkpwuhk3yqhggv
768667
768666
2026-06-25T20:00:58Z
Mewtow
31375
/* Les additionneurs à anticipation de retenue multi-niveau */
768667
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir les circuits capables de faire une addition ou une soustraction, ainsi que quelques circuits spécialisés. Précisons cependant que les fabricants de processeurs travaillent d'arrache-pied pour trouver des moyens de rendre ces circuits de calcul plus rapides et plus économes en énergie. Autant vous dire que les circuits que vous allez voir sont vraiment des circuits qui font pâle figure comparé à ce que l'on peut trouver dans un vrai processeur commercial !
==Les circuits pour additionner 2 ou 3 bits==
L'addition se fait en binaire de la même manière qu'en décimal. On additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. La soustraction fonctionne sur le même principe, sur le même modèle qu'en décimal.
[[File:Binary Addition Demonstration.svg|centre|vignette|Exemple d'addition en binaire.]]
En clair, additionner deux nombres demande d'additionner 2 bits et une retenue sur chaque colonne, et de propager les retenues d'une colonne à l'autre. La propagation des retenues est quelque chose de simple en apparence, mais qui est sujet à des optimisations extraordinairement nombreuses. Aussi, pour simplifier l'exposition, nous allons voir comment gérer une colonne avant de voir comment sont propagées les retenues.
[[File:Full Adder Block.svg|vignette|upright=1|class=transparent|Additionneur complet.]]
Si on effectue une addition en colonne, on doit additionner les deux bits sur la colonne, mais aussi additionner une éventuelle retenue. Il faut donc créer un circuit qui additionne trois bits : deux bits de données, plus une retenue. Ce circuit qui additionne trois bits est appelé un '''additionneur complet'''. Il fournit en sortie deux bits : un bit de somme et une retenue sortante. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|-
||0||0||0|| ||0||0
|-
||0||0||1|| ||0||1
|-
||0||1||0|| ||0||1
|-
||0||1||1|| ||1||0
|-
||1||0||0|| ||0||1
|-
||1||0||1|| ||1||0
|-
||1||1||0|| ||1||0
|-
||1||1||1|| ||1||1
|}
Il est possible d'utiliser un tableau de Karnaugh pour traduire la table de vérité, mais elle donne un résultat assez compliqué. La retenue et le bit de somme sont calculés à part. La retenue est calculée avec trois portes ET et une porte OU. Le bit de somme est calculé lui avec 4 portes ET, une porte OU, et trois portes NON.
[[File:Full Adler with 3 entries.png|centre|vignette|upright=2|Additionneur complet, fabriqué à partir de la table de vérité.]]
D'autres méthodes donnent des résultats plus compréhensibles. Nous allons les voir dans la suite de cette section.
===L'additionneur complet basé sur des demi-additionneurs===
[[File:1-bit half-adder.svg|class=transparent|vignette|Demi-addtionneur.]]
Nous avons déjà vu comment additionner deux bits dans le chapitre sur les incrémenteurs, mais quelques rappels ne seront pas de trop. Le '''demi-additionneur''' est un circuit qui additionne deux bits. Il implémente la table d'addition, qui est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Mais un demi-additionneur n'additionne que deux bits, il manque de quoi additionner la retenue. Une solution pour cela est d'enchaîner deux demi-additionneurs : un qui additionne les deux bits de données, et un second qui additionne la retenue au résultat du précédent. La retenue finale se calcule en combinant les sorties de retenue des deux demi-additionneurs, avec une porte OU. Le résultat est un additionneur complet fabriqué avec deux demi-additionneurs. Pour vous en convaincre, établissez la table de vérité de ce circuit, vous verrez que ça marche.
[[File:Full Adder Blocks.svg|centre|vignette|upright=2|Additionneur complet fabriqué avec deux demi-additionneurs.]]
[[File:Half-adder.svg|vignette|upright=1|Circuit d'un demi-additionneur naïf.]]
L'implémentation naïve d'un demi-additionneur utilise une porte XOR et une porte ET, comme illustré ci-contre. Le circuit s'obtient en faisant uen traduction littérale de la table de vérité du circuit. Si je dis qu'elle est naïve, c'est car il est possible de l'optimiser, de manière à éliminer des redondances cachées. Mais nous avions déjà vu cela dans le chapitre sur les incrémenteurs, aussi laissons cela pour plus tard.
En combinant deux demi-additionneurs de ce type, on obtient l'additionneur complet suivant. Les deux sorties de retenue des demi-additionneurs sont combinées avec une porte OU, pour calculer la retenue finale.
{| class="flexible"
|[[File:Full Adder Modules.svg|class=transparent|Composition d'un additionneur complet. On voit bien que celui-ci est composé de deux demi-additionneurs, en rouge et en bleu, auxquels on a ajouté une porte OU pour calculer la retenue finale.]]
|[[File:Full-adder.svg|300px|class=transparent|Circuit d'un additionneur complet.]]
|}
Le fait de combiner les deux retenues avec une porte OU n'est pas ce qu'il y a de plus intuitif. La table de vérité nous dit que ça fonctionne, mais on comprend mal pourquoi. Mais tout devient plus clair quand on sait que les deux retenues ne peuvent pas être à 1 en même temps. Même dans l'addition 1 + 1 + 1, seule une retenue est à 1. Soit l'addition des deux bits d'opérande donne naissance à une retenue, soit c'est l'addition ''résultat + retenue entrante''. La retenue sortant est donc à 1 quand une des deux addition donne une retenue. D'où l'usage d'une porte OU. Remarquez qu'une porte XOR donne le même résultat, vu que le cas où les deux retenues sont à 1 n'est jamais rencontré.
===La propagation et la génération des retenues===
L'additionneur complet que nous allons voir dans cette section sert à introduire les concepts de propagation, génération et absorption de retenue. Il s'agit de concepts très importants quand on étudie les additionneurs. l'idée part d'un principe très simple : la retenue sortante dépend de la retenue d'entrée. La relation entre les deux se résume à trois cas, qui dépendent de la valeur des deux bits additionnés, nommés A et B.
* Dans le premier cas, la retenue entrante est égale à la retenue sortante. On dit que la retenue entrante est propagée par l'additionneur.
* Dans le second cas, la retenue est forcée à 0 : la retenue sortante vaut 0, peu importe la valeur de la retenue entrante.
* Dans le troisième cas, la retenue est forcée à 1 : la retenue entrante vaut 1, peu importe la valeur de la retenue entrante.
[[File:Signaux P et G fournis par un demi-additionneur.png|vignette|Signaux P et G fournis par un demi-additionneur]]
Maintenant, créons un circuit qui nous dise si une retenue est propagée ou générée. Il a deux bits de sortie, nommés P et G : P pour ''Propagate'', G pour ''Generate''. Le bit P est à 1 si la retenue entrante est propagée, il est à 0 sinon. Le bit G est à 1 si une retenue est générée, à 0 sinon. Une retenue est considérée comme absorbée si elle n'est pas ni propagée ni générée, pas besoin d'un troisième bit pour gérer ce cas.
Il se trouve que ce circuit n'est autre qu'un demi-additionneur ! Pour vous en rendre compte, regardez la table de vérité d'un additionneur complet, illustrée ci-dessous. Lorsque les deux bits d'opérande sont à 0, la retenue sortante vaut toujours 0. Si ils sont tous deux à 1, alors la retenue sortante vaut 1. S'ils sont différents, alors retenues sortante et entrante sont égales. Le bit P est donc généré par une simple porte XOR. Quant au bit G, il est à 1 si les deux bits d'opérandes sont à 1, ce qui correspond à une porte ET. Il se trouve que ces deux portes forment un demi-additionneur !
{|class="wikitable"
|-
! Opérande 1 !! Opérande 2 !! Retenue entrante !! !! Retenue sortante
|- class="f_rouge"
||0||0||0|| ||0
|- class="f_rouge"
||0||0||1|| ||0
|- class="f_bleu"
||0||1||0|| ||0
|- class="f_bleu"
||0||1||1|| ||1
|- class="f_bleu"
||1||0||0|| ||0
|- class="f_bleu"
||1||0||1|| ||1
|- class="f_vert"
||1||1||0|| ||1
|- class="f_vert"
||1||1||1|| ||1
|}
Ensuite, créons un circuit qui prend ces deux sorties P et G, et calcule la retenue sortante en fonction. Le circuit en question a trois entrées : la retenue entrante, les deux bits P et G. La retenue sortante vaut 1 soit si une retenue est générée, soit si la retenue entrante est propagée et qu'elle vaut 1. En notant <math>{C_{in}}</math> et <math>{C_{out}}</math> les retenues entrantes et sortante, on a :
: <math>{C_{out}} = G + \left( P . C_{in} \right)</math>
Le circuit est donc composé d'une porte OU et d'une porte ET. En combinant un demi-additionneur avec le circuit de calcul de retenue sortante vu plus haut, on a :
[[File:Additionneur complet avec propagation et génération de retenue.png|class=transparent|centre|vignette|upright=2|Additionneur complet avec propagation et génération de retenue.]]
Certes, on retombe sur le circuit vu plus haut. Mais il est possible de remplacer le circuit en orange par un autre. Par exemple, il est possible de le remplacer par multiplexeur, qui choisit entre la retenue générée et la retenue entrante (propagée). Le choix se fait selon la valeur du bit P, qui chosiit entre propager la retenue et la générer.
[[File:Additionneur crée avec un multiplexeur.png|centre|vignette|upright=1.5|Additionneur crée avec un multiplexeur]]
Le circuit exact est illustré ci-dessous. Il semble utiliser plus de portes logiques que l'additionneur complet précédent. Cependant, nous verrons dans quelques chapitres qu'il est possible d'implémenter un multiplexeur avec seulement 6 transistors, voire moins ! L'implémentation utilise des portes à transmission, mais nous en reparlerons dans le chapitre sur les transistors, quand nous verrons les additionneurs à ''Manchester Carry Chain''. Au passage, une variante de ce circuit a été utilisée dans le processeur processeur 8086 d'Intel, comme on le verra dans le chapitre suivant.
[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
[[File:FulladderPG.png|vignette|upright=1|Additionneur complet avec deux sorties P et G.]]
Pour finir, sachez qu'il existe des additionneurs qui fournissent : le bit de résultat, deux sorties P et G qui indiquent si l'addition propage ou génère une retenue. Il ne s'agit pas d'additionneurs complets, car il manque de quoi calculer la retenue sortante. De tels additionneurs seront appelés des '''additionneurs partiels'''. De tels additionneurs sont utilisés dans certains additionneurs pour gagner en performance.
Enfin, il existe aussi ce que j'ai décidé d'appeler des '''additionneurs P/G''', qui sont des additionneurs complets auxquels on a ajouté deux sorties P et G, en plus de la sortie de retenue. Leur circuit est le même que celui d'un additionneur complet, auquel on a ajouté deux fils. Vu que ces deux sorties sont fournies par le premier demi-additionneur, ajouter ces deux sorties demande d'ajouter des fils, pas de portes logiques. De tels additionneurs sont aussi utilisés dans certains additionneurs pour gagner en performance.
===L'additionneur complet basé sur une porte à majorité===
Maintenant, voyons une dernière implémentation possible de l'additionneur complet. Mais avant de voir comment l'additionneur est implémenté, nous allons devoir faire un petit focus sur la retenue sortante. Reprenons la table de vérité, et regardons ce qu'il en est pour la retenue sortante uniquement.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante
|-
||0||0||0|| ||0
|-
||0||0||1|| ||0
|-
||0||1||0|| ||0
|-
||0||1||1|| ||1
|-
||1||0||0|| ||0
|-
||1||0||1|| ||1
|-
||1||1||0|| ||1
|-
||1||1||1|| ||1
|}
Vous remarquerez que la retenue sortante est égale au bit majoritaire, parmi les trois bits d'opérande. Si deux ou trois bits sont à 1, la retenue sortante vaut 1. Et réciproquement avec 0. Le circuit de calcul de la retenue peut donc être remplacé par une '''porte à majorité'''.
[[File:Additionneur crée avec une porte à majorité.png|centre|vignette|upright=2|Additionneur crée avec une porte à majorité]]
Mais une porte à majorité est assez gourmande en circuit, ce qui fait qu'un additionneur pareil ne serait pas pratique, ni utile. Du moins, ce serait le cas s'il n'y avait pas une possibilité d'optimisation extrêmement intéressante. Il est possible de calculer le 'bit de somme' à partir de la retenue sortante ! En effet, le bit de somme est l'inverse de la retenue sortante, sauf dans deux cas : les trois bits d'entrée sont à 0, où ils sont tous à 1. Le bit de somme vaut 0 dans le premier cas, 1 dans le second cas.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|- class="f_bleu"
||0||0||0|| ||0||0
|- class="f_vert"
||0||0||1|| ||0||1
|- class="f_vert"
||0||1||0|| ||0||1
|- class="f_vert"
||0||1||1|| ||1||0
|- class="f_vert"
||1||0||0|| ||0||1
|- class="f_vert"
||1||0||1|| ||1||0
|- class="f_vert"
||1||1||0|| ||1||0
|- class="f_rouge"
||1||1||1|| ||1||1
|}
En clair, 6 lignes de la table de vérité sur 8 peuvent se calculer avec une porte à majorité, le reste demandant quelques portes logiques pour faire la correction. L'implémentation en circuit demande donc trois choses :
* de calculer la retenue sortante et de quoi l'inverser ;
* un circuit qui vérifie si tous les bits opérande valent 0 : une porte NOR ou OU fait l'affaire ;
* un circuit qui vérifie s'ils valent tous 1, à savoir une porte ET.
Ensuite, on combine le résultat des trois circuits précédents pour obtenir le résultat final. La combinaison est le fait du circuit en jaune dans le schéma ci-dessous.
[[File:Full adder basé sur une modification de la retenue.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue]]
Le circuit en jaune est assez simple. Quand la porte ET sort un 1, la sortie doit être forcée à 1. Par contre, quand la porte OU sort un zéro, la sortie doit être forcée à 0. Il suffit donc d'enchainer un circuit de mise à 1 et un circuit de mise à 0. Nous avons vu dans le chapitre sur les opérations de masquage qu'il s'agit respectivement d'une porte OU et d'une porte ET.
[[File:Full adder basé sur une modification de la retenue interne, au complet.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue interne, au complet]]
Divers processeurs utilisaient des additionneurs complets de ce type : le fameux Z80, l'Intel 4004 et de l'Intel 8008 sont les exemples les plus notables. Pourtant, on pourrait s'étonner qu'un tel circuit ait existé. Il utilise beaucoup de portes logiques, a une profondeur logique supérieure : il n'a rien d'avantageux. Sauf qu'il était utilisé sur d'anciens processeurs, qui utilisaient la technologie dite TTL, différente de la technologie CMOS des transistors modernes. Et avec la technologie TTL, il est possible de fusionner plusieurs portes logiques ET et NOR en une seule porte logique ET/OU/NON ! Un additionneur complet construit ainsi ne prenait que deux portes logiques : une pour le calcul de la retenue sortante, une autre pour le reste du circuit.
==L'addition non signée==
Voyons maintenant un circuit capable d'additionner deux nombres entiers: l''''additionneur'''. Dans la version qu'on va voir, ce circuit manipulera des nombres strictement positifs. L'addition des nombres codés en complètement à deux sera vu dans une section ultérieure.
===L'additionneur série===
Il est possible d'additionner deux nombres bit par bit, grâce à un additionneur complet associé à plusieurs registres à décalages. Les opérandes sont placées chacune dans un registre à décalage, et l'additionneur complet utilise les bits sortants de ces registres à décalage. Le bit du résultat est envoyé au registre à décalage pour le résultat. La retenue de l'addition est stockée dans une bascule de 1 bit, en attente du prochain cycle d'horloge. Un tel additionneur est appelé un '''additionneur série'''. Il a été utilisé sur d'anciens ordinateurs dans les années 50-60, aussi bien des prototypes que des ordinateurs commerciaux.
[[File:Additionneur série.jpg|centre|vignette|upright=2.5|Additionneur série.]]
===L'additionneur à propagation de retenue===
L''''additionneur à propagation de retenue''' est aussi appelé l'additionneur ''ripple carry'', c'est son nom anglais. J'utiliserais occasionnellement le nom anglais dans ce qui suit, car celui-ci est plus court. Ilutilise un additionneur complet pour chaque colonne de l'addition. Pour gérer les retenues, il suffit ainsi de câbler des additionneurs complets les uns à la suite des autres, chacun envoyant sa retenue à la colonne suivante.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Notons la présence de la retenue sortante, qui est utilisée pour détecter les débordements d'entier, ainsi que pour d'autres opérations. Le bit de retenue final est souvent stocké dans un registre spécial du processeur (généralement appelé ''carry flag'').
Notez aussi, à droite du schéma précédent, l'entrée de retenue <math>\text{R}_\text{Entrée}</math>. Elle est très utile pour l'implémentation de certaines opérations comme l'inversion de signe, la soustraction, l'incrémentation, etc. Certains processeurs sont capables de faire une opération appelée ADC, ADDC ou autre nom signifiant ''Addition with Carry'', qui permet de faire le calcul A + B + Retenue (la retenue en question est la retenue sortante de l'addition précédente, stockée dans le registre ''carry flag''). Son utilité principale est de permettre des additions d'entiers plus grands que ceux supportés par le processeur. Par exemple, cela permet de faire des additions d'entiers 32 bits sur un processeur 16 bits.
[[File:Adder Network 3 Bit Sum Delay 003.svg|vignette|upright=0.5|Propagation de retenue dans l'additionneur.]]
Cet additionneur utilise très peu de portes logiques, ce qui fait qu'il était utilisé sur certains processeurs 8 et 16 bits, dont le budget en portes logiques était limité. Bien que très simple, cet additionneur est cependant peu performant. Son temps de calcul est proportionnel à la taille des opérandes. Par exemple, additionner deux opérandes de 32 bits prendra deux fois plus de temps que pour des opérandes de 16 bits. La raison est que les retenues doivent se propager à travers le circuit, du premier additionneur jusqu'au dernier.
L'addition étant une opération fréquente, d'autres additionneurs ont été inventés. Ils utilisent des optimisations qui utilisent plus de circuits pour gagner en rapidité. Mais avant de voir ces optimisations, nous allons voir s'il est possible d'optimiser les additionneurs à propagation de retenue.
===L'additionneur à saut de retenue===
L'additionneur à propagation de retenue peut être optimisé de deux manières. La première accélère la propagation de la retenue, en agissant au niveau des additionneurs complets. La ''Manchester carry chain'' est une optimisation de ce type, mais on ne peut pas encore expliquer à ce stade du cours, car elle optimisait les additionneurs complets au niveau des transistors eux-mêmes. Elle a été utilisée sur de nombreux processeurs connus, comme le 8086 d'Intel ou le Z80.
La seconde manière rend l'addition plus rapide dans certains cas. Le résultat est un additionneur dont le temps de calcul est variable. Le calcul prendra quelques cycles d'horloges avec certains opérandes, mais d'autres opérandes ne verront aucune amélioration. Il n'améliore pas le pire des cas, dans lequel la retenue doit être propagée du début à la fin, du bit de poids faible au bit de poids fort. Mais dans d'autres cas, il permet d'avoir le résultat en avance. L'optimisation en question donne l''''additionneur à saut de retenue''' (''carry-skip adder'').
Il est composé de blocs qui additionnent 4/5 bits, rarement plus. La taille d'un bloc varie d'un additionneur à l'autre, elle peut même varier dans l'additionneur, mais nous allons prendre un exemple où les blocs font 4 bits. L'idée est de détecter précocement si la retenue entrante est propagée à travers tout le bloc. Si la retenue est propagée dans tous le bloc, on peut directement l'envoyer sur la sortie de retenue, la retenue saute le bloc entier. Dans le cas contraire, on doit calculer la retenue normalement. Le choix entre les deux est le fait d'un multiplexeur. Toute la difficulté est de savoir comment commander le multiplexeur.
[[File:Carry skip adder, principe de base.png|centre|vignette|upright=2.5|Carry skip adder : principe de base]]
Pour savoir si une retenue est propagée dans un bloc entier, il faut utiliser des additionneurs complets, qui ont en plus une sortie P indiquant que l'additionneur propage la retenue. Le bloc propage une retenue si chaque additionneur complet propage la retenue, donc s'ils ont tous leur sortie P à 1. Un simple ET logique suffit à déterminer si c'est le cas. Le signal de commande du multiplexeur est donc un vulgaire ET entre toutes les sorties P des additionneurs complets du bloc.
[[File:Additionneur à saut de retenue.png|centre|vignette|upright=2.5|Calcul de la commande du MUX.]]
Voici ce que ça donne pour un bloc de 4 bits :
[[File:CSAdder4Bit.svg|centre|vignette|upright=1.5|Additionneur ''carry skip'' : bloc de 4 bits.]]
L'additionneur à saut de retenue est construit en assemblant plusieurs blocs de ce type.
[[File:BCSAdder16Bit.svg|centre|vignette|upright=3|Additionneur à saut de retenue.]]
===L'additionneur à sélection de retenue===
L''''additionneur à sélection de retenue''' utilise aussi des blocs, comme les additionneurs précédents. L'addition se fait en deux versions : une avec la retenue du bloc précédent valant zéro, et une autre version avec la retenue du bloc précédent valant 1. Il suffira alors de choisir le bon résultat avec un multiplexeur, une fois cette retenue connue. On gagne ainsi du temps en calculant à l'avance les valeurs de certains bits du résultat, sans connaître la valeur de la retenue. Petit détail : sur certains additionneurs à sélection de retenue, les blocs de base n'ont pas la même taille. Cela permet de tenir compte des temps de propagation des retenues entre les blocs.
[[File:Additionneur à sélection de retenue.png|centre|vignette|upright=1.5|Additionneur à sélection de retenue avec seulement deux blocs.]]
Dans les exemples du dessus, chaque sous-additionneur étaient des additionneurs à propagation de retenue. Mais ce n'est pas une obligation, et tout autre type d’additionneur peut être utilisé. Par exemple, on peut faire en sorte que les sous-additionneurs soient eux-mêmes des additionneurs à sélection de retenue, et poursuivre ainsi de suite, récursivement. On obtient alors un '''additionneur à somme conditionnelle''', plus rapide que l'additionneur à sélection de retenue, mais qui utilise beaucoup plus de portes logiques.
===Les additionneurs à anticipation de retenue===
Les '''additionneurs à anticipation de retenue''' calculent chaque retenue sans avoir à propager les retenues précédentes. Au lieu de calculer les retenues une par une, ils calculent toutes les retenues en parallèle, à partir des bits des opérandes. Une fois les retenues pré-calculées, il suffit de les additionner avec les deux bits adéquats, pour obtenir le résultat. Ces additionneurs sont composés de deux parties :
* un circuit qui pré-calcule les retenues, sans les propager, directement à partir des opérandes ;
* d'une couche d'additionneurs complets, qui additionnent chacun deux bits d'opérande et la retenue pré-calculée.
[[File:Additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Additionneur à anticipation de retenue.]]
Le calcul des retenues se fait en deux étapes. La première détermine si les retenues sont propagées ou générées, pour chaque colonne. Elle détermine, pour chaque colonne, les bits P et G qui indiquent respectivement : qu'une retenue a été générée sur cette colonne, que la colonne propage la retenue précédente. La seconde étape utilise ces signaux P et G pour déterminer la retenue sur chaque colonne. Le circuit de calcul des retenues est donc composé de deux parties : une couche de demi-additionneurs pour générer les signaux P et G, un circuit de calcul des retenues proprement dit.
[[File:Circuit complet d'un additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Circuit complet d'un additionneur à anticipation de retenue.]]
Pour rappel, la retenue de la colonne i, notée <math>C_i</math> est égale à :
: <math>G_i + (P_i . C_{i-1})</math>, avec G le signal de génération de retenue, P le signal de propagation de retenue.
Si on utilisait cette formule sans trop réfléchir, on retomberait sur un additionneur à propagation de retenue inutilement compliqué. L'astuce des additionneurs à anticipation de retenue consiste à remplacer le terme Ci−1 par sa valeur calculée avant. Par exemple, je prends un additionneur 4 bits. Je dispose de deux nombres A et B, contenant chacun 4 bits : A3, A2, A1, et A0 pour le nombre A, et B3, B2, B1, et B0 pour le nombre B. Si j'effectue les remplacements, j'obtiens les formules suivantes :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Ces formules nous permettent de déduire la valeur d'une retenue directement : il reste alors à créer un circuit qui implémente ces formules, et le tour est joué.
[[File:Four bit adder with carry lookahead.svg|centre|vignette|upright=1.5|Additionneur à anticipation de retenue de 4 bits.]]
Plus haut, j'ai dit que le circuit était composé de trois couches : une couche de demi-additionneur pour les signaux P et G ; un circuit d'anticipation de retenue, puis l'addition finale. Mais les deux bits d'opérandes sont déjà additionnés dans la couche de demi-additionneur, autant réutiliser le résultat pour l'additionner à la retenue. Pour éliminer toute redondance de ce type, il est possible d'utiliser des additionneurs partiels (des additionneurs où la sortie de retenue a été remplacée par deux sorties P et G). L'additionneur final est alors composé de plusieurs additionneurs complets de ce type, couplé à un circuit d'anticipation de retenue.
[[File:Cla4bits.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue de 4 bits.]]
Les additionneurs à anticipation de retenue sont plus rapides que les additionneurs à propagation de retenue. La raison est que les retenues sont calculées rapidement, dans un temps plus petit qu'en propageant la retenue. Pour rentrer dans les détails mathématiques, le temps de calcul n'est pas proportionnel au nombre de bits, mais proportionnel au logarithme de ce nombre de bits. Par contre, le gain en temps de calcul s'accompagne d'un cout en portes logiques conséquent. Le circuit qui calcule les retenues anticipées utilise beaucoup de portes logiques.
===Les additionneurs à calcul parallèle de préfixe===
Du fait du cout en portes logiques, utiliser un additionneur à anticipation de retenue est une solution pratique pour additionner 4 ou 8 bits. Mais pour des opérandes plus grands, de 16 ou 32 bits, cela utiliserait trop de portes logiques. Une partie de ce cout en portes logiques est cependant lié à des redondances. Si on analyse deux retenues différentes, à des colonnes différentes. Les circuits qui calculent ces deux retenues seront différents, mais ils contiendront des sous-circuits identiques. Pour vous en rendre compte, reprenons les quatre équations obtenues précédemment et essayez de trouver des redondances :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Vous devriez trouver :
* le terme ''P0 · C0'' dans toutes les lignes ;
* les termes ''P1 · P0 · C0'' et ''P1 · P0'' dans les trois dernières lignes ;
* le terme ''P2 · P1 · P0 · C0'' ; ''P2 · P1 · G0'' et ''P2 · G1'' dans les deux dernières lignes.
Il existe plusieurs manières d'exploiter de telles redondances, exploitées dans les '''additionneurs à calcul parallèle de préfixe'''. L'optimisation apportée est de générer des signaux ''propagate'' et ''generate'' pour des groupes de 2, 3, 4, ..., N colonnes. Par exemple, il est possible de générer un signal ''P 0 vers 3'', qui indique si la retenue de la première colonne est propagée jusqu'à la 4ème colonne. Un autre exemple est un signal qui indique qu'une retenue a été générée entre les colonnes 4 à 7 génèrent une retenue ou non. En clair, les signaux P et G ont maintenant un intervalle, qui précise de quelle colonne vers quelle colonne se fait la propagation, ou entre quelles colonnes se fait la génération.
Il est possible de combiner les signaux P et G de deux groupes de colonne, s'ils sont contiguës (ils peuvent aussi se recouvrir). Par exemple, il est possible de calculer les bits P et G pour les colonnes 0 à 10, à partir des deux signaux P/G des colonnes 0-4 et 5-10. Pour cela, les équations sont assez simples. Si je prends deux groupes nommés A et B, avec A pour les colonnes de poids fort et B celles de poids faible, on a :
: <math>P_{sortie} = P_A . P_B</math>
: <math>G_{sortie} = G_A . P_A . G_B</math>
Pour les bits des colonnes 2, 4, 8, 16, 32, 64, et autres, le calcul est simple. L'idée est de grouper les colonnes par groupes de 2. Puis, on calcule chaque groupe de 4 bits à partir de deux groupes de 2. Puis les groupes de 8 bits sont calculés à partir de deux groupes de 4 bits, et ainsi de suite pour toute puissance de deux.
[[File:Squelette d'un additionneur à calcul parallèle de préfixe.jpg|centre|vignette|upright=2|Squelette d'un additionneur à calcul parallèle de préfixe]]
Les seules difficultés surviennent pour les colonnes dont le nombre n'est pas une puissance de deux. Il y a plusieurs manières pour subdiviser ces colonnes en intervalles plus petits et combiner le tout. Et elles donnent chacune des additionneurs différents. Ils portent les noms d'''additionneur de Ladner-Fisher'', d'''additionneur de Brent-Kung'', d'''additionneur de Kogge-Stone'', etc. Dans le cas général, l'additionneur de Kogge-Stone est un des plus rapide, mais aussi un des plus gourmand en circuits, alors que c'est l'inverse pour l'additionneur de Brent-Kung. Les deux additionneurs sont illustrés ci-dessous, pour des opérandes de 8 bits. Dans ceux-ci, chaque losange calculent les bits P et G, à partir des signaux P et G de deux groupes précédents.
{|
|[[File:Brent-kung-8-bit.png|vignette|Brent-kung-8-bit]]
|[[File:Kogge-stone-8-bit.png|vignette|upright=1.2|Kogge-stone-8-bit]]
|}
===Le calcul parallèle de la retenue===
L'anticipation de retenue est très rapide, mais utilise beaucoup de circuits, même en utilisant le calcul parallèle de préfixe. Les additionneurs à propagation de retenue font eux le compromis inverse. Mais il existe des intermédiaires, qui visent à obtenir un compromis entre performance et cout en portes logiques.
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Un de ces compromis découpe un additionneur de 16/32/64 bits en additionneurs de 4/5 bits, qui sont enchainés en utilisant la propagation de retenue. Par contre, ces petits additionneurs de 4/5 bits utilisent l'anticipation de retenue. Ces additionneurs simples seront nommés ''blocs'' dans ce qui suit, et l'un d'entre eux est illustré ci-contre. Vous remarquerez qu'un bloc a une retenue entrante et une retenue sortante. Par exemple, on peut utiliser 4 additionneurs à anticipation de retenue de 4 bits, et propager les retenues entre eux. Le premier bloc calcule une retenue, qui est propagée au second bloc. Le second bloc calcule lui aussi une retenue, qui est propagée au troisième bloc, etc.
[[File:4008 Functional Diagram.svg|vignette|upright=1|4008 Functional Diagram]]
Le gain en performance est significatif, mais il est possible de faire presque aussi rapide, tout en économisant beaucoup de circuits. L'idée est de simplifier les additionneurs 4 bits, afin de ne pas utiliser une anticipation de retenue complète. L'anticipation de retenue n'est utilisée que pour la retenue sortante, alors que les bits de somme sont calculées avec propagation de retenue. La retenue sortante est donc calculée en parallèle de l'addition. Chaque bloc contient, à côté d'un additionneur proprement dit, un circuit qui calcule la retenue sortante. Il existe de nombreuses manières de calculer la retenue sortante.
Le gain en portes logiques est assez significatif. En effet, on se passe de circuits d'anticipation de retenue pour les 4 bits de somme, on ne garde que celui pour la retenue sortante. Le fait d'utiliser la propagation de retenue pour calculer les 4 bits de somme est censé avoir un léger cout en performance, mais il se trouve que ce cout est très faible. En effet, le résultat n'est connu qu'une fois les retenues propagées jusqu'au dernier bloc. Et malgré l'anticipation des retenues, la propagation est assez lente. les additionneurs à propagation de retenue auront terminé leur travail bien avant.
Un exemple est celui de l'additionneur CMOS 4008, un additionneur de 4 bit. Il est composé en trois sections. Une première couche de demi-additionneurs calcule les signaux P et G utilisés à la fois pour l'anticipation de la retenue sortante, que pour calculer les bits de somme. Le circuit de calcul de la retenue sortante utilise l'anticipation de retenue. Le reste du circuit fait l'addition en propageant les retenues.
[[File:CMOS 4008, circuit découpé en sections.png|centre|vignette|upright=1.5|CMOS 4008, circuit découpé en sections]]
===Les additionneurs à anticipation de retenue multi-niveau===
Les additionneurs précédents mixent anticipation et propagation de retenue. Ceux que nous allons voir utilisent des blocs, mais utilisent l'anticipation de retenue entre les blocs. Les blocs utilisés peuvent être un additionneur à propagation de retenue, ou à anticipation de retenue, peu importe. Ce qui compte est que la retenue entrante de chaque bloc est calculée par anticipation de retenue.
Par exemple, il est possible de créer un additionneur 16 bits en utilisant 4 additionneurs/blocs de 4 bits, couplé à un circuit d’anticipation de retenue par bloc. Pour cela, les additionneurs de 4 bits doivent fournir deux sorties P et G. Les deux sorties indiquent que le bloc soit a généré une retenue, soit a propagé la retenue entrante. Par propagé, on veut dire que la retenue entrante du bloc s'est retrouvée sur la sortie de retenue du bloc. C'est le cas si les 4 colonnes propagent la retenue, ce qui signifie que :
: <math>P_\text{4 bits} = P_0 \cdot P_1 \cdot P_2 \cdot P_3</math>
Pour la génération, cela veut dire qu'une colonne a générée une retenue, qui a été propagée jusqu'à la sortie. En clair, pour chaque colonne, il faut que le G = 1, et que les signaux P des colonnes soient à 1. Pour les 4 colonnes, cela donne :
: <math>G_\text{4 bits} = G_3 + G_2 \cdot P_3 + G_1 \cdot P_3 \cdot P_2 + G_0 \cdot P_3 \cdot P_2 \cdot P_1</math>
[[File:Cla4bitsPG.png|centre|vignette|upright=2|Additionneur à anticipation de retenue de 4 bits.]]
Pour combiner 4 blocs de 4 bits, on devra donc combiner 4 bits P et G, avec un circuit d'anticipation de retenue.
[[File:16-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 16 bits.]]
Il est même possible d'aller plus loin et de combiner 4 additionneurs précédents pour obtenir un additionneur 64 bits.
[[File:64-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 64 bits.]]
Le résultat est un additionneur à plusieurs niveaux d’anticipation de retenue. Une anticipation de retenue dans un blocs de 4 bits (facultative), entre blocs de 4 bits, entre blocs de 16 bits. La même logique peut être utilisée avec des blocs de taille différente de 4, 16 et 64 bits.
==L'addition signée et la soustraction==
Après avoir vu l'addition, il est logique de passer à la soustraction, les deux opérations étant très proches. Si on sait câbler une addition entre entiers positifs, câbler une soustraction n'est pas très compliqué. De plus, la soustraction permet de faire des additions de nombres signés.
===Le soustracteur pour opérandes entiers===
Pour soustraire deux nombres entiers, on peut adapter l'algorithme de soustraction utilisé en décimal, celui que vous avez appris à l'école. Celui-ci ressemble fortement à l'algorithme d'addition : on soustrait les bits de même poids, et on propage éventuellement une retenue sur la colonne suivante. À la différence de l'addition, la retenue est soustraite, et non ajoutée. La table de soustraction nous dit quel est le résultat de la soustraction de deux bits. La voici :
* 0 - 0 = 0 ;
* 0 - 1 = 1 et une retenue ;
* 1 - 0 = 1 ;
* 1 - 1 = 0.
[[File:Algebra1 05 fig014.svg|centre|vignette|Soustraction en binaire, avec les retenues en rouge.]]
La table de soustraction peut servir de table de vérité pour construire un circuit qui soustrait deux bits. Celui-ci est appelé un '''demi-soustracteur'''. Il ressemble beaucoup à un demi-additionneur, les différences se résumant à une porte NON ajoutée pour le calcul de la retenue.
[[File:Half Subtractor Vektor.svg|centre|vignette|Demi-soustracteur.]]
Comme pour l'additionneur, deux demi-soustracteurs peuvent être combinés pour donner un '''soustracteur complet'''. Le calcul de la retenue se fait en combinant les deux retenues des demi-soustracteurs avec une porte OU. Les soustracteurs complets sont utilisés pour créer des soustracteurs à propagation de retenue ou tout autre circuit soustracteur, sur le même modèle que les additionneurs.
[[File:FullSubtractor.svg|centre|vignette|upright=2|Soustracteur complet.]]
Vous l'aurez sans doute remarqué, mais le demi-soustracteur ressemble beaucoup au demi-additionneur. La seule différence est une porte NON ajoutée au bon endroit dans le demi-soustracteur.
{|
|[[File:Half-adder.svg|vignette|Demi-additionneur]]
|[[File:Half Subtractor Vektor.svg|vignette|upright=1.3|Demi-soustracteur]]
|}
La conséquence est qu'il est possible de créer un circuit capable de faire à la fois des additions et des soustractions. L'idée est de remplacer la porte NON du demi-soustracteur par un inverseur commandable. Si on envoie un zéro sur l'entrée de commande, l'inverseur commandable se comportera comme une porte OUI. Le circuit sera alors identique à un demi-additionneur. Si on envoie un 1 sur l'entrée de commande, l'inverseur commandable se comportera comme une porte NON. le circuit sera alors un demi-soustracteur. Il faut faire cela pour chaque demi-soustracteur, ce qui donne ceci pour un soustracteur complet. le résultat est alors appelé un '''additionneur-soustracteur complet'''.
[[File:Additionneur-soustracteur complet.png|centre|vignette|upright=2|Additionneur-soustracteur complet]]
===L'additionneur-soustracteur pour opérandes codées en complément à deux===
Étudions le cas de la soustraction en complément à deux, dans l'objectif de créer un circuit soustracteur. Vous savez sûrement que a−b et a+(−b) sont deux expressions équivalentes. Et en complément à deux, − b = not(b) + 1. Dit autrement, a − b = a + not(b) + 1. On pourrait se dire qu'il faut deux additionneurs pour faire le calcul, mais la majorité des additionneurs possède une entrée de retenue pour incrémenter le résultat de l'addition. Un soustracteur en complément à deux est donc simplement composé d'un additionneur et d'un inverseur.
[[File:Additionneur soustracteur.png|centre|vignette|upright=2|Soustracteur en complément à deux.]]
Il est possible de créer un circuit capable d'effectuer soit une addition, soit une soustraction : il suffit de remplacer l'inverseur par un inverseur commandable, qui peut être désactivé. On a vu comment créer un tel inverseur commandable dans le chapitre sur les circuits combinatoires. On peut remarquer que l'entrée de retenue et l'entrée de commande de l'inverseur sont activées en même temps : on peut fusionner les deux signaux en un seul.
[[File:Additionneur-soustracteur en complément à deux.png|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux.]]
Une implémentation alternative est la suivante. Elle remplace l'inverseur commandable par un multiplexeur.
[[File:4-bit ripple carry adder-subtracter.svg|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux, version alternative.]]
===L'additionneur-soustracteur pour opérandes codées en signe-magnitude===
Passons maintenant aux nombres codés en signe-valeur absolue, les deux opérandes étant notées A et B. Suivant les signes des deux opérandes, on a quatre cas possibles : A + B, A − B (B négatif), −A + B (A négatif) et −A − B (A et B négatifs). Une astuce est que le circuit n'a besoin que de calculer A + B et A − B : il peut les inverser pour obtenir − A − B ou B − A. A + B et A − B peuvent se calculer avec un additionneur-soustracteur, reste à corriger le résultat. Il suffit de lui ajouter un inverseur commandable pour obtenir le circuit d'addition finale.
[[File:Additionneur en signe-valeur absolue.png|centre|vignette|upright=2|Additionneur en signe-valeur absolue.]]
Toute la difficulté tient dans le calcul du bit de signe du résultat, quand interviennent des soustractions. Autant l'addition de deux nombres de même signe (A + B et −A − B) ne pose aucun problème, autant les soustractions posent problème (A − B et −A + B). Suivant que <math>A<B</math> ou que <math>A>B</math>, le signe du résultat ne sera pas le même. Déterminer le signe du résultat se fait en regardant les bits de débordement d'entier, comme on le verra plus bas.
===L'additionneur-soustracteur pour opérandes codées en représentation par excès===
Passons maintenant aux nombres codés en représentation par excès. On pourrait croire que ces nombres s'additionnent comme des nombres non-signés, mais ce serait oublier la présence du biais, qui pose problème. Dans les cas de nombres signés gérés avec un biais, voyons ce que donne l'addition de deux nombres :
: <math>( n_1 + biais ) + ( n_2 + biais ) = n_1 + n_2 + 2 \times biais</math>
Or, le résultat correct serait :
: <math>n_1 + n_2 + biais</math>
En effectuant l'addition telle quelle, le biais est compté deux fois. On doit donc le soustraire après l'addition pour obtenir le résultat correct.
Même chose pour la soustraction qui donne ceci :
: <math>(n_1 + biais) - (n_2 + biais) = n_1 - n_2</math>
Or, le résultat correct serait :
: <math> ( n_1 - n_2 ) + biais </math>
Il faut rajouter le biais pour obtenir l'exposant correct.
On a donc besoin de deux additionneurs/soustracteurs : un pour additionner/soustraire les représentations binaires des opérandes, et un autre pour ajouter/retirer le biais en trop/manquant.
==L'additionneur BCD==
Maintenant, voyons un additionneur qui additionne deux entiers au format BCD. Pour cela, nous allons devoir passer par deux étapes. La première est de créer un circuit capable d'additionneur deux chiffres BCD. Ensuite, nous allons voir comment enchaîner ces circuits pour créer un additionneur BCD complet.
===L'additionneur BCD qui fait l'opération chiffre par chiffre===
Nous allons commencer par voir un additionneur qui additionne deux chiffres en BCD, une sorte d'équivalent BCD de l'additionneur complet. Il fournit un résultat sur 4 bits et une retenue qui est mise à 1 si le résultat dépasse 10 (la limite d'un chiffre BCD). Les deux opérandes sont des chiffres BCD codés sur 4 bits et sont additionnés en binaire par un additionneur des plus normaux, similaire à ceux vus plus haut. Le résultat est alors un entier codé en binaire, sur 5 bits, qu'on corrige/convertit pour obtenir un chiffre BCD et une retenue sortante.
Pour corriger le résultat, une idée intuitive serait de prendre le résultat et de faire une division par 10. Le quotient donne la retenue, alors que le reste est le résultat, le chiffre BCD. Mais un circuit diviseur par 10 utilise beaucoup de portes logiques, ce qui ne vaut pas le coup. Une autre méthode détecte si le résultat est égal ou supérieur à 10, ce qui correspond à un "débordement" (on dépasse les limites d'un chiffre BCD). Si le résultat est plus petit que 10, il n'y a rien à faire : le résultat est bon et la retenue est de zéro. Par contre, si le résultat vaut 10 ou plus, il faut corriger le résultat et générer une retenue à 1.
Il faut donc ajouter un circuit qui détecte si le résultat est supérieur à 9, qui calcule directement la retenue. Ce circuit peut se fabriquer simplement à partir de sa table de vérité, ou en utilisant les techniques que nous verrons dans un chapitre ultérieur sur les comparateurs. La solution la plus simple est clairement d'utiliser la table de vérité, ce qui est très simple, assez pour être laissé en exercice au lecteur. Pour comprendre comment corriger le résultat, établissons une table de vérité qui associe le résultat et le résultat corrigé. L'entrée vaut au minimum 10 et au maximum 9 + 9 = 18. On considère la sortie comme un tout, la retenue étant un 5ème bit, le bit de poids fort.
{|class="wikitable"
|-
! colspan="5" | Entrée
!
! rowspan="10" |
! Retenue
! Résultat corrigé (sans retenue)
! interprétation de la sortie en binaire (retenue inclue)
|-
| 0 || 1 || 0 || 1 || 0
| (10)
| 1 || 0000
| (16)
|-
| 0 || 1 || 0 || 1 || 1
| (11)
| 1 || 0001
| (17)
|-
| 0 || 1 || 1 || 0 || 0
| (12)
| 1 || 0010
| (18)
|-
| 0 || 1 || 1 || 0 || 1
| (13)
| 1 || 0011
| (19)
|-
| 0 || 1 || 1 || 1 || 0
| (14)
| 1 || 0100
| (20)
|-
| 0 || 1 || 1 || 1 || 1
| (15)
| 1 || 0101
| (21)
|-
| 1 || 0 || 0 || 0 || 0
| (16)
| 1 || 0110
| (22)
|-
| 1 || 0 || 0 || 0 || 1
| (17)
| 1 || 0111
| (23)
|-
| 1 || 0 || 0 || 1 || 0
| (18)
| 1 || 1000
| (24)
|}
En analysant le tableau, on voit que pour corriger le résultat, il suffit d'ajouter 6. La raison est que le résultat déborde d'un nibble à 16 en binaire, mais à 10 en décimal : il suffit d'ajouter la différence entre les deux, à savoir 6, et le débordement binaire fait son travail. Donc, la correction après une addition est très simple : si le résultat dépasse 9, on ajoute 6.
On peut maintenant implémenter l'additionneur BCD, en combinant le comparateur avec 10, le circuit de correction, et l'additionneur. La première solution calcule deux versions du résultat : la version corrigée, la version normale. Le choix entre les deux est réalisée par un multiplexeur, commandé par le comparateur.
[[File:Additionneur BCD.png|centre|vignette|upright=1.5|Additionneur BCD]]
L'autre solution utilise un circuit commandable qui soit additionne 6, soit ne fait rien. Le choix entre les deux est commandé par le bit calculé par le comparateur.
[[File:Additionneur BCD, seconde version.png|centre|vignette|upright=1.5|Additionneur BCD, seconde version.]]
Une version alternative du circuit précédent est la suivante. Il contient deux additionneurs : un pour additionner les deux chiffres BCD, un autre pour additionner 6 si besoin. Le résultat du comparateur est directement utilisé pour générer l'opérande du second additionneur : 0 ou 6. Le circuit est simple à concevoir, mais gaspille beaucoup de circuit. Idéalement, il vaudrait mieux utiliser un circuit combinatoire d'addition avec une constante.
[[File:Adder 4 Bit BCD.svg|centre|vignette|upright=2|Additionneur BCD, circuit complet.]]
Pour obtenir un additionneur BCD complet, il suffit d’enchaîner les additionneurs précédents, comme on le ferait avec les additionneurs complets dans un additionneur à propagation de retenue. Au final, l'additionneur BCD est beaucoup plus compliqué qu'un additionneur normal, car il rajoute un comparateur ">9", un petit additionneur pour ajouter 6 et éventuellement d'autres circuits. De plus, il est difficile d'appliquer les optimisations disponibles sur les additionneurs non-BCD. Notamment, les circuits d'anticipation de retenue sont totalement à refaire et le résultat est relativement compliqué. C'est ce qui explique pourquoi le BCD a progressivement été abandonné au profit du binaire simple.
La soustraction en BCD se fait comme en binaire : le nombre à soustraire est remplacé par son complément, le circuit additionne le complément et l'autre opérande, le débordement d'entier fait que le résultat marche. Sauf qu'ici, le complément est un complément à 9. Il se calcule chiffre par chiffre : chaque chiffre est remplacé par (9 - le chiffre en question).
===L'additionneur BCD par ajustement décimal===
L'additionneur BCD précédent effectuait son travail chiffre BCD par chiffre BCD, mais il existe des additionneurs BCD qui font autrement. Sur les premiers processeurs x86, il n'y avait pas d'opération d'addition BCD proprement dit, seulement une addition binaire normale de 8, 16 ou 32 bits. Par contre, elle était secondée par une opération dite d''''ajustement décimal''' qui transformait un nombre binaire en nombre codé en BCD. L'opération d'ajustement décimal prenait un opérande de 8 bits codé en binaire et fournissait un résultat de la même taille, c'est à dire deux chiffres BCD. Effectuer une addition BCD demandait donc de faire deux opérations à la suite : une addition binaire simple, suivie par l'opération d'ajustement décimal. Cela permettait de gérer des nombres entiers en binaire usuel et des entiers BCD sans avoir deux instructions d'addition séparées pour les deux, sans compter que cela simplifiait aussi les circuits d'addition.
L'ajustement décimal s'effectue en ajoutant une constante bien précise à l'opérande à convertir en BCD. L'idée est que la constante est découpée en morceaux de 4 bits, correspondant chacun à un chiffre BCD de l'opérande, chaque morceau contenant soit un 0, soit 6. Cela permet d'ajouter soit 0, soit 6, à chaque chiffre BCD, et donc de le corriger. La propagation des retenues d'un chiffre à l'autre est effectuée automatiquement par l'addition binaire de la constante. L'opération d'ajustement décimal calcule automatiquement la constante. Elle découpe l'opérande en ''nibbles'', vérifie si chaque ''nibble'' est supérieur ou égal à 10, puis détermine la valeur de chaque ''nibble'' de la constante finale. Par exemple, si je prends l'opérande 1001 1110, le ''nibble'' de poids faible déborde, alors que celui de poids fort non. La constante sera donc 0000 0110 : 0x06. Inversement, si le ''nibble'' de poids fort déborde et pas celui de poids faible, la constante sera alors 0x60. Et la constante est de 0x66 si les deux ''nibbles'' débordent, de 0x00 si aucun ne déborde.
Le circuit d’ajustement décimal est donc composé de trois étapes : deux étapes pour calculer la constante, et un circuit d'addition pour additionner cette constante au nombre de départ. La première étape découpe l'opérande en morceaux de 4 bits, en chiffres BCD, et vérifie si chacun d'entre eux vaut 10 ou plus. La seconde étape prend les résultats de la première étape, et les combine pour calculer la constante. Enfin, on trouve l'addition finale, qui était réalisée par un circuit d'addition utilisé à la fois pour l'ajustement décimal et l'addition binaire. La différence entre une addition normale et une opération d'ajustement décimal tient dans le fait que les deux premières étapes sont désactivées dans une addition normale.
[[File:Additionneur BCD parallèle.png|centre|vignette|upright=1.5|Additionneur BCD parallèle]]
===L'additionneur biquinaire===
Les entiers BCD ne sont qu'un des encodages hybrides entre décimal et binaire. L'encodage biquinaire est l'un d'entre eux et nous allons faire un rappel rapide à ce sujet. Pour simplifier, un chiffre encodé en biquinaire est composé de deux parties : un bit, couplé à une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits (0, 1, 2, 3 et 4). Le bit indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9.
Additionner deux nombres de biquinaire demande donc d'additionner deux parties quinaires encodées en ''one-hot'' et d'additionner deux bits. Mais attention : il faut tenir compte de la retenue de l'addition des parties quinaires. Et idéalement, il faut aussi tenir compte d'une retenue entrante, provenant de l'addition de la colonne de chiffres précédente. Toute la difficulté vient de la création de l'additionneur ''one-hot''. Heureusement, vu qu'il n'y a que 4-5 bits à additionner, il est souvent fabriqué à partir de sa table de vérité.
[[File:Additionneur bi-quinaire.png|centre|vignette|upright=2|Additionneur bi-quinaire]]
Un avantage du biquinaire est que le calcul du complément à 9 est très simple. Il faut pour cela : inverser la partie binaire avec une porte NON, puis inverser l'ordre des bits de la partie quinaire. Concrètement, le bit de poids faible devient le bit de poids fort, et ainsi de suite. Par exemple, une partie quinaire 01000 devient 00010, 10000 devient 00001, 00100 ne change pas, etc. Le tout peut se calculer avec une porte NON et 5 multiplexeurs.
===L'additionneur BCD avec calculs intermédiaires en biquinaire===
L'ordinateur IBM 1401, un ancien ''mainframe'' des années 60, utilisait un additionneur BCD un peu particulier. Les nombres étaient encodés en BCD dans la mémoire de l'ordinateur, mais les circuits de calcul utilisaient la représentation biquinaire. Lors d'un calcul, le processeur de l'ordinateur traduisait les chiffres BCD en représentation biquinaire, faisait une addition en biquinaire, avant de traduire le résultat en BCD normal.
Pour être précis, l'IBM 1401 utilisait une variante du biquinaire. L'encodage biquinaire de l'IBM 1401 est le suivant : la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre pair encodé par la partie quinaire. Si l'IBM 1401 utilisait cette variante du biquinaire, c'est car elle donnait des circuits de conversion BCD-biquinaire plus économes en portes logiques et plus rapides.
La partie binaire est le bit de poids faible du chiffre BCD, la partie biquinaire est calculée par un simple décodeur qui prend en entrée le chiffre BCD, amputé de son bit de poids faible. La traduction inverse demande d'utiliser un encodeur, à la place du décodeur. Par contre, le circuit d'addition biquinaire était plus compliqué du fait de la gestion des retenues. L'addition des parties binaires et quinaires se faisait en parallèle, dans deux additionneurs séparés. Cependant, l'addition des parties binaire fournit une retenue, qu'il faut prendre en compte. Pour cela, l'IBM 1401 disposait d'un troisième additionneur qui fournissait le résultat final, encodé en biquinaire.
[[File:Additionneur bi-quinaire de l'IBM 1401.png|centre|vignette|upright=2|Additionneur biquinaire de l'IBM 1401]]
Une implémentation moderne demanderait d'utiliser des portes ET combinées à des portes OU, le circuit pouvant être construit simplement à partir de sa table de vérité. Sur l'IBM 1401, le circuit était cependant différent, en raison de l'utilisation de OU câblés, des croisements de fils qui fonctionnent comme des portes OU, que nous n'avons pas encore vu pour le moment, mais qui seront détaillés dans quelques chapitres. Les OU câblés étaient utilisés pour simplifier le design du circuit, mais demandaient des portes logiques spécifiques, ce qui collait avec le fait que ce ''mainframe'' utilisait des transistors en Germanium. L'implémentation exacte est décrite dans cet article de blog, mais je ne recommande sa lecture qu'à ceux qui savent ce qu'est un OU câblé :
* [https://www.righto.com/2015/10/qui-binary-arithmetic-how-1960s-ibm.html Qui-binary arithmetic: how a 1960s IBM mainframe does math].
==Les débordements d'entier lors d'une addition/soustraction==
Les instructions arithmétiques manipulent des entiers codés sur un nombre fixe de bits, qui ne peuvent prendre leurs valeurs que dans un intervalle. Pour les nombres positifs, un ordinateur qui code ses entiers sur n bits pourra coder tous les entiers allant de 0 à <math>2^n -1</math>. Pour les nombres négatifs, l'intervalle est différent et dépend de la représentation utilisée. Dans le cas général, l'ordinateur peut coder les valeurs comprises de <math>N_\text{min}</math> à <math>N_\text{max}</math>. Si le résultat d'un calcul sort de cet intervalle, il ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math> , alors que la valeur basse vaut <math>N_\text{min} - 1</math> (avec <math>N_\text{max}</math> et <math>N_\text{min}</math> respectivement la plus grande et la plus petite valeur codable par l'ordinateur).
===La correction des débordements d'entier : l'arithmétique saturée===
Quand un débordement d'entier survient, tous les circuits de calcul ne procèdent pas de la même manière. Dans les grandes lignes, il y a deux réactions possibles : soit on corrige automatiquement le résultat du débordement, soit on ne fait rien et on se contente de détecter le débordement.
Si le débordement n'est pas corrigé automatiquement, le circuit ne conserve que les bits de poids faibles du résultat. Les bits en trop sont simplement ignorés. On dit qu'on utilise l''''arithmétique modulaire'''. Le problème avec ce genre d'arithmétique, c'est qu'une opération entre deux grands nombres peut donner un résultat très petit. Par exemple, si je dispose de registres 4 bits et que je souhaite faire l'addition 1111 + 0010 (ce qui donne 15 + 2), le résultat est censé être 10001 (17), ce qui est un résultat plus grand que la taille d'un registre. En conservant les 4 bits de poids faible, j’obtiens 0001 (1). En clair, un résultat très grand est transformé en un résultat très petit. Cela peut poser problèmes si on travaille uniquement avec des nombres positifs, mais c'est aussi utilisé pour coder des nombres en complément à deux.
D'autres circuits utilisent ce qu'on appelle l''''arithmétique saturée''' : si un calcul génère un débordement, on arrondi le résultat au plus grand entier supporté par le circuit. Les circuits capables de calculer en arithmétique saturée sont un peu plus complexes, vu qu'il faut rajouter des circuits pour corriger le résultat en cas de débordement. Il suffit généralement de rajouter un ''circuit de saturation'', qui prend en entrée le résultat et le corrige en cas de débordement. Ce circuit de saturation met la valeur maximale en sortie si un débordement survient, mais se contente de recopier le résultat du calcul sur sa sortie s'il n'y a pas de débordement. Typiquement, il est composé d'une couche de multiplexeurs, qui sélectionnent quelle valeur mettre sur la sortie : soit le résultat du calcul, soit le plus grand nombre entier géré par le processeur, soit le plus petit (pour les nombres négatifs/soustractions).
L'arithmétique saturée est utilisée pour les additions et soustractions, mais c'est plus rare pour les multiplications/divisions. Une des raisons est que le résultat d'une addition/soustraction prend un bit de plus que le résultat, là où les multiplications doublent le nombre de bits. Quand une addition déborde, le résultat réel est proche de la valeur maximale codable. mais quand une multiplication déborde, le résultat peut parfois valoir 200 à 60000 fois plus que la valeur maximale codable. Les calculs avec une valeur saturée/corrigée sont donc crédibles pour une suite d'additions, mais pas pour une suite de multiplications.
===La détection des débordements entiers===
Quand un débordement d'entier a eu lieu, il vaut mieux que l'additionneur prévienne ! Pour cela, l'additionneur a une '''sortie de débordement''', parfois nommée ''Overflow'', dont la valeur indique si l'addition a généré un débordement d'entier ou non. Reste que détecter un débordement ne se fait pas de la même manière selon que l'on parle d'un additionneur non-signé ou signé.
Pour les additionneur non-signés, l'additionneur calcule un bit de plus que ce qui est supporté par l'ordinateur. Par exemple, un additionneur 32 bits fournit un résultat sur 33 bits, un débordement d'entier a lieu quand le 33ème bit est à 1. Précisément, la sortie de débordement n'est autre que la retenue finale, celle fournie par le dernier additionneur complet. Le seul type de débordement possible est un débordement par le haut, où le résultat dépasse la valeur maximale. Avec l'arithmétique saturée, le circuit de saturation consiste en une seule couche de multiplexeurs, voire en un circuit de mise à la valeur maximale tel que vu dans le chapitre sur les opérations bits à bits.
[[File:Gestion des débordements d'entiers.png|centre|vignette|upright=2.5|Gestion des débordements d'entiers lors d'une addition non-signée.]]
Pour les additionneurs non-signés, la gestion des débordements d'entiers dépend fortement de la représentation signée. Nous allons étudier le cas du complément à deux. Si vous vous rappelez le chapitre 1, les calculs sur des nombres en complètement à deux utilisent les règles de l'arithmétique modulaire, c'est une condition nécessaire. À priori, on peut penser que dans ces conditions, les débordements d'entiers sont une chose parfaitement normale, qui nous permet d'avoir des résultats corrects. Néanmoins, certains débordements d'entiers peuvent survenir malgré tout et produire des bugs assez ennuyeux.
Si l'on tient en compte les règles du complément à deux, on sait que le bit de poids fort (le plus à gauche) permet de déterminer si le nombre est positif ou négatif : il indique le signe du nombre. Tout se passe comme si les entiers en complément à deux étaient codés sur un bit de moins, et avaient leur longueur amputé du bit de poids fort. Si le résultat d'un calcul écrase le bit de poids fort, il y a un débordement d'entiers. Il existe une règle simple qui permet de détecter ces débordements d'entiers. L'addition de deux nombres positifs ne peut pas être un nombre négatif. Si on additionne deux nombres dont le bit de signe est à 0 et que le bit de signe du résultat est à 1, on est en face d'un débordement d'entiers. Même chose pour deux nombres négatifs : le résultat de l'addition ne peut pas être positif. On peut résumer cela en une phrase : si deux nombres de même signe sont ajoutés, un débordement a lieu quand le bit du signe du résultat a le signe opposé.
Modifier les circuits d'au-dessus pour qu'ils détectent les débordements en complément à deux est simple comme bonjour : il suffit créer un petit circuit combinatoire qui prenne en entrée les bits de signe des opérandes et du résultat, et qui fasse le calcul de l'indicateur de débordements. Si l'on rédige sa table de vérité, on doit se retrouver avec la table suivante :
{|class="wikitable"
|-
!Entrées
!Sortie
|-
|000||0
|-
|001||1
|-
|010||0
|-
|011||0
|-
|100||0
|-
|101||0
|-
|110||1
|-
|111||0
|}
L'équation de ce circuit est la suivante, avec <math>S_a</math> et <math>S_b</math> les signes des deux opérandes, et <math>C_i</math> la retenue de la colonne précédente :
: <math> ( S_a . S_b . \overline{C_i} ) + ( \overline{S_a} . \overline{S_b} . C_i )</math>
En simplifiant, on obtient alors :
: <math>( S_a . S_b ) \oplus C_i</math>
Or, il se trouve que <math>S_a . S_b</math> est tout simplement la retenue en sortie du dernier additionneur, que nous noterons <math>C_o</math>. On trouve donc :
: <math>C_o \oplus C_i</math>
Il suffit donc de faire un XOR entre la dernière retenue et la précédente pour obtenir le bit de débordement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de décalage et de rotation
| prevText=Les circuits de décalage et de rotation
| next=Les circuits de comparaison
| nextText=Les circuits de comparaison
}}
</noinclude>
k0mnturrsu81st26jpgt1u8tu3j7wy0
768668
768667
2026-06-25T20:01:28Z
Mewtow
31375
/* L'addition non signée */
768668
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir les circuits capables de faire une addition ou une soustraction, ainsi que quelques circuits spécialisés. Précisons cependant que les fabricants de processeurs travaillent d'arrache-pied pour trouver des moyens de rendre ces circuits de calcul plus rapides et plus économes en énergie. Autant vous dire que les circuits que vous allez voir sont vraiment des circuits qui font pâle figure comparé à ce que l'on peut trouver dans un vrai processeur commercial !
==Les circuits pour additionner 2 ou 3 bits==
L'addition se fait en binaire de la même manière qu'en décimal. On additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. La soustraction fonctionne sur le même principe, sur le même modèle qu'en décimal.
[[File:Binary Addition Demonstration.svg|centre|vignette|Exemple d'addition en binaire.]]
En clair, additionner deux nombres demande d'additionner 2 bits et une retenue sur chaque colonne, et de propager les retenues d'une colonne à l'autre. La propagation des retenues est quelque chose de simple en apparence, mais qui est sujet à des optimisations extraordinairement nombreuses. Aussi, pour simplifier l'exposition, nous allons voir comment gérer une colonne avant de voir comment sont propagées les retenues.
[[File:Full Adder Block.svg|vignette|upright=1|class=transparent|Additionneur complet.]]
Si on effectue une addition en colonne, on doit additionner les deux bits sur la colonne, mais aussi additionner une éventuelle retenue. Il faut donc créer un circuit qui additionne trois bits : deux bits de données, plus une retenue. Ce circuit qui additionne trois bits est appelé un '''additionneur complet'''. Il fournit en sortie deux bits : un bit de somme et une retenue sortante. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|-
||0||0||0|| ||0||0
|-
||0||0||1|| ||0||1
|-
||0||1||0|| ||0||1
|-
||0||1||1|| ||1||0
|-
||1||0||0|| ||0||1
|-
||1||0||1|| ||1||0
|-
||1||1||0|| ||1||0
|-
||1||1||1|| ||1||1
|}
Il est possible d'utiliser un tableau de Karnaugh pour traduire la table de vérité, mais elle donne un résultat assez compliqué. La retenue et le bit de somme sont calculés à part. La retenue est calculée avec trois portes ET et une porte OU. Le bit de somme est calculé lui avec 4 portes ET, une porte OU, et trois portes NON.
[[File:Full Adler with 3 entries.png|centre|vignette|upright=2|Additionneur complet, fabriqué à partir de la table de vérité.]]
D'autres méthodes donnent des résultats plus compréhensibles. Nous allons les voir dans la suite de cette section.
===L'additionneur complet basé sur des demi-additionneurs===
[[File:1-bit half-adder.svg|class=transparent|vignette|Demi-addtionneur.]]
Nous avons déjà vu comment additionner deux bits dans le chapitre sur les incrémenteurs, mais quelques rappels ne seront pas de trop. Le '''demi-additionneur''' est un circuit qui additionne deux bits. Il implémente la table d'addition, qui est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Mais un demi-additionneur n'additionne que deux bits, il manque de quoi additionner la retenue. Une solution pour cela est d'enchaîner deux demi-additionneurs : un qui additionne les deux bits de données, et un second qui additionne la retenue au résultat du précédent. La retenue finale se calcule en combinant les sorties de retenue des deux demi-additionneurs, avec une porte OU. Le résultat est un additionneur complet fabriqué avec deux demi-additionneurs. Pour vous en convaincre, établissez la table de vérité de ce circuit, vous verrez que ça marche.
[[File:Full Adder Blocks.svg|centre|vignette|upright=2|Additionneur complet fabriqué avec deux demi-additionneurs.]]
[[File:Half-adder.svg|vignette|upright=1|Circuit d'un demi-additionneur naïf.]]
L'implémentation naïve d'un demi-additionneur utilise une porte XOR et une porte ET, comme illustré ci-contre. Le circuit s'obtient en faisant uen traduction littérale de la table de vérité du circuit. Si je dis qu'elle est naïve, c'est car il est possible de l'optimiser, de manière à éliminer des redondances cachées. Mais nous avions déjà vu cela dans le chapitre sur les incrémenteurs, aussi laissons cela pour plus tard.
En combinant deux demi-additionneurs de ce type, on obtient l'additionneur complet suivant. Les deux sorties de retenue des demi-additionneurs sont combinées avec une porte OU, pour calculer la retenue finale.
{| class="flexible"
|[[File:Full Adder Modules.svg|class=transparent|Composition d'un additionneur complet. On voit bien que celui-ci est composé de deux demi-additionneurs, en rouge et en bleu, auxquels on a ajouté une porte OU pour calculer la retenue finale.]]
|[[File:Full-adder.svg|300px|class=transparent|Circuit d'un additionneur complet.]]
|}
Le fait de combiner les deux retenues avec une porte OU n'est pas ce qu'il y a de plus intuitif. La table de vérité nous dit que ça fonctionne, mais on comprend mal pourquoi. Mais tout devient plus clair quand on sait que les deux retenues ne peuvent pas être à 1 en même temps. Même dans l'addition 1 + 1 + 1, seule une retenue est à 1. Soit l'addition des deux bits d'opérande donne naissance à une retenue, soit c'est l'addition ''résultat + retenue entrante''. La retenue sortant est donc à 1 quand une des deux addition donne une retenue. D'où l'usage d'une porte OU. Remarquez qu'une porte XOR donne le même résultat, vu que le cas où les deux retenues sont à 1 n'est jamais rencontré.
===La propagation et la génération des retenues===
L'additionneur complet que nous allons voir dans cette section sert à introduire les concepts de propagation, génération et absorption de retenue. Il s'agit de concepts très importants quand on étudie les additionneurs. l'idée part d'un principe très simple : la retenue sortante dépend de la retenue d'entrée. La relation entre les deux se résume à trois cas, qui dépendent de la valeur des deux bits additionnés, nommés A et B.
* Dans le premier cas, la retenue entrante est égale à la retenue sortante. On dit que la retenue entrante est propagée par l'additionneur.
* Dans le second cas, la retenue est forcée à 0 : la retenue sortante vaut 0, peu importe la valeur de la retenue entrante.
* Dans le troisième cas, la retenue est forcée à 1 : la retenue entrante vaut 1, peu importe la valeur de la retenue entrante.
[[File:Signaux P et G fournis par un demi-additionneur.png|vignette|Signaux P et G fournis par un demi-additionneur]]
Maintenant, créons un circuit qui nous dise si une retenue est propagée ou générée. Il a deux bits de sortie, nommés P et G : P pour ''Propagate'', G pour ''Generate''. Le bit P est à 1 si la retenue entrante est propagée, il est à 0 sinon. Le bit G est à 1 si une retenue est générée, à 0 sinon. Une retenue est considérée comme absorbée si elle n'est pas ni propagée ni générée, pas besoin d'un troisième bit pour gérer ce cas.
Il se trouve que ce circuit n'est autre qu'un demi-additionneur ! Pour vous en rendre compte, regardez la table de vérité d'un additionneur complet, illustrée ci-dessous. Lorsque les deux bits d'opérande sont à 0, la retenue sortante vaut toujours 0. Si ils sont tous deux à 1, alors la retenue sortante vaut 1. S'ils sont différents, alors retenues sortante et entrante sont égales. Le bit P est donc généré par une simple porte XOR. Quant au bit G, il est à 1 si les deux bits d'opérandes sont à 1, ce qui correspond à une porte ET. Il se trouve que ces deux portes forment un demi-additionneur !
{|class="wikitable"
|-
! Opérande 1 !! Opérande 2 !! Retenue entrante !! !! Retenue sortante
|- class="f_rouge"
||0||0||0|| ||0
|- class="f_rouge"
||0||0||1|| ||0
|- class="f_bleu"
||0||1||0|| ||0
|- class="f_bleu"
||0||1||1|| ||1
|- class="f_bleu"
||1||0||0|| ||0
|- class="f_bleu"
||1||0||1|| ||1
|- class="f_vert"
||1||1||0|| ||1
|- class="f_vert"
||1||1||1|| ||1
|}
Ensuite, créons un circuit qui prend ces deux sorties P et G, et calcule la retenue sortante en fonction. Le circuit en question a trois entrées : la retenue entrante, les deux bits P et G. La retenue sortante vaut 1 soit si une retenue est générée, soit si la retenue entrante est propagée et qu'elle vaut 1. En notant <math>{C_{in}}</math> et <math>{C_{out}}</math> les retenues entrantes et sortante, on a :
: <math>{C_{out}} = G + \left( P . C_{in} \right)</math>
Le circuit est donc composé d'une porte OU et d'une porte ET. En combinant un demi-additionneur avec le circuit de calcul de retenue sortante vu plus haut, on a :
[[File:Additionneur complet avec propagation et génération de retenue.png|class=transparent|centre|vignette|upright=2|Additionneur complet avec propagation et génération de retenue.]]
Certes, on retombe sur le circuit vu plus haut. Mais il est possible de remplacer le circuit en orange par un autre. Par exemple, il est possible de le remplacer par multiplexeur, qui choisit entre la retenue générée et la retenue entrante (propagée). Le choix se fait selon la valeur du bit P, qui chosiit entre propager la retenue et la générer.
[[File:Additionneur crée avec un multiplexeur.png|centre|vignette|upright=1.5|Additionneur crée avec un multiplexeur]]
Le circuit exact est illustré ci-dessous. Il semble utiliser plus de portes logiques que l'additionneur complet précédent. Cependant, nous verrons dans quelques chapitres qu'il est possible d'implémenter un multiplexeur avec seulement 6 transistors, voire moins ! L'implémentation utilise des portes à transmission, mais nous en reparlerons dans le chapitre sur les transistors, quand nous verrons les additionneurs à ''Manchester Carry Chain''. Au passage, une variante de ce circuit a été utilisée dans le processeur processeur 8086 d'Intel, comme on le verra dans le chapitre suivant.
[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
[[File:FulladderPG.png|vignette|upright=1|Additionneur complet avec deux sorties P et G.]]
Pour finir, sachez qu'il existe des additionneurs qui fournissent : le bit de résultat, deux sorties P et G qui indiquent si l'addition propage ou génère une retenue. Il ne s'agit pas d'additionneurs complets, car il manque de quoi calculer la retenue sortante. De tels additionneurs seront appelés des '''additionneurs partiels'''. De tels additionneurs sont utilisés dans certains additionneurs pour gagner en performance.
Enfin, il existe aussi ce que j'ai décidé d'appeler des '''additionneurs P/G''', qui sont des additionneurs complets auxquels on a ajouté deux sorties P et G, en plus de la sortie de retenue. Leur circuit est le même que celui d'un additionneur complet, auquel on a ajouté deux fils. Vu que ces deux sorties sont fournies par le premier demi-additionneur, ajouter ces deux sorties demande d'ajouter des fils, pas de portes logiques. De tels additionneurs sont aussi utilisés dans certains additionneurs pour gagner en performance.
===L'additionneur complet basé sur une porte à majorité===
Maintenant, voyons une dernière implémentation possible de l'additionneur complet. Mais avant de voir comment l'additionneur est implémenté, nous allons devoir faire un petit focus sur la retenue sortante. Reprenons la table de vérité, et regardons ce qu'il en est pour la retenue sortante uniquement.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante
|-
||0||0||0|| ||0
|-
||0||0||1|| ||0
|-
||0||1||0|| ||0
|-
||0||1||1|| ||1
|-
||1||0||0|| ||0
|-
||1||0||1|| ||1
|-
||1||1||0|| ||1
|-
||1||1||1|| ||1
|}
Vous remarquerez que la retenue sortante est égale au bit majoritaire, parmi les trois bits d'opérande. Si deux ou trois bits sont à 1, la retenue sortante vaut 1. Et réciproquement avec 0. Le circuit de calcul de la retenue peut donc être remplacé par une '''porte à majorité'''.
[[File:Additionneur crée avec une porte à majorité.png|centre|vignette|upright=2|Additionneur crée avec une porte à majorité]]
Mais une porte à majorité est assez gourmande en circuit, ce qui fait qu'un additionneur pareil ne serait pas pratique, ni utile. Du moins, ce serait le cas s'il n'y avait pas une possibilité d'optimisation extrêmement intéressante. Il est possible de calculer le 'bit de somme' à partir de la retenue sortante ! En effet, le bit de somme est l'inverse de la retenue sortante, sauf dans deux cas : les trois bits d'entrée sont à 0, où ils sont tous à 1. Le bit de somme vaut 0 dans le premier cas, 1 dans le second cas.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|- class="f_bleu"
||0||0||0|| ||0||0
|- class="f_vert"
||0||0||1|| ||0||1
|- class="f_vert"
||0||1||0|| ||0||1
|- class="f_vert"
||0||1||1|| ||1||0
|- class="f_vert"
||1||0||0|| ||0||1
|- class="f_vert"
||1||0||1|| ||1||0
|- class="f_vert"
||1||1||0|| ||1||0
|- class="f_rouge"
||1||1||1|| ||1||1
|}
En clair, 6 lignes de la table de vérité sur 8 peuvent se calculer avec une porte à majorité, le reste demandant quelques portes logiques pour faire la correction. L'implémentation en circuit demande donc trois choses :
* de calculer la retenue sortante et de quoi l'inverser ;
* un circuit qui vérifie si tous les bits opérande valent 0 : une porte NOR ou OU fait l'affaire ;
* un circuit qui vérifie s'ils valent tous 1, à savoir une porte ET.
Ensuite, on combine le résultat des trois circuits précédents pour obtenir le résultat final. La combinaison est le fait du circuit en jaune dans le schéma ci-dessous.
[[File:Full adder basé sur une modification de la retenue.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue]]
Le circuit en jaune est assez simple. Quand la porte ET sort un 1, la sortie doit être forcée à 1. Par contre, quand la porte OU sort un zéro, la sortie doit être forcée à 0. Il suffit donc d'enchainer un circuit de mise à 1 et un circuit de mise à 0. Nous avons vu dans le chapitre sur les opérations de masquage qu'il s'agit respectivement d'une porte OU et d'une porte ET.
[[File:Full adder basé sur une modification de la retenue interne, au complet.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue interne, au complet]]
Divers processeurs utilisaient des additionneurs complets de ce type : le fameux Z80, l'Intel 4004 et de l'Intel 8008 sont les exemples les plus notables. Pourtant, on pourrait s'étonner qu'un tel circuit ait existé. Il utilise beaucoup de portes logiques, a une profondeur logique supérieure : il n'a rien d'avantageux. Sauf qu'il était utilisé sur d'anciens processeurs, qui utilisaient la technologie dite TTL, différente de la technologie CMOS des transistors modernes. Et avec la technologie TTL, il est possible de fusionner plusieurs portes logiques ET et NOR en une seule porte logique ET/OU/NON ! Un additionneur complet construit ainsi ne prenait que deux portes logiques : une pour le calcul de la retenue sortante, une autre pour le reste du circuit.
==L'addition non signée==
Voyons maintenant un circuit capable d'additionner deux nombres entiers: l''''additionneur'''. Dans la version qu'on va voir, ce circuit manipulera des nombres strictement positifs. L'addition des nombres codés en complètement à deux sera vu dans une section ultérieure.
===L'additionneur série===
Il est possible d'additionner deux nombres bit par bit, grâce à un additionneur complet associé à plusieurs registres à décalages. Les opérandes sont placées chacune dans un registre à décalage, et l'additionneur complet utilise les bits sortants de ces registres à décalage. Le bit du résultat est envoyé au registre à décalage pour le résultat. La retenue de l'addition est stockée dans une bascule de 1 bit, en attente du prochain cycle d'horloge. Un tel additionneur est appelé un '''additionneur série'''. Il a été utilisé sur d'anciens ordinateurs dans les années 50-60, aussi bien des prototypes que des ordinateurs commerciaux.
[[File:Additionneur série.jpg|centre|vignette|upright=2.5|Additionneur série.]]
===L'additionneur à propagation de retenue===
L''''additionneur à propagation de retenue''' est aussi appelé l'additionneur ''ripple carry'', c'est son nom anglais. J'utiliserais occasionnellement le nom anglais dans ce qui suit, car celui-ci est plus court. Ilutilise un additionneur complet pour chaque colonne de l'addition. Pour gérer les retenues, il suffit ainsi de câbler des additionneurs complets les uns à la suite des autres, chacun envoyant sa retenue à la colonne suivante.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Notons la présence de la retenue sortante, qui est utilisée pour détecter les débordements d'entier, ainsi que pour d'autres opérations. Le bit de retenue final est souvent stocké dans un registre spécial du processeur (généralement appelé ''carry flag'').
Notez aussi, à droite du schéma précédent, l'entrée de retenue <math>\text{R}_\text{Entrée}</math>. Elle est très utile pour l'implémentation de certaines opérations comme l'inversion de signe, la soustraction, l'incrémentation, etc. Certains processeurs sont capables de faire une opération appelée ADC, ADDC ou autre nom signifiant ''Addition with Carry'', qui permet de faire le calcul A + B + Retenue (la retenue en question est la retenue sortante de l'addition précédente, stockée dans le registre ''carry flag''). Son utilité principale est de permettre des additions d'entiers plus grands que ceux supportés par le processeur. Par exemple, cela permet de faire des additions d'entiers 32 bits sur un processeur 16 bits.
[[File:Adder Network 3 Bit Sum Delay 003.svg|vignette|upright=0.5|Propagation de retenue dans l'additionneur.]]
Cet additionneur utilise très peu de portes logiques, ce qui fait qu'il était utilisé sur certains processeurs 8 et 16 bits, dont le budget en portes logiques était limité. Bien que très simple, cet additionneur est cependant peu performant. Son temps de calcul est proportionnel à la taille des opérandes. Par exemple, additionner deux opérandes de 32 bits prendra deux fois plus de temps que pour des opérandes de 16 bits. La raison est que les retenues doivent se propager à travers le circuit, du premier additionneur jusqu'au dernier.
L'addition étant une opération fréquente, d'autres additionneurs ont été inventés. Ils utilisent des optimisations qui utilisent plus de circuits pour gagner en rapidité. Mais avant de voir ces optimisations, nous allons voir s'il est possible d'optimiser les additionneurs à propagation de retenue.
===L'additionneur à saut de retenue===
L'additionneur à propagation de retenue peut être optimisé de deux manières. La première accélère la propagation de la retenue, en agissant au niveau des additionneurs complets. La ''Manchester carry chain'' est une optimisation de ce type, mais on ne peut pas encore expliquer à ce stade du cours, car elle optimisait les additionneurs complets au niveau des transistors eux-mêmes. Elle a été utilisée sur de nombreux processeurs connus, comme le 8086 d'Intel ou le Z80.
La seconde manière rend l'addition plus rapide dans certains cas. Le résultat est un additionneur dont le temps de calcul est variable. Le calcul prendra quelques cycles d'horloges avec certains opérandes, mais d'autres opérandes ne verront aucune amélioration. Il n'améliore pas le pire des cas, dans lequel la retenue doit être propagée du début à la fin, du bit de poids faible au bit de poids fort. Mais dans d'autres cas, il permet d'avoir le résultat en avance. L'optimisation en question donne l''''additionneur à saut de retenue''' (''carry-skip adder'').
Il est composé de blocs qui additionnent 4/5 bits, rarement plus. La taille d'un bloc varie d'un additionneur à l'autre, elle peut même varier dans l'additionneur, mais nous allons prendre un exemple où les blocs font 4 bits. L'idée est de détecter précocement si la retenue entrante est propagée à travers tout le bloc. Si la retenue est propagée dans tous le bloc, on peut directement l'envoyer sur la sortie de retenue, la retenue saute le bloc entier. Dans le cas contraire, on doit calculer la retenue normalement. Le choix entre les deux est le fait d'un multiplexeur. Toute la difficulté est de savoir comment commander le multiplexeur.
[[File:Carry skip adder, principe de base.png|centre|vignette|upright=2.5|Carry skip adder : principe de base]]
Pour savoir si une retenue est propagée dans un bloc entier, il faut utiliser des additionneurs complets, qui ont en plus une sortie P indiquant que l'additionneur propage la retenue. Le bloc propage une retenue si chaque additionneur complet propage la retenue, donc s'ils ont tous leur sortie P à 1. Un simple ET logique suffit à déterminer si c'est le cas. Le signal de commande du multiplexeur est donc un vulgaire ET entre toutes les sorties P des additionneurs complets du bloc.
[[File:Additionneur à saut de retenue.png|centre|vignette|upright=2.5|Calcul de la commande du MUX.]]
Voici ce que ça donne pour un bloc de 4 bits :
[[File:CSAdder4Bit.svg|centre|vignette|upright=1.5|Additionneur ''carry skip'' : bloc de 4 bits.]]
L'additionneur à saut de retenue est construit en assemblant plusieurs blocs de ce type.
[[File:BCSAdder16Bit.svg|centre|vignette|upright=3|Additionneur à saut de retenue.]]
===L'additionneur à sélection de retenue===
L''''additionneur à sélection de retenue''' utilise aussi des blocs, comme les additionneurs précédents. L'addition se fait en deux versions : une avec la retenue du bloc précédent valant zéro, et une autre version avec la retenue du bloc précédent valant 1. Il suffira alors de choisir le bon résultat avec un multiplexeur, une fois cette retenue connue. On gagne ainsi du temps en calculant à l'avance les valeurs de certains bits du résultat, sans connaître la valeur de la retenue. Petit détail : sur certains additionneurs à sélection de retenue, les blocs de base n'ont pas la même taille. Cela permet de tenir compte des temps de propagation des retenues entre les blocs.
[[File:Additionneur à sélection de retenue.png|centre|vignette|upright=1.5|Additionneur à sélection de retenue avec seulement deux blocs.]]
Dans les exemples du dessus, chaque sous-additionneur étaient des additionneurs à propagation de retenue. Mais ce n'est pas une obligation, et tout autre type d’additionneur peut être utilisé. Par exemple, on peut faire en sorte que les sous-additionneurs soient eux-mêmes des additionneurs à sélection de retenue, et poursuivre ainsi de suite, récursivement. On obtient alors un '''additionneur à somme conditionnelle''', plus rapide que l'additionneur à sélection de retenue, mais qui utilise beaucoup plus de portes logiques.
===Les additionneurs à anticipation de retenue===
Les '''additionneurs à anticipation de retenue''' calculent chaque retenue sans avoir à propager les retenues précédentes. Au lieu de calculer les retenues une par une, ils calculent toutes les retenues en parallèle, à partir des bits des opérandes. Une fois les retenues pré-calculées, il suffit de les additionner avec les deux bits adéquats, pour obtenir le résultat. Ces additionneurs sont composés de deux parties :
* un circuit qui pré-calcule les retenues, sans les propager, directement à partir des opérandes ;
* d'une couche d'additionneurs complets, qui additionnent chacun deux bits d'opérande et la retenue pré-calculée.
[[File:Additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Additionneur à anticipation de retenue.]]
Le calcul des retenues se fait en deux étapes. La première détermine si les retenues sont propagées ou générées, pour chaque colonne. Elle détermine, pour chaque colonne, les bits P et G qui indiquent respectivement : qu'une retenue a été générée sur cette colonne, que la colonne propage la retenue précédente. La seconde étape utilise ces signaux P et G pour déterminer la retenue sur chaque colonne. Le circuit de calcul des retenues est donc composé de deux parties : une couche de demi-additionneurs pour générer les signaux P et G, un circuit de calcul des retenues proprement dit.
[[File:Circuit complet d'un additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Circuit complet d'un additionneur à anticipation de retenue.]]
Pour rappel, la retenue de la colonne i, notée <math>C_i</math> est égale à :
: <math>G_i + (P_i . C_{i-1})</math>, avec G le signal de génération de retenue, P le signal de propagation de retenue.
Si on utilisait cette formule sans trop réfléchir, on retomberait sur un additionneur à propagation de retenue inutilement compliqué. L'astuce des additionneurs à anticipation de retenue consiste à remplacer le terme Ci−1 par sa valeur calculée avant. Par exemple, je prends un additionneur 4 bits. Je dispose de deux nombres A et B, contenant chacun 4 bits : A3, A2, A1, et A0 pour le nombre A, et B3, B2, B1, et B0 pour le nombre B. Si j'effectue les remplacements, j'obtiens les formules suivantes :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Ces formules nous permettent de déduire la valeur d'une retenue directement : il reste alors à créer un circuit qui implémente ces formules, et le tour est joué.
[[File:Four bit adder with carry lookahead.svg|centre|vignette|upright=1.5|Additionneur à anticipation de retenue de 4 bits.]]
Plus haut, j'ai dit que le circuit était composé de trois couches : une couche de demi-additionneur pour les signaux P et G ; un circuit d'anticipation de retenue, puis l'addition finale. Mais les deux bits d'opérandes sont déjà additionnés dans la couche de demi-additionneur, autant réutiliser le résultat pour l'additionner à la retenue. Pour éliminer toute redondance de ce type, il est possible d'utiliser des additionneurs partiels (des additionneurs où la sortie de retenue a été remplacée par deux sorties P et G). L'additionneur final est alors composé de plusieurs additionneurs complets de ce type, couplé à un circuit d'anticipation de retenue.
[[File:Cla4bits.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue de 4 bits.]]
Les additionneurs à anticipation de retenue sont plus rapides que les additionneurs à propagation de retenue. La raison est que les retenues sont calculées rapidement, dans un temps plus petit qu'en propageant la retenue. Pour rentrer dans les détails mathématiques, le temps de calcul n'est pas proportionnel au nombre de bits, mais proportionnel au logarithme de ce nombre de bits. Par contre, le gain en temps de calcul s'accompagne d'un cout en portes logiques conséquent. Le circuit qui calcule les retenues anticipées utilise beaucoup de portes logiques.
===Les additionneurs à calcul parallèle de préfixe===
Du fait du cout en portes logiques, utiliser un additionneur à anticipation de retenue est une solution pratique pour additionner 4 ou 8 bits. Mais pour des opérandes plus grands, de 16 ou 32 bits, cela utiliserait trop de portes logiques. Une partie de ce cout en portes logiques est cependant lié à des redondances. Si on analyse deux retenues différentes, à des colonnes différentes. Les circuits qui calculent ces deux retenues seront différents, mais ils contiendront des sous-circuits identiques. Pour vous en rendre compte, reprenons les quatre équations obtenues précédemment et essayez de trouver des redondances :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Vous devriez trouver :
* le terme ''P0 · C0'' dans toutes les lignes ;
* les termes ''P1 · P0 · C0'' et ''P1 · P0'' dans les trois dernières lignes ;
* le terme ''P2 · P1 · P0 · C0'' ; ''P2 · P1 · G0'' et ''P2 · G1'' dans les deux dernières lignes.
Il existe plusieurs manières d'exploiter de telles redondances, exploitées dans les '''additionneurs à calcul parallèle de préfixe'''. L'optimisation apportée est de générer des signaux ''propagate'' et ''generate'' pour des groupes de 2, 3, 4, ..., N colonnes. Par exemple, il est possible de générer un signal ''P 0 vers 3'', qui indique si la retenue de la première colonne est propagée jusqu'à la 4ème colonne. Un autre exemple est un signal qui indique qu'une retenue a été générée entre les colonnes 4 à 7 génèrent une retenue ou non. En clair, les signaux P et G ont maintenant un intervalle, qui précise de quelle colonne vers quelle colonne se fait la propagation, ou entre quelles colonnes se fait la génération.
Il est possible de combiner les signaux P et G de deux groupes de colonne, s'ils sont contiguës (ils peuvent aussi se recouvrir). Par exemple, il est possible de calculer les bits P et G pour les colonnes 0 à 10, à partir des deux signaux P/G des colonnes 0-4 et 5-10. Pour cela, les équations sont assez simples. Si je prends deux groupes nommés A et B, avec A pour les colonnes de poids fort et B celles de poids faible, on a :
: <math>P_{sortie} = P_A . P_B</math>
: <math>G_{sortie} = G_A . P_A . G_B</math>
Pour les bits des colonnes 2, 4, 8, 16, 32, 64, et autres, le calcul est simple. L'idée est de grouper les colonnes par groupes de 2. Puis, on calcule chaque groupe de 4 bits à partir de deux groupes de 2. Puis les groupes de 8 bits sont calculés à partir de deux groupes de 4 bits, et ainsi de suite pour toute puissance de deux.
[[File:Squelette d'un additionneur à calcul parallèle de préfixe.jpg|centre|vignette|upright=2|Squelette d'un additionneur à calcul parallèle de préfixe]]
Les seules difficultés surviennent pour les colonnes dont le nombre n'est pas une puissance de deux. Il y a plusieurs manières pour subdiviser ces colonnes en intervalles plus petits et combiner le tout. Et elles donnent chacune des additionneurs différents. Ils portent les noms d'''additionneur de Ladner-Fisher'', d'''additionneur de Brent-Kung'', d'''additionneur de Kogge-Stone'', etc. Dans le cas général, l'additionneur de Kogge-Stone est un des plus rapide, mais aussi un des plus gourmand en circuits, alors que c'est l'inverse pour l'additionneur de Brent-Kung. Les deux additionneurs sont illustrés ci-dessous, pour des opérandes de 8 bits. Dans ceux-ci, chaque losange calculent les bits P et G, à partir des signaux P et G de deux groupes précédents.
{|
|[[File:Brent-kung-8-bit.png|vignette|Brent-kung-8-bit]]
|[[File:Kogge-stone-8-bit.png|vignette|upright=1.2|Kogge-stone-8-bit]]
|}
===Le calcul parallèle de la retenue===
L'anticipation de retenue est très rapide, mais utilise beaucoup de circuits, même en utilisant le calcul parallèle de préfixe. Les additionneurs à propagation de retenue font eux le compromis inverse. Mais il existe des intermédiaires, qui visent à obtenir un compromis entre performance et cout en portes logiques.
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Un de ces compromis découpe un additionneur de 16/32/64 bits en additionneurs de 4/5 bits, qui sont enchainés en utilisant la propagation de retenue. Par contre, ces petits additionneurs de 4/5 bits utilisent l'anticipation de retenue. Ces additionneurs simples seront nommés ''blocs'' dans ce qui suit, et l'un d'entre eux est illustré ci-contre. Vous remarquerez qu'un bloc a une retenue entrante et une retenue sortante. Par exemple, on peut utiliser 4 additionneurs à anticipation de retenue de 4 bits, et propager les retenues entre eux. Le premier bloc calcule une retenue, qui est propagée au second bloc. Le second bloc calcule lui aussi une retenue, qui est propagée au troisième bloc, etc.
[[File:4008 Functional Diagram.svg|vignette|upright=1|4008 Functional Diagram]]
Le gain en performance est significatif, mais il est possible de faire presque aussi rapide, tout en économisant beaucoup de circuits. L'idée est de simplifier les additionneurs 4 bits, afin de ne pas utiliser une anticipation de retenue complète. L'anticipation de retenue n'est utilisée que pour la retenue sortante, alors que les bits de somme sont calculées avec propagation de retenue. La retenue sortante est donc calculée en parallèle de l'addition. Chaque bloc contient, à côté d'un additionneur proprement dit, un circuit qui calcule la retenue sortante. Il existe de nombreuses manières de calculer la retenue sortante.
Le gain en portes logiques est assez significatif. En effet, on se passe de circuits d'anticipation de retenue pour les 4 bits de somme, on ne garde que celui pour la retenue sortante. Le fait d'utiliser la propagation de retenue pour calculer les 4 bits de somme est censé avoir un léger cout en performance, mais il se trouve que ce cout est très faible. En effet, le résultat n'est connu qu'une fois les retenues propagées jusqu'au dernier bloc. Et malgré l'anticipation des retenues, la propagation est assez lente. les additionneurs à propagation de retenue auront terminé leur travail bien avant.
Un exemple est celui de l'additionneur CMOS 4008, un additionneur de 4 bit. Il est composé en trois sections. Une première couche de demi-additionneurs calcule les signaux P et G utilisés à la fois pour l'anticipation de la retenue sortante, que pour calculer les bits de somme. Le circuit de calcul de la retenue sortante utilise l'anticipation de retenue. Le reste du circuit fait l'addition en propageant les retenues.
[[File:CMOS 4008, circuit découpé en sections.png|centre|vignette|upright=1.5|CMOS 4008, circuit découpé en sections]]
===Les additionneurs à anticipation de retenue multi-niveau===
Les additionneurs précédents mixent anticipation et propagation de retenue. Ceux que nous allons voir utilisent des blocs, mais utilisent l'anticipation de retenue entre les blocs. Les blocs utilisés peuvent être un additionneur à propagation de retenue, ou à anticipation de retenue, peu importe. Ce qui compte est que la retenue entrante de chaque bloc est calculée par anticipation de retenue.
Par exemple, il est possible de créer un additionneur 16 bits en utilisant 4 additionneurs/blocs de 4 bits, couplé à un circuit d’anticipation de retenue par bloc. Pour cela, les additionneurs de 4 bits doivent fournir deux sorties P et G. Les deux sorties indiquent que le bloc soit a généré une retenue, soit a propagé la retenue entrante. Par propagé, on veut dire que la retenue entrante du bloc s'est retrouvée sur la sortie de retenue du bloc. C'est le cas si les 4 colonnes propagent la retenue, ce qui signifie que :
: <math>P_\text{4 bits} = P_0 \cdot P_1 \cdot P_2 \cdot P_3</math>
Pour la génération, cela veut dire qu'une colonne a générée une retenue, qui a été propagée jusqu'à la sortie. En clair, pour chaque colonne, il faut que le G = 1, et que les signaux P des colonnes soient à 1. Pour les 4 colonnes, cela donne :
: <math>G_\text{4 bits} = G_3 + G_2 \cdot P_3 + G_1 \cdot P_3 \cdot P_2 + G_0 \cdot P_3 \cdot P_2 \cdot P_1</math>
[[File:Cla4bitsPG.png|centre|vignette|upright=2|Additionneur à anticipation de retenue de 4 bits.]]
Pour combiner 4 blocs de 4 bits, on devra donc combiner 4 bits P et G, avec un circuit d'anticipation de retenue.
[[File:16-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 16 bits.]]
Il est même possible d'aller plus loin et de combiner 4 additionneurs précédents pour obtenir un additionneur 64 bits.
[[File:64-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 64 bits.]]
Le résultat est un additionneur à plusieurs niveaux d’anticipation de retenue. Une anticipation de retenue dans un blocs de 4 bits (facultative), entre blocs de 4 bits, entre blocs de 16 bits. La même logique peut être utilisée avec des blocs de taille différente de 4, 16 et 64 bits.
==L'addition signée et la soustraction==
Après avoir vu l'addition, il est logique de passer à la soustraction, les deux opérations étant très proches. Si on sait câbler une addition entre entiers positifs, câbler une soustraction n'est pas très compliqué. De plus, la soustraction permet de faire des additions de nombres signés.
===Le soustracteur pour opérandes entiers===
Pour soustraire deux nombres entiers, on peut adapter l'algorithme de soustraction utilisé en décimal, celui que vous avez appris à l'école. Celui-ci ressemble fortement à l'algorithme d'addition : on soustrait les bits de même poids, et on propage éventuellement une retenue sur la colonne suivante. À la différence de l'addition, la retenue est soustraite, et non ajoutée. La table de soustraction nous dit quel est le résultat de la soustraction de deux bits. La voici :
* 0 - 0 = 0 ;
* 0 - 1 = 1 et une retenue ;
* 1 - 0 = 1 ;
* 1 - 1 = 0.
[[File:Algebra1 05 fig014.svg|centre|vignette|Soustraction en binaire, avec les retenues en rouge.]]
La table de soustraction peut servir de table de vérité pour construire un circuit qui soustrait deux bits. Celui-ci est appelé un '''demi-soustracteur'''. Il ressemble beaucoup à un demi-additionneur, les différences se résumant à une porte NON ajoutée pour le calcul de la retenue.
[[File:Half Subtractor Vektor.svg|centre|vignette|Demi-soustracteur.]]
Comme pour l'additionneur, deux demi-soustracteurs peuvent être combinés pour donner un '''soustracteur complet'''. Le calcul de la retenue se fait en combinant les deux retenues des demi-soustracteurs avec une porte OU. Les soustracteurs complets sont utilisés pour créer des soustracteurs à propagation de retenue ou tout autre circuit soustracteur, sur le même modèle que les additionneurs.
[[File:FullSubtractor.svg|centre|vignette|upright=2|Soustracteur complet.]]
Vous l'aurez sans doute remarqué, mais le demi-soustracteur ressemble beaucoup au demi-additionneur. La seule différence est une porte NON ajoutée au bon endroit dans le demi-soustracteur.
{|
|[[File:Half-adder.svg|vignette|Demi-additionneur]]
|[[File:Half Subtractor Vektor.svg|vignette|upright=1.3|Demi-soustracteur]]
|}
La conséquence est qu'il est possible de créer un circuit capable de faire à la fois des additions et des soustractions. L'idée est de remplacer la porte NON du demi-soustracteur par un inverseur commandable. Si on envoie un zéro sur l'entrée de commande, l'inverseur commandable se comportera comme une porte OUI. Le circuit sera alors identique à un demi-additionneur. Si on envoie un 1 sur l'entrée de commande, l'inverseur commandable se comportera comme une porte NON. le circuit sera alors un demi-soustracteur. Il faut faire cela pour chaque demi-soustracteur, ce qui donne ceci pour un soustracteur complet. le résultat est alors appelé un '''additionneur-soustracteur complet'''.
[[File:Additionneur-soustracteur complet.png|centre|vignette|upright=2|Additionneur-soustracteur complet]]
===L'additionneur-soustracteur pour opérandes codées en complément à deux===
Étudions le cas de la soustraction en complément à deux, dans l'objectif de créer un circuit soustracteur. Vous savez sûrement que a−b et a+(−b) sont deux expressions équivalentes. Et en complément à deux, − b = not(b) + 1. Dit autrement, a − b = a + not(b) + 1. On pourrait se dire qu'il faut deux additionneurs pour faire le calcul, mais la majorité des additionneurs possède une entrée de retenue pour incrémenter le résultat de l'addition. Un soustracteur en complément à deux est donc simplement composé d'un additionneur et d'un inverseur.
[[File:Additionneur soustracteur.png|centre|vignette|upright=2|Soustracteur en complément à deux.]]
Il est possible de créer un circuit capable d'effectuer soit une addition, soit une soustraction : il suffit de remplacer l'inverseur par un inverseur commandable, qui peut être désactivé. On a vu comment créer un tel inverseur commandable dans le chapitre sur les circuits combinatoires. On peut remarquer que l'entrée de retenue et l'entrée de commande de l'inverseur sont activées en même temps : on peut fusionner les deux signaux en un seul.
[[File:Additionneur-soustracteur en complément à deux.png|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux.]]
Une implémentation alternative est la suivante. Elle remplace l'inverseur commandable par un multiplexeur.
[[File:4-bit ripple carry adder-subtracter.svg|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux, version alternative.]]
===L'additionneur-soustracteur pour opérandes codées en signe-magnitude===
Passons maintenant aux nombres codés en signe-valeur absolue, les deux opérandes étant notées A et B. Suivant les signes des deux opérandes, on a quatre cas possibles : A + B, A − B (B négatif), −A + B (A négatif) et −A − B (A et B négatifs). Une astuce est que le circuit n'a besoin que de calculer A + B et A − B : il peut les inverser pour obtenir − A − B ou B − A. A + B et A − B peuvent se calculer avec un additionneur-soustracteur, reste à corriger le résultat. Il suffit de lui ajouter un inverseur commandable pour obtenir le circuit d'addition finale.
[[File:Additionneur en signe-valeur absolue.png|centre|vignette|upright=2|Additionneur en signe-valeur absolue.]]
Toute la difficulté tient dans le calcul du bit de signe du résultat, quand interviennent des soustractions. Autant l'addition de deux nombres de même signe (A + B et −A − B) ne pose aucun problème, autant les soustractions posent problème (A − B et −A + B). Suivant que <math>A<B</math> ou que <math>A>B</math>, le signe du résultat ne sera pas le même. Déterminer le signe du résultat se fait en regardant les bits de débordement d'entier, comme on le verra plus bas.
===L'additionneur-soustracteur pour opérandes codées en représentation par excès===
Passons maintenant aux nombres codés en représentation par excès. On pourrait croire que ces nombres s'additionnent comme des nombres non-signés, mais ce serait oublier la présence du biais, qui pose problème. Dans les cas de nombres signés gérés avec un biais, voyons ce que donne l'addition de deux nombres :
: <math>( n_1 + biais ) + ( n_2 + biais ) = n_1 + n_2 + 2 \times biais</math>
Or, le résultat correct serait :
: <math>n_1 + n_2 + biais</math>
En effectuant l'addition telle quelle, le biais est compté deux fois. On doit donc le soustraire après l'addition pour obtenir le résultat correct.
Même chose pour la soustraction qui donne ceci :
: <math>(n_1 + biais) - (n_2 + biais) = n_1 - n_2</math>
Or, le résultat correct serait :
: <math> ( n_1 - n_2 ) + biais </math>
Il faut rajouter le biais pour obtenir l'exposant correct.
On a donc besoin de deux additionneurs/soustracteurs : un pour additionner/soustraire les représentations binaires des opérandes, et un autre pour ajouter/retirer le biais en trop/manquant.
==L'additionneur BCD==
Maintenant, voyons un additionneur qui additionne deux entiers au format BCD. Pour cela, nous allons devoir passer par deux étapes. La première est de créer un circuit capable d'additionneur deux chiffres BCD. Ensuite, nous allons voir comment enchaîner ces circuits pour créer un additionneur BCD complet.
===L'additionneur BCD qui fait l'opération chiffre par chiffre===
Nous allons commencer par voir un additionneur qui additionne deux chiffres en BCD, une sorte d'équivalent BCD de l'additionneur complet. Il fournit un résultat sur 4 bits et une retenue qui est mise à 1 si le résultat dépasse 10 (la limite d'un chiffre BCD). Les deux opérandes sont des chiffres BCD codés sur 4 bits et sont additionnés en binaire par un additionneur des plus normaux, similaire à ceux vus plus haut. Le résultat est alors un entier codé en binaire, sur 5 bits, qu'on corrige/convertit pour obtenir un chiffre BCD et une retenue sortante.
Pour corriger le résultat, une idée intuitive serait de prendre le résultat et de faire une division par 10. Le quotient donne la retenue, alors que le reste est le résultat, le chiffre BCD. Mais un circuit diviseur par 10 utilise beaucoup de portes logiques, ce qui ne vaut pas le coup. Une autre méthode détecte si le résultat est égal ou supérieur à 10, ce qui correspond à un "débordement" (on dépasse les limites d'un chiffre BCD). Si le résultat est plus petit que 10, il n'y a rien à faire : le résultat est bon et la retenue est de zéro. Par contre, si le résultat vaut 10 ou plus, il faut corriger le résultat et générer une retenue à 1.
Il faut donc ajouter un circuit qui détecte si le résultat est supérieur à 9, qui calcule directement la retenue. Ce circuit peut se fabriquer simplement à partir de sa table de vérité, ou en utilisant les techniques que nous verrons dans un chapitre ultérieur sur les comparateurs. La solution la plus simple est clairement d'utiliser la table de vérité, ce qui est très simple, assez pour être laissé en exercice au lecteur. Pour comprendre comment corriger le résultat, établissons une table de vérité qui associe le résultat et le résultat corrigé. L'entrée vaut au minimum 10 et au maximum 9 + 9 = 18. On considère la sortie comme un tout, la retenue étant un 5ème bit, le bit de poids fort.
{|class="wikitable"
|-
! colspan="5" | Entrée
!
! rowspan="10" |
! Retenue
! Résultat corrigé (sans retenue)
! interprétation de la sortie en binaire (retenue inclue)
|-
| 0 || 1 || 0 || 1 || 0
| (10)
| 1 || 0000
| (16)
|-
| 0 || 1 || 0 || 1 || 1
| (11)
| 1 || 0001
| (17)
|-
| 0 || 1 || 1 || 0 || 0
| (12)
| 1 || 0010
| (18)
|-
| 0 || 1 || 1 || 0 || 1
| (13)
| 1 || 0011
| (19)
|-
| 0 || 1 || 1 || 1 || 0
| (14)
| 1 || 0100
| (20)
|-
| 0 || 1 || 1 || 1 || 1
| (15)
| 1 || 0101
| (21)
|-
| 1 || 0 || 0 || 0 || 0
| (16)
| 1 || 0110
| (22)
|-
| 1 || 0 || 0 || 0 || 1
| (17)
| 1 || 0111
| (23)
|-
| 1 || 0 || 0 || 1 || 0
| (18)
| 1 || 1000
| (24)
|}
En analysant le tableau, on voit que pour corriger le résultat, il suffit d'ajouter 6. La raison est que le résultat déborde d'un nibble à 16 en binaire, mais à 10 en décimal : il suffit d'ajouter la différence entre les deux, à savoir 6, et le débordement binaire fait son travail. Donc, la correction après une addition est très simple : si le résultat dépasse 9, on ajoute 6.
On peut maintenant implémenter l'additionneur BCD, en combinant le comparateur avec 10, le circuit de correction, et l'additionneur. La première solution calcule deux versions du résultat : la version corrigée, la version normale. Le choix entre les deux est réalisée par un multiplexeur, commandé par le comparateur.
[[File:Additionneur BCD.png|centre|vignette|upright=1.5|Additionneur BCD]]
L'autre solution utilise un circuit commandable qui soit additionne 6, soit ne fait rien. Le choix entre les deux est commandé par le bit calculé par le comparateur.
[[File:Additionneur BCD, seconde version.png|centre|vignette|upright=1.5|Additionneur BCD, seconde version.]]
Une version alternative du circuit précédent est la suivante. Il contient deux additionneurs : un pour additionner les deux chiffres BCD, un autre pour additionner 6 si besoin. Le résultat du comparateur est directement utilisé pour générer l'opérande du second additionneur : 0 ou 6. Le circuit est simple à concevoir, mais gaspille beaucoup de circuit. Idéalement, il vaudrait mieux utiliser un circuit combinatoire d'addition avec une constante.
[[File:Adder 4 Bit BCD.svg|centre|vignette|upright=2|Additionneur BCD, circuit complet.]]
Pour obtenir un additionneur BCD complet, il suffit d’enchaîner les additionneurs précédents, comme on le ferait avec les additionneurs complets dans un additionneur à propagation de retenue. Au final, l'additionneur BCD est beaucoup plus compliqué qu'un additionneur normal, car il rajoute un comparateur ">9", un petit additionneur pour ajouter 6 et éventuellement d'autres circuits. De plus, il est difficile d'appliquer les optimisations disponibles sur les additionneurs non-BCD. Notamment, les circuits d'anticipation de retenue sont totalement à refaire et le résultat est relativement compliqué. C'est ce qui explique pourquoi le BCD a progressivement été abandonné au profit du binaire simple.
La soustraction en BCD se fait comme en binaire : le nombre à soustraire est remplacé par son complément, le circuit additionne le complément et l'autre opérande, le débordement d'entier fait que le résultat marche. Sauf qu'ici, le complément est un complément à 9. Il se calcule chiffre par chiffre : chaque chiffre est remplacé par (9 - le chiffre en question).
===L'additionneur BCD par ajustement décimal===
L'additionneur BCD précédent effectuait son travail chiffre BCD par chiffre BCD, mais il existe des additionneurs BCD qui font autrement. Sur les premiers processeurs x86, il n'y avait pas d'opération d'addition BCD proprement dit, seulement une addition binaire normale de 8, 16 ou 32 bits. Par contre, elle était secondée par une opération dite d''''ajustement décimal''' qui transformait un nombre binaire en nombre codé en BCD. L'opération d'ajustement décimal prenait un opérande de 8 bits codé en binaire et fournissait un résultat de la même taille, c'est à dire deux chiffres BCD. Effectuer une addition BCD demandait donc de faire deux opérations à la suite : une addition binaire simple, suivie par l'opération d'ajustement décimal. Cela permettait de gérer des nombres entiers en binaire usuel et des entiers BCD sans avoir deux instructions d'addition séparées pour les deux, sans compter que cela simplifiait aussi les circuits d'addition.
L'ajustement décimal s'effectue en ajoutant une constante bien précise à l'opérande à convertir en BCD. L'idée est que la constante est découpée en morceaux de 4 bits, correspondant chacun à un chiffre BCD de l'opérande, chaque morceau contenant soit un 0, soit 6. Cela permet d'ajouter soit 0, soit 6, à chaque chiffre BCD, et donc de le corriger. La propagation des retenues d'un chiffre à l'autre est effectuée automatiquement par l'addition binaire de la constante. L'opération d'ajustement décimal calcule automatiquement la constante. Elle découpe l'opérande en ''nibbles'', vérifie si chaque ''nibble'' est supérieur ou égal à 10, puis détermine la valeur de chaque ''nibble'' de la constante finale. Par exemple, si je prends l'opérande 1001 1110, le ''nibble'' de poids faible déborde, alors que celui de poids fort non. La constante sera donc 0000 0110 : 0x06. Inversement, si le ''nibble'' de poids fort déborde et pas celui de poids faible, la constante sera alors 0x60. Et la constante est de 0x66 si les deux ''nibbles'' débordent, de 0x00 si aucun ne déborde.
Le circuit d’ajustement décimal est donc composé de trois étapes : deux étapes pour calculer la constante, et un circuit d'addition pour additionner cette constante au nombre de départ. La première étape découpe l'opérande en morceaux de 4 bits, en chiffres BCD, et vérifie si chacun d'entre eux vaut 10 ou plus. La seconde étape prend les résultats de la première étape, et les combine pour calculer la constante. Enfin, on trouve l'addition finale, qui était réalisée par un circuit d'addition utilisé à la fois pour l'ajustement décimal et l'addition binaire. La différence entre une addition normale et une opération d'ajustement décimal tient dans le fait que les deux premières étapes sont désactivées dans une addition normale.
[[File:Additionneur BCD parallèle.png|centre|vignette|upright=1.5|Additionneur BCD parallèle]]
===L'additionneur biquinaire===
Les entiers BCD ne sont qu'un des encodages hybrides entre décimal et binaire. L'encodage biquinaire est l'un d'entre eux et nous allons faire un rappel rapide à ce sujet. Pour simplifier, un chiffre encodé en biquinaire est composé de deux parties : un bit, couplé à une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits (0, 1, 2, 3 et 4). Le bit indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9.
Additionner deux nombres de biquinaire demande donc d'additionner deux parties quinaires encodées en ''one-hot'' et d'additionner deux bits. Mais attention : il faut tenir compte de la retenue de l'addition des parties quinaires. Et idéalement, il faut aussi tenir compte d'une retenue entrante, provenant de l'addition de la colonne de chiffres précédente. Toute la difficulté vient de la création de l'additionneur ''one-hot''. Heureusement, vu qu'il n'y a que 4-5 bits à additionner, il est souvent fabriqué à partir de sa table de vérité.
[[File:Additionneur bi-quinaire.png|centre|vignette|upright=2|Additionneur bi-quinaire]]
Un avantage du biquinaire est que le calcul du complément à 9 est très simple. Il faut pour cela : inverser la partie binaire avec une porte NON, puis inverser l'ordre des bits de la partie quinaire. Concrètement, le bit de poids faible devient le bit de poids fort, et ainsi de suite. Par exemple, une partie quinaire 01000 devient 00010, 10000 devient 00001, 00100 ne change pas, etc. Le tout peut se calculer avec une porte NON et 5 multiplexeurs.
===L'additionneur BCD avec calculs intermédiaires en biquinaire===
L'ordinateur IBM 1401, un ancien ''mainframe'' des années 60, utilisait un additionneur BCD un peu particulier. Les nombres étaient encodés en BCD dans la mémoire de l'ordinateur, mais les circuits de calcul utilisaient la représentation biquinaire. Lors d'un calcul, le processeur de l'ordinateur traduisait les chiffres BCD en représentation biquinaire, faisait une addition en biquinaire, avant de traduire le résultat en BCD normal.
Pour être précis, l'IBM 1401 utilisait une variante du biquinaire. L'encodage biquinaire de l'IBM 1401 est le suivant : la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre pair encodé par la partie quinaire. Si l'IBM 1401 utilisait cette variante du biquinaire, c'est car elle donnait des circuits de conversion BCD-biquinaire plus économes en portes logiques et plus rapides.
La partie binaire est le bit de poids faible du chiffre BCD, la partie biquinaire est calculée par un simple décodeur qui prend en entrée le chiffre BCD, amputé de son bit de poids faible. La traduction inverse demande d'utiliser un encodeur, à la place du décodeur. Par contre, le circuit d'addition biquinaire était plus compliqué du fait de la gestion des retenues. L'addition des parties binaires et quinaires se faisait en parallèle, dans deux additionneurs séparés. Cependant, l'addition des parties binaire fournit une retenue, qu'il faut prendre en compte. Pour cela, l'IBM 1401 disposait d'un troisième additionneur qui fournissait le résultat final, encodé en biquinaire.
[[File:Additionneur bi-quinaire de l'IBM 1401.png|centre|vignette|upright=2|Additionneur biquinaire de l'IBM 1401]]
Une implémentation moderne demanderait d'utiliser des portes ET combinées à des portes OU, le circuit pouvant être construit simplement à partir de sa table de vérité. Sur l'IBM 1401, le circuit était cependant différent, en raison de l'utilisation de OU câblés, des croisements de fils qui fonctionnent comme des portes OU, que nous n'avons pas encore vu pour le moment, mais qui seront détaillés dans quelques chapitres. Les OU câblés étaient utilisés pour simplifier le design du circuit, mais demandaient des portes logiques spécifiques, ce qui collait avec le fait que ce ''mainframe'' utilisait des transistors en Germanium. L'implémentation exacte est décrite dans cet article de blog, mais je ne recommande sa lecture qu'à ceux qui savent ce qu'est un OU câblé :
* [https://www.righto.com/2015/10/qui-binary-arithmetic-how-1960s-ibm.html Qui-binary arithmetic: how a 1960s IBM mainframe does math].
==Les débordements d'entier lors d'une addition/soustraction==
Les instructions arithmétiques manipulent des entiers codés sur un nombre fixe de bits, qui ne peuvent prendre leurs valeurs que dans un intervalle. Pour les nombres positifs, un ordinateur qui code ses entiers sur n bits pourra coder tous les entiers allant de 0 à <math>2^n -1</math>. Pour les nombres négatifs, l'intervalle est différent et dépend de la représentation utilisée. Dans le cas général, l'ordinateur peut coder les valeurs comprises de <math>N_\text{min}</math> à <math>N_\text{max}</math>. Si le résultat d'un calcul sort de cet intervalle, il ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math> , alors que la valeur basse vaut <math>N_\text{min} - 1</math> (avec <math>N_\text{max}</math> et <math>N_\text{min}</math> respectivement la plus grande et la plus petite valeur codable par l'ordinateur).
===La correction des débordements d'entier : l'arithmétique saturée===
Quand un débordement d'entier survient, tous les circuits de calcul ne procèdent pas de la même manière. Dans les grandes lignes, il y a deux réactions possibles : soit on corrige automatiquement le résultat du débordement, soit on ne fait rien et on se contente de détecter le débordement.
Si le débordement n'est pas corrigé automatiquement, le circuit ne conserve que les bits de poids faibles du résultat. Les bits en trop sont simplement ignorés. On dit qu'on utilise l''''arithmétique modulaire'''. Le problème avec ce genre d'arithmétique, c'est qu'une opération entre deux grands nombres peut donner un résultat très petit. Par exemple, si je dispose de registres 4 bits et que je souhaite faire l'addition 1111 + 0010 (ce qui donne 15 + 2), le résultat est censé être 10001 (17), ce qui est un résultat plus grand que la taille d'un registre. En conservant les 4 bits de poids faible, j’obtiens 0001 (1). En clair, un résultat très grand est transformé en un résultat très petit. Cela peut poser problèmes si on travaille uniquement avec des nombres positifs, mais c'est aussi utilisé pour coder des nombres en complément à deux.
D'autres circuits utilisent ce qu'on appelle l''''arithmétique saturée''' : si un calcul génère un débordement, on arrondi le résultat au plus grand entier supporté par le circuit. Les circuits capables de calculer en arithmétique saturée sont un peu plus complexes, vu qu'il faut rajouter des circuits pour corriger le résultat en cas de débordement. Il suffit généralement de rajouter un ''circuit de saturation'', qui prend en entrée le résultat et le corrige en cas de débordement. Ce circuit de saturation met la valeur maximale en sortie si un débordement survient, mais se contente de recopier le résultat du calcul sur sa sortie s'il n'y a pas de débordement. Typiquement, il est composé d'une couche de multiplexeurs, qui sélectionnent quelle valeur mettre sur la sortie : soit le résultat du calcul, soit le plus grand nombre entier géré par le processeur, soit le plus petit (pour les nombres négatifs/soustractions).
L'arithmétique saturée est utilisée pour les additions et soustractions, mais c'est plus rare pour les multiplications/divisions. Une des raisons est que le résultat d'une addition/soustraction prend un bit de plus que le résultat, là où les multiplications doublent le nombre de bits. Quand une addition déborde, le résultat réel est proche de la valeur maximale codable. mais quand une multiplication déborde, le résultat peut parfois valoir 200 à 60000 fois plus que la valeur maximale codable. Les calculs avec une valeur saturée/corrigée sont donc crédibles pour une suite d'additions, mais pas pour une suite de multiplications.
===La détection des débordements entiers===
Quand un débordement d'entier a eu lieu, il vaut mieux que l'additionneur prévienne ! Pour cela, l'additionneur a une '''sortie de débordement''', parfois nommée ''Overflow'', dont la valeur indique si l'addition a généré un débordement d'entier ou non. Reste que détecter un débordement ne se fait pas de la même manière selon que l'on parle d'un additionneur non-signé ou signé.
Pour les additionneur non-signés, l'additionneur calcule un bit de plus que ce qui est supporté par l'ordinateur. Par exemple, un additionneur 32 bits fournit un résultat sur 33 bits, un débordement d'entier a lieu quand le 33ème bit est à 1. Précisément, la sortie de débordement n'est autre que la retenue finale, celle fournie par le dernier additionneur complet. Le seul type de débordement possible est un débordement par le haut, où le résultat dépasse la valeur maximale. Avec l'arithmétique saturée, le circuit de saturation consiste en une seule couche de multiplexeurs, voire en un circuit de mise à la valeur maximale tel que vu dans le chapitre sur les opérations bits à bits.
[[File:Gestion des débordements d'entiers.png|centre|vignette|upright=2.5|Gestion des débordements d'entiers lors d'une addition non-signée.]]
Pour les additionneurs non-signés, la gestion des débordements d'entiers dépend fortement de la représentation signée. Nous allons étudier le cas du complément à deux. Si vous vous rappelez le chapitre 1, les calculs sur des nombres en complètement à deux utilisent les règles de l'arithmétique modulaire, c'est une condition nécessaire. À priori, on peut penser que dans ces conditions, les débordements d'entiers sont une chose parfaitement normale, qui nous permet d'avoir des résultats corrects. Néanmoins, certains débordements d'entiers peuvent survenir malgré tout et produire des bugs assez ennuyeux.
Si l'on tient en compte les règles du complément à deux, on sait que le bit de poids fort (le plus à gauche) permet de déterminer si le nombre est positif ou négatif : il indique le signe du nombre. Tout se passe comme si les entiers en complément à deux étaient codés sur un bit de moins, et avaient leur longueur amputé du bit de poids fort. Si le résultat d'un calcul écrase le bit de poids fort, il y a un débordement d'entiers. Il existe une règle simple qui permet de détecter ces débordements d'entiers. L'addition de deux nombres positifs ne peut pas être un nombre négatif. Si on additionne deux nombres dont le bit de signe est à 0 et que le bit de signe du résultat est à 1, on est en face d'un débordement d'entiers. Même chose pour deux nombres négatifs : le résultat de l'addition ne peut pas être positif. On peut résumer cela en une phrase : si deux nombres de même signe sont ajoutés, un débordement a lieu quand le bit du signe du résultat a le signe opposé.
Modifier les circuits d'au-dessus pour qu'ils détectent les débordements en complément à deux est simple comme bonjour : il suffit créer un petit circuit combinatoire qui prenne en entrée les bits de signe des opérandes et du résultat, et qui fasse le calcul de l'indicateur de débordements. Si l'on rédige sa table de vérité, on doit se retrouver avec la table suivante :
{|class="wikitable"
|-
!Entrées
!Sortie
|-
|000||0
|-
|001||1
|-
|010||0
|-
|011||0
|-
|100||0
|-
|101||0
|-
|110||1
|-
|111||0
|}
L'équation de ce circuit est la suivante, avec <math>S_a</math> et <math>S_b</math> les signes des deux opérandes, et <math>C_i</math> la retenue de la colonne précédente :
: <math> ( S_a . S_b . \overline{C_i} ) + ( \overline{S_a} . \overline{S_b} . C_i )</math>
En simplifiant, on obtient alors :
: <math>( S_a . S_b ) \oplus C_i</math>
Or, il se trouve que <math>S_a . S_b</math> est tout simplement la retenue en sortie du dernier additionneur, que nous noterons <math>C_o</math>. On trouve donc :
: <math>C_o \oplus C_i</math>
Il suffit donc de faire un XOR entre la dernière retenue et la précédente pour obtenir le bit de débordement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de décalage et de rotation
| prevText=Les circuits de décalage et de rotation
| next=Les circuits de comparaison
| nextText=Les circuits de comparaison
}}
</noinclude>
me1kp27qptdk81su5d1qbwgpqvuubmw
768669
768668
2026-06-25T20:02:34Z
Mewtow
31375
/* L'additionneur à saut de retenue */
768669
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir les circuits capables de faire une addition ou une soustraction, ainsi que quelques circuits spécialisés. Précisons cependant que les fabricants de processeurs travaillent d'arrache-pied pour trouver des moyens de rendre ces circuits de calcul plus rapides et plus économes en énergie. Autant vous dire que les circuits que vous allez voir sont vraiment des circuits qui font pâle figure comparé à ce que l'on peut trouver dans un vrai processeur commercial !
==Les circuits pour additionner 2 ou 3 bits==
L'addition se fait en binaire de la même manière qu'en décimal. On additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. La soustraction fonctionne sur le même principe, sur le même modèle qu'en décimal.
[[File:Binary Addition Demonstration.svg|centre|vignette|Exemple d'addition en binaire.]]
En clair, additionner deux nombres demande d'additionner 2 bits et une retenue sur chaque colonne, et de propager les retenues d'une colonne à l'autre. La propagation des retenues est quelque chose de simple en apparence, mais qui est sujet à des optimisations extraordinairement nombreuses. Aussi, pour simplifier l'exposition, nous allons voir comment gérer une colonne avant de voir comment sont propagées les retenues.
[[File:Full Adder Block.svg|vignette|upright=1|class=transparent|Additionneur complet.]]
Si on effectue une addition en colonne, on doit additionner les deux bits sur la colonne, mais aussi additionner une éventuelle retenue. Il faut donc créer un circuit qui additionne trois bits : deux bits de données, plus une retenue. Ce circuit qui additionne trois bits est appelé un '''additionneur complet'''. Il fournit en sortie deux bits : un bit de somme et une retenue sortante. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|-
||0||0||0|| ||0||0
|-
||0||0||1|| ||0||1
|-
||0||1||0|| ||0||1
|-
||0||1||1|| ||1||0
|-
||1||0||0|| ||0||1
|-
||1||0||1|| ||1||0
|-
||1||1||0|| ||1||0
|-
||1||1||1|| ||1||1
|}
Il est possible d'utiliser un tableau de Karnaugh pour traduire la table de vérité, mais elle donne un résultat assez compliqué. La retenue et le bit de somme sont calculés à part. La retenue est calculée avec trois portes ET et une porte OU. Le bit de somme est calculé lui avec 4 portes ET, une porte OU, et trois portes NON.
[[File:Full Adler with 3 entries.png|centre|vignette|upright=2|Additionneur complet, fabriqué à partir de la table de vérité.]]
D'autres méthodes donnent des résultats plus compréhensibles. Nous allons les voir dans la suite de cette section.
===L'additionneur complet basé sur des demi-additionneurs===
[[File:1-bit half-adder.svg|class=transparent|vignette|Demi-addtionneur.]]
Nous avons déjà vu comment additionner deux bits dans le chapitre sur les incrémenteurs, mais quelques rappels ne seront pas de trop. Le '''demi-additionneur''' est un circuit qui additionne deux bits. Il implémente la table d'addition, qui est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Mais un demi-additionneur n'additionne que deux bits, il manque de quoi additionner la retenue. Une solution pour cela est d'enchaîner deux demi-additionneurs : un qui additionne les deux bits de données, et un second qui additionne la retenue au résultat du précédent. La retenue finale se calcule en combinant les sorties de retenue des deux demi-additionneurs, avec une porte OU. Le résultat est un additionneur complet fabriqué avec deux demi-additionneurs. Pour vous en convaincre, établissez la table de vérité de ce circuit, vous verrez que ça marche.
[[File:Full Adder Blocks.svg|centre|vignette|upright=2|Additionneur complet fabriqué avec deux demi-additionneurs.]]
[[File:Half-adder.svg|vignette|upright=1|Circuit d'un demi-additionneur naïf.]]
L'implémentation naïve d'un demi-additionneur utilise une porte XOR et une porte ET, comme illustré ci-contre. Le circuit s'obtient en faisant uen traduction littérale de la table de vérité du circuit. Si je dis qu'elle est naïve, c'est car il est possible de l'optimiser, de manière à éliminer des redondances cachées. Mais nous avions déjà vu cela dans le chapitre sur les incrémenteurs, aussi laissons cela pour plus tard.
En combinant deux demi-additionneurs de ce type, on obtient l'additionneur complet suivant. Les deux sorties de retenue des demi-additionneurs sont combinées avec une porte OU, pour calculer la retenue finale.
{| class="flexible"
|[[File:Full Adder Modules.svg|class=transparent|Composition d'un additionneur complet. On voit bien que celui-ci est composé de deux demi-additionneurs, en rouge et en bleu, auxquels on a ajouté une porte OU pour calculer la retenue finale.]]
|[[File:Full-adder.svg|300px|class=transparent|Circuit d'un additionneur complet.]]
|}
Le fait de combiner les deux retenues avec une porte OU n'est pas ce qu'il y a de plus intuitif. La table de vérité nous dit que ça fonctionne, mais on comprend mal pourquoi. Mais tout devient plus clair quand on sait que les deux retenues ne peuvent pas être à 1 en même temps. Même dans l'addition 1 + 1 + 1, seule une retenue est à 1. Soit l'addition des deux bits d'opérande donne naissance à une retenue, soit c'est l'addition ''résultat + retenue entrante''. La retenue sortant est donc à 1 quand une des deux addition donne une retenue. D'où l'usage d'une porte OU. Remarquez qu'une porte XOR donne le même résultat, vu que le cas où les deux retenues sont à 1 n'est jamais rencontré.
===La propagation et la génération des retenues===
L'additionneur complet que nous allons voir dans cette section sert à introduire les concepts de propagation, génération et absorption de retenue. Il s'agit de concepts très importants quand on étudie les additionneurs. l'idée part d'un principe très simple : la retenue sortante dépend de la retenue d'entrée. La relation entre les deux se résume à trois cas, qui dépendent de la valeur des deux bits additionnés, nommés A et B.
* Dans le premier cas, la retenue entrante est égale à la retenue sortante. On dit que la retenue entrante est propagée par l'additionneur.
* Dans le second cas, la retenue est forcée à 0 : la retenue sortante vaut 0, peu importe la valeur de la retenue entrante.
* Dans le troisième cas, la retenue est forcée à 1 : la retenue entrante vaut 1, peu importe la valeur de la retenue entrante.
[[File:Signaux P et G fournis par un demi-additionneur.png|vignette|Signaux P et G fournis par un demi-additionneur]]
Maintenant, créons un circuit qui nous dise si une retenue est propagée ou générée. Il a deux bits de sortie, nommés P et G : P pour ''Propagate'', G pour ''Generate''. Le bit P est à 1 si la retenue entrante est propagée, il est à 0 sinon. Le bit G est à 1 si une retenue est générée, à 0 sinon. Une retenue est considérée comme absorbée si elle n'est pas ni propagée ni générée, pas besoin d'un troisième bit pour gérer ce cas.
Il se trouve que ce circuit n'est autre qu'un demi-additionneur ! Pour vous en rendre compte, regardez la table de vérité d'un additionneur complet, illustrée ci-dessous. Lorsque les deux bits d'opérande sont à 0, la retenue sortante vaut toujours 0. Si ils sont tous deux à 1, alors la retenue sortante vaut 1. S'ils sont différents, alors retenues sortante et entrante sont égales. Le bit P est donc généré par une simple porte XOR. Quant au bit G, il est à 1 si les deux bits d'opérandes sont à 1, ce qui correspond à une porte ET. Il se trouve que ces deux portes forment un demi-additionneur !
{|class="wikitable"
|-
! Opérande 1 !! Opérande 2 !! Retenue entrante !! !! Retenue sortante
|- class="f_rouge"
||0||0||0|| ||0
|- class="f_rouge"
||0||0||1|| ||0
|- class="f_bleu"
||0||1||0|| ||0
|- class="f_bleu"
||0||1||1|| ||1
|- class="f_bleu"
||1||0||0|| ||0
|- class="f_bleu"
||1||0||1|| ||1
|- class="f_vert"
||1||1||0|| ||1
|- class="f_vert"
||1||1||1|| ||1
|}
Ensuite, créons un circuit qui prend ces deux sorties P et G, et calcule la retenue sortante en fonction. Le circuit en question a trois entrées : la retenue entrante, les deux bits P et G. La retenue sortante vaut 1 soit si une retenue est générée, soit si la retenue entrante est propagée et qu'elle vaut 1. En notant <math>{C_{in}}</math> et <math>{C_{out}}</math> les retenues entrantes et sortante, on a :
: <math>{C_{out}} = G + \left( P . C_{in} \right)</math>
Le circuit est donc composé d'une porte OU et d'une porte ET. En combinant un demi-additionneur avec le circuit de calcul de retenue sortante vu plus haut, on a :
[[File:Additionneur complet avec propagation et génération de retenue.png|class=transparent|centre|vignette|upright=2|Additionneur complet avec propagation et génération de retenue.]]
Certes, on retombe sur le circuit vu plus haut. Mais il est possible de remplacer le circuit en orange par un autre. Par exemple, il est possible de le remplacer par multiplexeur, qui choisit entre la retenue générée et la retenue entrante (propagée). Le choix se fait selon la valeur du bit P, qui chosiit entre propager la retenue et la générer.
[[File:Additionneur crée avec un multiplexeur.png|centre|vignette|upright=1.5|Additionneur crée avec un multiplexeur]]
Le circuit exact est illustré ci-dessous. Il semble utiliser plus de portes logiques que l'additionneur complet précédent. Cependant, nous verrons dans quelques chapitres qu'il est possible d'implémenter un multiplexeur avec seulement 6 transistors, voire moins ! L'implémentation utilise des portes à transmission, mais nous en reparlerons dans le chapitre sur les transistors, quand nous verrons les additionneurs à ''Manchester Carry Chain''. Au passage, une variante de ce circuit a été utilisée dans le processeur processeur 8086 d'Intel, comme on le verra dans le chapitre suivant.
[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
[[File:FulladderPG.png|vignette|upright=1|Additionneur complet avec deux sorties P et G.]]
Pour finir, sachez qu'il existe des additionneurs qui fournissent : le bit de résultat, deux sorties P et G qui indiquent si l'addition propage ou génère une retenue. Il ne s'agit pas d'additionneurs complets, car il manque de quoi calculer la retenue sortante. De tels additionneurs seront appelés des '''additionneurs partiels'''. De tels additionneurs sont utilisés dans certains additionneurs pour gagner en performance.
Enfin, il existe aussi ce que j'ai décidé d'appeler des '''additionneurs P/G''', qui sont des additionneurs complets auxquels on a ajouté deux sorties P et G, en plus de la sortie de retenue. Leur circuit est le même que celui d'un additionneur complet, auquel on a ajouté deux fils. Vu que ces deux sorties sont fournies par le premier demi-additionneur, ajouter ces deux sorties demande d'ajouter des fils, pas de portes logiques. De tels additionneurs sont aussi utilisés dans certains additionneurs pour gagner en performance.
===L'additionneur complet basé sur une porte à majorité===
Maintenant, voyons une dernière implémentation possible de l'additionneur complet. Mais avant de voir comment l'additionneur est implémenté, nous allons devoir faire un petit focus sur la retenue sortante. Reprenons la table de vérité, et regardons ce qu'il en est pour la retenue sortante uniquement.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante
|-
||0||0||0|| ||0
|-
||0||0||1|| ||0
|-
||0||1||0|| ||0
|-
||0||1||1|| ||1
|-
||1||0||0|| ||0
|-
||1||0||1|| ||1
|-
||1||1||0|| ||1
|-
||1||1||1|| ||1
|}
Vous remarquerez que la retenue sortante est égale au bit majoritaire, parmi les trois bits d'opérande. Si deux ou trois bits sont à 1, la retenue sortante vaut 1. Et réciproquement avec 0. Le circuit de calcul de la retenue peut donc être remplacé par une '''porte à majorité'''.
[[File:Additionneur crée avec une porte à majorité.png|centre|vignette|upright=2|Additionneur crée avec une porte à majorité]]
Mais une porte à majorité est assez gourmande en circuit, ce qui fait qu'un additionneur pareil ne serait pas pratique, ni utile. Du moins, ce serait le cas s'il n'y avait pas une possibilité d'optimisation extrêmement intéressante. Il est possible de calculer le 'bit de somme' à partir de la retenue sortante ! En effet, le bit de somme est l'inverse de la retenue sortante, sauf dans deux cas : les trois bits d'entrée sont à 0, où ils sont tous à 1. Le bit de somme vaut 0 dans le premier cas, 1 dans le second cas.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|- class="f_bleu"
||0||0||0|| ||0||0
|- class="f_vert"
||0||0||1|| ||0||1
|- class="f_vert"
||0||1||0|| ||0||1
|- class="f_vert"
||0||1||1|| ||1||0
|- class="f_vert"
||1||0||0|| ||0||1
|- class="f_vert"
||1||0||1|| ||1||0
|- class="f_vert"
||1||1||0|| ||1||0
|- class="f_rouge"
||1||1||1|| ||1||1
|}
En clair, 6 lignes de la table de vérité sur 8 peuvent se calculer avec une porte à majorité, le reste demandant quelques portes logiques pour faire la correction. L'implémentation en circuit demande donc trois choses :
* de calculer la retenue sortante et de quoi l'inverser ;
* un circuit qui vérifie si tous les bits opérande valent 0 : une porte NOR ou OU fait l'affaire ;
* un circuit qui vérifie s'ils valent tous 1, à savoir une porte ET.
Ensuite, on combine le résultat des trois circuits précédents pour obtenir le résultat final. La combinaison est le fait du circuit en jaune dans le schéma ci-dessous.
[[File:Full adder basé sur une modification de la retenue.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue]]
Le circuit en jaune est assez simple. Quand la porte ET sort un 1, la sortie doit être forcée à 1. Par contre, quand la porte OU sort un zéro, la sortie doit être forcée à 0. Il suffit donc d'enchainer un circuit de mise à 1 et un circuit de mise à 0. Nous avons vu dans le chapitre sur les opérations de masquage qu'il s'agit respectivement d'une porte OU et d'une porte ET.
[[File:Full adder basé sur une modification de la retenue interne, au complet.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue interne, au complet]]
Divers processeurs utilisaient des additionneurs complets de ce type : le fameux Z80, l'Intel 4004 et de l'Intel 8008 sont les exemples les plus notables. Pourtant, on pourrait s'étonner qu'un tel circuit ait existé. Il utilise beaucoup de portes logiques, a une profondeur logique supérieure : il n'a rien d'avantageux. Sauf qu'il était utilisé sur d'anciens processeurs, qui utilisaient la technologie dite TTL, différente de la technologie CMOS des transistors modernes. Et avec la technologie TTL, il est possible de fusionner plusieurs portes logiques ET et NOR en une seule porte logique ET/OU/NON ! Un additionneur complet construit ainsi ne prenait que deux portes logiques : une pour le calcul de la retenue sortante, une autre pour le reste du circuit.
==L'addition non signée==
Voyons maintenant un circuit capable d'additionner deux nombres entiers: l''''additionneur'''. Dans la version qu'on va voir, ce circuit manipulera des nombres strictement positifs. L'addition des nombres codés en complètement à deux sera vu dans une section ultérieure.
===L'additionneur série===
Il est possible d'additionner deux nombres bit par bit, grâce à un additionneur complet associé à plusieurs registres à décalages. Les opérandes sont placées chacune dans un registre à décalage, et l'additionneur complet utilise les bits sortants de ces registres à décalage. Le bit du résultat est envoyé au registre à décalage pour le résultat. La retenue de l'addition est stockée dans une bascule de 1 bit, en attente du prochain cycle d'horloge. Un tel additionneur est appelé un '''additionneur série'''. Il a été utilisé sur d'anciens ordinateurs dans les années 50-60, aussi bien des prototypes que des ordinateurs commerciaux.
[[File:Additionneur série.jpg|centre|vignette|upright=2.5|Additionneur série.]]
===L'additionneur à propagation de retenue===
L''''additionneur à propagation de retenue''' est aussi appelé l'additionneur ''ripple carry'', c'est son nom anglais. J'utiliserais occasionnellement le nom anglais dans ce qui suit, car celui-ci est plus court. Ilutilise un additionneur complet pour chaque colonne de l'addition. Pour gérer les retenues, il suffit ainsi de câbler des additionneurs complets les uns à la suite des autres, chacun envoyant sa retenue à la colonne suivante.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Notons la présence de la retenue sortante, qui est utilisée pour détecter les débordements d'entier, ainsi que pour d'autres opérations. Le bit de retenue final est souvent stocké dans un registre spécial du processeur (généralement appelé ''carry flag'').
Notez aussi, à droite du schéma précédent, l'entrée de retenue <math>\text{R}_\text{Entrée}</math>. Elle est très utile pour l'implémentation de certaines opérations comme l'inversion de signe, la soustraction, l'incrémentation, etc. Certains processeurs sont capables de faire une opération appelée ADC, ADDC ou autre nom signifiant ''Addition with Carry'', qui permet de faire le calcul A + B + Retenue (la retenue en question est la retenue sortante de l'addition précédente, stockée dans le registre ''carry flag''). Son utilité principale est de permettre des additions d'entiers plus grands que ceux supportés par le processeur. Par exemple, cela permet de faire des additions d'entiers 32 bits sur un processeur 16 bits.
[[File:Adder Network 3 Bit Sum Delay 003.svg|vignette|upright=0.5|Propagation de retenue dans l'additionneur.]]
Cet additionneur utilise très peu de portes logiques, ce qui fait qu'il était utilisé sur certains processeurs 8 et 16 bits, dont le budget en portes logiques était limité. Bien que très simple, cet additionneur est cependant peu performant. Son temps de calcul est proportionnel à la taille des opérandes. Par exemple, additionner deux opérandes de 32 bits prendra deux fois plus de temps que pour des opérandes de 16 bits. La raison est que les retenues doivent se propager à travers le circuit, du premier additionneur jusqu'au dernier.
L'addition étant une opération fréquente, d'autres additionneurs ont été inventés. Ils utilisent des optimisations qui utilisent plus de circuits pour gagner en rapidité. Mais avant de voir ces optimisations, nous allons voir s'il est possible d'optimiser les additionneurs à propagation de retenue.
===L'additionneur à saut de retenue===
L'additionneur à propagation de retenue peut être optimisé de deux manières. La première accélère la propagation de la retenue, en agissant au niveau des additionneurs complets. La ''Manchester carry chain'' est une optimisation de ce type, mais on ne peut pas encore expliquer à ce stade du cours, car elle optimisait les additionneurs complets au niveau des transistors eux-mêmes. Elle a été utilisée sur de nombreux processeurs connus, comme le 8086 d'Intel ou le Z80.
La seconde manière rend l'addition plus rapide dans certains cas. Le résultat est un additionneur dont le temps de calcul est variable. Le calcul prendra quelques cycles d'horloges avec certains opérandes, mais d'autres opérandes ne verront aucune amélioration. Il n'améliore pas le pire des cas, dans lequel la retenue doit être propagée du début à la fin, du bit de poids faible au bit de poids fort. Mais dans d'autres cas, il permet d'avoir le résultat en avance. L'optimisation en question donne l''''additionneur à saut de retenue''' (''carry-skip adder'').
Il est composé de blocs qui additionnent 4/5 bits, rarement plus. La taille d'un bloc varie d'un additionneur à l'autre, elle peut même varier dans l'additionneur, mais nous allons prendre un exemple où les blocs font 4 bits. L'idée est de détecter précocement si la retenue entrante est propagée à travers tout le bloc. Si la retenue est propagée dans tous le bloc, on peut directement l'envoyer sur la sortie de retenue, la retenue saute le bloc entier. Dans le cas contraire, on doit calculer la retenue normalement. Le choix entre les deux est le fait d'un multiplexeur. Toute la difficulté est de savoir comment commander le multiplexeur.
[[File:Carry skip adder, principe de base.png|centre|vignette|upright=2.5|Carry skip adder : principe de base]]
Pour savoir si une retenue est propagée dans un bloc entier, il faut utiliser des additionneurs complets, qui ont en plus une sortie P indiquant que l'additionneur propage la retenue. Le bloc propage une retenue si chaque additionneur complet propage la retenue, donc s'ils ont tous leur sortie P à 1. Un simple ET logique suffit à déterminer si c'est le cas. Le signal de commande du multiplexeur est donc un vulgaire ET entre toutes les sorties P des additionneurs complets du bloc.
[[File:Additionneur à saut de retenue.png|centre|vignette|upright=2.5|Calcul de la commande du MUX.]]
Voici ce que ça donne pour un bloc de 4 bits :
[[File:CSAdder4Bit.svg|centre|vignette|upright=1.5|Additionneur ''carry skip'' : bloc de 4 bits.]]
L'additionneur à saut de retenue est construit en assemblant plusieurs blocs de ce type.
[[File:BCSAdder16Bit.svg|centre|vignette|upright=3|Additionneur à saut de retenue.]]
Les blocs sont tous identiques dans le cas le plus simple, mais il est possible d'utiliser des blocs de taille variable. Par exemple, le premier bloc peut avoir des opérandes de 6 bits, le second des opérandes de 7 bits, etc. Faire ainsi permet de gagner un petit peu en performances, si la taille de chaque bloc est bien choisie. La raison est une question de temps de propagation des retenues. La retenue met plus de temps à se propager à travers 8 blocs qu'à travers 4, ce qui prend plus de temps qu'à travers 2 blocs, etc. En tenir compte fait que la taille des blocs tend à augmenter ou diminuer quand on se rapproche des bits de poids fort.
===L'additionneur à sélection de retenue===
L''''additionneur à sélection de retenue''' utilise aussi des blocs, comme les additionneurs précédents. L'addition se fait en deux versions : une avec la retenue du bloc précédent valant zéro, et une autre version avec la retenue du bloc précédent valant 1. Il suffira alors de choisir le bon résultat avec un multiplexeur, une fois cette retenue connue. On gagne ainsi du temps en calculant à l'avance les valeurs de certains bits du résultat, sans connaître la valeur de la retenue. Petit détail : sur certains additionneurs à sélection de retenue, les blocs de base n'ont pas la même taille. Cela permet de tenir compte des temps de propagation des retenues entre les blocs.
[[File:Additionneur à sélection de retenue.png|centre|vignette|upright=1.5|Additionneur à sélection de retenue avec seulement deux blocs.]]
Dans les exemples du dessus, chaque sous-additionneur étaient des additionneurs à propagation de retenue. Mais ce n'est pas une obligation, et tout autre type d’additionneur peut être utilisé. Par exemple, on peut faire en sorte que les sous-additionneurs soient eux-mêmes des additionneurs à sélection de retenue, et poursuivre ainsi de suite, récursivement. On obtient alors un '''additionneur à somme conditionnelle''', plus rapide que l'additionneur à sélection de retenue, mais qui utilise beaucoup plus de portes logiques.
===Les additionneurs à anticipation de retenue===
Les '''additionneurs à anticipation de retenue''' calculent chaque retenue sans avoir à propager les retenues précédentes. Au lieu de calculer les retenues une par une, ils calculent toutes les retenues en parallèle, à partir des bits des opérandes. Une fois les retenues pré-calculées, il suffit de les additionner avec les deux bits adéquats, pour obtenir le résultat. Ces additionneurs sont composés de deux parties :
* un circuit qui pré-calcule les retenues, sans les propager, directement à partir des opérandes ;
* d'une couche d'additionneurs complets, qui additionnent chacun deux bits d'opérande et la retenue pré-calculée.
[[File:Additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Additionneur à anticipation de retenue.]]
Le calcul des retenues se fait en deux étapes. La première détermine si les retenues sont propagées ou générées, pour chaque colonne. Elle détermine, pour chaque colonne, les bits P et G qui indiquent respectivement : qu'une retenue a été générée sur cette colonne, que la colonne propage la retenue précédente. La seconde étape utilise ces signaux P et G pour déterminer la retenue sur chaque colonne. Le circuit de calcul des retenues est donc composé de deux parties : une couche de demi-additionneurs pour générer les signaux P et G, un circuit de calcul des retenues proprement dit.
[[File:Circuit complet d'un additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Circuit complet d'un additionneur à anticipation de retenue.]]
Pour rappel, la retenue de la colonne i, notée <math>C_i</math> est égale à :
: <math>G_i + (P_i . C_{i-1})</math>, avec G le signal de génération de retenue, P le signal de propagation de retenue.
Si on utilisait cette formule sans trop réfléchir, on retomberait sur un additionneur à propagation de retenue inutilement compliqué. L'astuce des additionneurs à anticipation de retenue consiste à remplacer le terme Ci−1 par sa valeur calculée avant. Par exemple, je prends un additionneur 4 bits. Je dispose de deux nombres A et B, contenant chacun 4 bits : A3, A2, A1, et A0 pour le nombre A, et B3, B2, B1, et B0 pour le nombre B. Si j'effectue les remplacements, j'obtiens les formules suivantes :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Ces formules nous permettent de déduire la valeur d'une retenue directement : il reste alors à créer un circuit qui implémente ces formules, et le tour est joué.
[[File:Four bit adder with carry lookahead.svg|centre|vignette|upright=1.5|Additionneur à anticipation de retenue de 4 bits.]]
Plus haut, j'ai dit que le circuit était composé de trois couches : une couche de demi-additionneur pour les signaux P et G ; un circuit d'anticipation de retenue, puis l'addition finale. Mais les deux bits d'opérandes sont déjà additionnés dans la couche de demi-additionneur, autant réutiliser le résultat pour l'additionner à la retenue. Pour éliminer toute redondance de ce type, il est possible d'utiliser des additionneurs partiels (des additionneurs où la sortie de retenue a été remplacée par deux sorties P et G). L'additionneur final est alors composé de plusieurs additionneurs complets de ce type, couplé à un circuit d'anticipation de retenue.
[[File:Cla4bits.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue de 4 bits.]]
Les additionneurs à anticipation de retenue sont plus rapides que les additionneurs à propagation de retenue. La raison est que les retenues sont calculées rapidement, dans un temps plus petit qu'en propageant la retenue. Pour rentrer dans les détails mathématiques, le temps de calcul n'est pas proportionnel au nombre de bits, mais proportionnel au logarithme de ce nombre de bits. Par contre, le gain en temps de calcul s'accompagne d'un cout en portes logiques conséquent. Le circuit qui calcule les retenues anticipées utilise beaucoup de portes logiques.
===Les additionneurs à calcul parallèle de préfixe===
Du fait du cout en portes logiques, utiliser un additionneur à anticipation de retenue est une solution pratique pour additionner 4 ou 8 bits. Mais pour des opérandes plus grands, de 16 ou 32 bits, cela utiliserait trop de portes logiques. Une partie de ce cout en portes logiques est cependant lié à des redondances. Si on analyse deux retenues différentes, à des colonnes différentes. Les circuits qui calculent ces deux retenues seront différents, mais ils contiendront des sous-circuits identiques. Pour vous en rendre compte, reprenons les quatre équations obtenues précédemment et essayez de trouver des redondances :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Vous devriez trouver :
* le terme ''P0 · C0'' dans toutes les lignes ;
* les termes ''P1 · P0 · C0'' et ''P1 · P0'' dans les trois dernières lignes ;
* le terme ''P2 · P1 · P0 · C0'' ; ''P2 · P1 · G0'' et ''P2 · G1'' dans les deux dernières lignes.
Il existe plusieurs manières d'exploiter de telles redondances, exploitées dans les '''additionneurs à calcul parallèle de préfixe'''. L'optimisation apportée est de générer des signaux ''propagate'' et ''generate'' pour des groupes de 2, 3, 4, ..., N colonnes. Par exemple, il est possible de générer un signal ''P 0 vers 3'', qui indique si la retenue de la première colonne est propagée jusqu'à la 4ème colonne. Un autre exemple est un signal qui indique qu'une retenue a été générée entre les colonnes 4 à 7 génèrent une retenue ou non. En clair, les signaux P et G ont maintenant un intervalle, qui précise de quelle colonne vers quelle colonne se fait la propagation, ou entre quelles colonnes se fait la génération.
Il est possible de combiner les signaux P et G de deux groupes de colonne, s'ils sont contiguës (ils peuvent aussi se recouvrir). Par exemple, il est possible de calculer les bits P et G pour les colonnes 0 à 10, à partir des deux signaux P/G des colonnes 0-4 et 5-10. Pour cela, les équations sont assez simples. Si je prends deux groupes nommés A et B, avec A pour les colonnes de poids fort et B celles de poids faible, on a :
: <math>P_{sortie} = P_A . P_B</math>
: <math>G_{sortie} = G_A . P_A . G_B</math>
Pour les bits des colonnes 2, 4, 8, 16, 32, 64, et autres, le calcul est simple. L'idée est de grouper les colonnes par groupes de 2. Puis, on calcule chaque groupe de 4 bits à partir de deux groupes de 2. Puis les groupes de 8 bits sont calculés à partir de deux groupes de 4 bits, et ainsi de suite pour toute puissance de deux.
[[File:Squelette d'un additionneur à calcul parallèle de préfixe.jpg|centre|vignette|upright=2|Squelette d'un additionneur à calcul parallèle de préfixe]]
Les seules difficultés surviennent pour les colonnes dont le nombre n'est pas une puissance de deux. Il y a plusieurs manières pour subdiviser ces colonnes en intervalles plus petits et combiner le tout. Et elles donnent chacune des additionneurs différents. Ils portent les noms d'''additionneur de Ladner-Fisher'', d'''additionneur de Brent-Kung'', d'''additionneur de Kogge-Stone'', etc. Dans le cas général, l'additionneur de Kogge-Stone est un des plus rapide, mais aussi un des plus gourmand en circuits, alors que c'est l'inverse pour l'additionneur de Brent-Kung. Les deux additionneurs sont illustrés ci-dessous, pour des opérandes de 8 bits. Dans ceux-ci, chaque losange calculent les bits P et G, à partir des signaux P et G de deux groupes précédents.
{|
|[[File:Brent-kung-8-bit.png|vignette|Brent-kung-8-bit]]
|[[File:Kogge-stone-8-bit.png|vignette|upright=1.2|Kogge-stone-8-bit]]
|}
===Le calcul parallèle de la retenue===
L'anticipation de retenue est très rapide, mais utilise beaucoup de circuits, même en utilisant le calcul parallèle de préfixe. Les additionneurs à propagation de retenue font eux le compromis inverse. Mais il existe des intermédiaires, qui visent à obtenir un compromis entre performance et cout en portes logiques.
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Un de ces compromis découpe un additionneur de 16/32/64 bits en additionneurs de 4/5 bits, qui sont enchainés en utilisant la propagation de retenue. Par contre, ces petits additionneurs de 4/5 bits utilisent l'anticipation de retenue. Ces additionneurs simples seront nommés ''blocs'' dans ce qui suit, et l'un d'entre eux est illustré ci-contre. Vous remarquerez qu'un bloc a une retenue entrante et une retenue sortante. Par exemple, on peut utiliser 4 additionneurs à anticipation de retenue de 4 bits, et propager les retenues entre eux. Le premier bloc calcule une retenue, qui est propagée au second bloc. Le second bloc calcule lui aussi une retenue, qui est propagée au troisième bloc, etc.
[[File:4008 Functional Diagram.svg|vignette|upright=1|4008 Functional Diagram]]
Le gain en performance est significatif, mais il est possible de faire presque aussi rapide, tout en économisant beaucoup de circuits. L'idée est de simplifier les additionneurs 4 bits, afin de ne pas utiliser une anticipation de retenue complète. L'anticipation de retenue n'est utilisée que pour la retenue sortante, alors que les bits de somme sont calculées avec propagation de retenue. La retenue sortante est donc calculée en parallèle de l'addition. Chaque bloc contient, à côté d'un additionneur proprement dit, un circuit qui calcule la retenue sortante. Il existe de nombreuses manières de calculer la retenue sortante.
Le gain en portes logiques est assez significatif. En effet, on se passe de circuits d'anticipation de retenue pour les 4 bits de somme, on ne garde que celui pour la retenue sortante. Le fait d'utiliser la propagation de retenue pour calculer les 4 bits de somme est censé avoir un léger cout en performance, mais il se trouve que ce cout est très faible. En effet, le résultat n'est connu qu'une fois les retenues propagées jusqu'au dernier bloc. Et malgré l'anticipation des retenues, la propagation est assez lente. les additionneurs à propagation de retenue auront terminé leur travail bien avant.
Un exemple est celui de l'additionneur CMOS 4008, un additionneur de 4 bit. Il est composé en trois sections. Une première couche de demi-additionneurs calcule les signaux P et G utilisés à la fois pour l'anticipation de la retenue sortante, que pour calculer les bits de somme. Le circuit de calcul de la retenue sortante utilise l'anticipation de retenue. Le reste du circuit fait l'addition en propageant les retenues.
[[File:CMOS 4008, circuit découpé en sections.png|centre|vignette|upright=1.5|CMOS 4008, circuit découpé en sections]]
===Les additionneurs à anticipation de retenue multi-niveau===
Les additionneurs précédents mixent anticipation et propagation de retenue. Ceux que nous allons voir utilisent des blocs, mais utilisent l'anticipation de retenue entre les blocs. Les blocs utilisés peuvent être un additionneur à propagation de retenue, ou à anticipation de retenue, peu importe. Ce qui compte est que la retenue entrante de chaque bloc est calculée par anticipation de retenue.
Par exemple, il est possible de créer un additionneur 16 bits en utilisant 4 additionneurs/blocs de 4 bits, couplé à un circuit d’anticipation de retenue par bloc. Pour cela, les additionneurs de 4 bits doivent fournir deux sorties P et G. Les deux sorties indiquent que le bloc soit a généré une retenue, soit a propagé la retenue entrante. Par propagé, on veut dire que la retenue entrante du bloc s'est retrouvée sur la sortie de retenue du bloc. C'est le cas si les 4 colonnes propagent la retenue, ce qui signifie que :
: <math>P_\text{4 bits} = P_0 \cdot P_1 \cdot P_2 \cdot P_3</math>
Pour la génération, cela veut dire qu'une colonne a générée une retenue, qui a été propagée jusqu'à la sortie. En clair, pour chaque colonne, il faut que le G = 1, et que les signaux P des colonnes soient à 1. Pour les 4 colonnes, cela donne :
: <math>G_\text{4 bits} = G_3 + G_2 \cdot P_3 + G_1 \cdot P_3 \cdot P_2 + G_0 \cdot P_3 \cdot P_2 \cdot P_1</math>
[[File:Cla4bitsPG.png|centre|vignette|upright=2|Additionneur à anticipation de retenue de 4 bits.]]
Pour combiner 4 blocs de 4 bits, on devra donc combiner 4 bits P et G, avec un circuit d'anticipation de retenue.
[[File:16-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 16 bits.]]
Il est même possible d'aller plus loin et de combiner 4 additionneurs précédents pour obtenir un additionneur 64 bits.
[[File:64-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 64 bits.]]
Le résultat est un additionneur à plusieurs niveaux d’anticipation de retenue. Une anticipation de retenue dans un blocs de 4 bits (facultative), entre blocs de 4 bits, entre blocs de 16 bits. La même logique peut être utilisée avec des blocs de taille différente de 4, 16 et 64 bits.
==L'addition signée et la soustraction==
Après avoir vu l'addition, il est logique de passer à la soustraction, les deux opérations étant très proches. Si on sait câbler une addition entre entiers positifs, câbler une soustraction n'est pas très compliqué. De plus, la soustraction permet de faire des additions de nombres signés.
===Le soustracteur pour opérandes entiers===
Pour soustraire deux nombres entiers, on peut adapter l'algorithme de soustraction utilisé en décimal, celui que vous avez appris à l'école. Celui-ci ressemble fortement à l'algorithme d'addition : on soustrait les bits de même poids, et on propage éventuellement une retenue sur la colonne suivante. À la différence de l'addition, la retenue est soustraite, et non ajoutée. La table de soustraction nous dit quel est le résultat de la soustraction de deux bits. La voici :
* 0 - 0 = 0 ;
* 0 - 1 = 1 et une retenue ;
* 1 - 0 = 1 ;
* 1 - 1 = 0.
[[File:Algebra1 05 fig014.svg|centre|vignette|Soustraction en binaire, avec les retenues en rouge.]]
La table de soustraction peut servir de table de vérité pour construire un circuit qui soustrait deux bits. Celui-ci est appelé un '''demi-soustracteur'''. Il ressemble beaucoup à un demi-additionneur, les différences se résumant à une porte NON ajoutée pour le calcul de la retenue.
[[File:Half Subtractor Vektor.svg|centre|vignette|Demi-soustracteur.]]
Comme pour l'additionneur, deux demi-soustracteurs peuvent être combinés pour donner un '''soustracteur complet'''. Le calcul de la retenue se fait en combinant les deux retenues des demi-soustracteurs avec une porte OU. Les soustracteurs complets sont utilisés pour créer des soustracteurs à propagation de retenue ou tout autre circuit soustracteur, sur le même modèle que les additionneurs.
[[File:FullSubtractor.svg|centre|vignette|upright=2|Soustracteur complet.]]
Vous l'aurez sans doute remarqué, mais le demi-soustracteur ressemble beaucoup au demi-additionneur. La seule différence est une porte NON ajoutée au bon endroit dans le demi-soustracteur.
{|
|[[File:Half-adder.svg|vignette|Demi-additionneur]]
|[[File:Half Subtractor Vektor.svg|vignette|upright=1.3|Demi-soustracteur]]
|}
La conséquence est qu'il est possible de créer un circuit capable de faire à la fois des additions et des soustractions. L'idée est de remplacer la porte NON du demi-soustracteur par un inverseur commandable. Si on envoie un zéro sur l'entrée de commande, l'inverseur commandable se comportera comme une porte OUI. Le circuit sera alors identique à un demi-additionneur. Si on envoie un 1 sur l'entrée de commande, l'inverseur commandable se comportera comme une porte NON. le circuit sera alors un demi-soustracteur. Il faut faire cela pour chaque demi-soustracteur, ce qui donne ceci pour un soustracteur complet. le résultat est alors appelé un '''additionneur-soustracteur complet'''.
[[File:Additionneur-soustracteur complet.png|centre|vignette|upright=2|Additionneur-soustracteur complet]]
===L'additionneur-soustracteur pour opérandes codées en complément à deux===
Étudions le cas de la soustraction en complément à deux, dans l'objectif de créer un circuit soustracteur. Vous savez sûrement que a−b et a+(−b) sont deux expressions équivalentes. Et en complément à deux, − b = not(b) + 1. Dit autrement, a − b = a + not(b) + 1. On pourrait se dire qu'il faut deux additionneurs pour faire le calcul, mais la majorité des additionneurs possède une entrée de retenue pour incrémenter le résultat de l'addition. Un soustracteur en complément à deux est donc simplement composé d'un additionneur et d'un inverseur.
[[File:Additionneur soustracteur.png|centre|vignette|upright=2|Soustracteur en complément à deux.]]
Il est possible de créer un circuit capable d'effectuer soit une addition, soit une soustraction : il suffit de remplacer l'inverseur par un inverseur commandable, qui peut être désactivé. On a vu comment créer un tel inverseur commandable dans le chapitre sur les circuits combinatoires. On peut remarquer que l'entrée de retenue et l'entrée de commande de l'inverseur sont activées en même temps : on peut fusionner les deux signaux en un seul.
[[File:Additionneur-soustracteur en complément à deux.png|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux.]]
Une implémentation alternative est la suivante. Elle remplace l'inverseur commandable par un multiplexeur.
[[File:4-bit ripple carry adder-subtracter.svg|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux, version alternative.]]
===L'additionneur-soustracteur pour opérandes codées en signe-magnitude===
Passons maintenant aux nombres codés en signe-valeur absolue, les deux opérandes étant notées A et B. Suivant les signes des deux opérandes, on a quatre cas possibles : A + B, A − B (B négatif), −A + B (A négatif) et −A − B (A et B négatifs). Une astuce est que le circuit n'a besoin que de calculer A + B et A − B : il peut les inverser pour obtenir − A − B ou B − A. A + B et A − B peuvent se calculer avec un additionneur-soustracteur, reste à corriger le résultat. Il suffit de lui ajouter un inverseur commandable pour obtenir le circuit d'addition finale.
[[File:Additionneur en signe-valeur absolue.png|centre|vignette|upright=2|Additionneur en signe-valeur absolue.]]
Toute la difficulté tient dans le calcul du bit de signe du résultat, quand interviennent des soustractions. Autant l'addition de deux nombres de même signe (A + B et −A − B) ne pose aucun problème, autant les soustractions posent problème (A − B et −A + B). Suivant que <math>A<B</math> ou que <math>A>B</math>, le signe du résultat ne sera pas le même. Déterminer le signe du résultat se fait en regardant les bits de débordement d'entier, comme on le verra plus bas.
===L'additionneur-soustracteur pour opérandes codées en représentation par excès===
Passons maintenant aux nombres codés en représentation par excès. On pourrait croire que ces nombres s'additionnent comme des nombres non-signés, mais ce serait oublier la présence du biais, qui pose problème. Dans les cas de nombres signés gérés avec un biais, voyons ce que donne l'addition de deux nombres :
: <math>( n_1 + biais ) + ( n_2 + biais ) = n_1 + n_2 + 2 \times biais</math>
Or, le résultat correct serait :
: <math>n_1 + n_2 + biais</math>
En effectuant l'addition telle quelle, le biais est compté deux fois. On doit donc le soustraire après l'addition pour obtenir le résultat correct.
Même chose pour la soustraction qui donne ceci :
: <math>(n_1 + biais) - (n_2 + biais) = n_1 - n_2</math>
Or, le résultat correct serait :
: <math> ( n_1 - n_2 ) + biais </math>
Il faut rajouter le biais pour obtenir l'exposant correct.
On a donc besoin de deux additionneurs/soustracteurs : un pour additionner/soustraire les représentations binaires des opérandes, et un autre pour ajouter/retirer le biais en trop/manquant.
==L'additionneur BCD==
Maintenant, voyons un additionneur qui additionne deux entiers au format BCD. Pour cela, nous allons devoir passer par deux étapes. La première est de créer un circuit capable d'additionneur deux chiffres BCD. Ensuite, nous allons voir comment enchaîner ces circuits pour créer un additionneur BCD complet.
===L'additionneur BCD qui fait l'opération chiffre par chiffre===
Nous allons commencer par voir un additionneur qui additionne deux chiffres en BCD, une sorte d'équivalent BCD de l'additionneur complet. Il fournit un résultat sur 4 bits et une retenue qui est mise à 1 si le résultat dépasse 10 (la limite d'un chiffre BCD). Les deux opérandes sont des chiffres BCD codés sur 4 bits et sont additionnés en binaire par un additionneur des plus normaux, similaire à ceux vus plus haut. Le résultat est alors un entier codé en binaire, sur 5 bits, qu'on corrige/convertit pour obtenir un chiffre BCD et une retenue sortante.
Pour corriger le résultat, une idée intuitive serait de prendre le résultat et de faire une division par 10. Le quotient donne la retenue, alors que le reste est le résultat, le chiffre BCD. Mais un circuit diviseur par 10 utilise beaucoup de portes logiques, ce qui ne vaut pas le coup. Une autre méthode détecte si le résultat est égal ou supérieur à 10, ce qui correspond à un "débordement" (on dépasse les limites d'un chiffre BCD). Si le résultat est plus petit que 10, il n'y a rien à faire : le résultat est bon et la retenue est de zéro. Par contre, si le résultat vaut 10 ou plus, il faut corriger le résultat et générer une retenue à 1.
Il faut donc ajouter un circuit qui détecte si le résultat est supérieur à 9, qui calcule directement la retenue. Ce circuit peut se fabriquer simplement à partir de sa table de vérité, ou en utilisant les techniques que nous verrons dans un chapitre ultérieur sur les comparateurs. La solution la plus simple est clairement d'utiliser la table de vérité, ce qui est très simple, assez pour être laissé en exercice au lecteur. Pour comprendre comment corriger le résultat, établissons une table de vérité qui associe le résultat et le résultat corrigé. L'entrée vaut au minimum 10 et au maximum 9 + 9 = 18. On considère la sortie comme un tout, la retenue étant un 5ème bit, le bit de poids fort.
{|class="wikitable"
|-
! colspan="5" | Entrée
!
! rowspan="10" |
! Retenue
! Résultat corrigé (sans retenue)
! interprétation de la sortie en binaire (retenue inclue)
|-
| 0 || 1 || 0 || 1 || 0
| (10)
| 1 || 0000
| (16)
|-
| 0 || 1 || 0 || 1 || 1
| (11)
| 1 || 0001
| (17)
|-
| 0 || 1 || 1 || 0 || 0
| (12)
| 1 || 0010
| (18)
|-
| 0 || 1 || 1 || 0 || 1
| (13)
| 1 || 0011
| (19)
|-
| 0 || 1 || 1 || 1 || 0
| (14)
| 1 || 0100
| (20)
|-
| 0 || 1 || 1 || 1 || 1
| (15)
| 1 || 0101
| (21)
|-
| 1 || 0 || 0 || 0 || 0
| (16)
| 1 || 0110
| (22)
|-
| 1 || 0 || 0 || 0 || 1
| (17)
| 1 || 0111
| (23)
|-
| 1 || 0 || 0 || 1 || 0
| (18)
| 1 || 1000
| (24)
|}
En analysant le tableau, on voit que pour corriger le résultat, il suffit d'ajouter 6. La raison est que le résultat déborde d'un nibble à 16 en binaire, mais à 10 en décimal : il suffit d'ajouter la différence entre les deux, à savoir 6, et le débordement binaire fait son travail. Donc, la correction après une addition est très simple : si le résultat dépasse 9, on ajoute 6.
On peut maintenant implémenter l'additionneur BCD, en combinant le comparateur avec 10, le circuit de correction, et l'additionneur. La première solution calcule deux versions du résultat : la version corrigée, la version normale. Le choix entre les deux est réalisée par un multiplexeur, commandé par le comparateur.
[[File:Additionneur BCD.png|centre|vignette|upright=1.5|Additionneur BCD]]
L'autre solution utilise un circuit commandable qui soit additionne 6, soit ne fait rien. Le choix entre les deux est commandé par le bit calculé par le comparateur.
[[File:Additionneur BCD, seconde version.png|centre|vignette|upright=1.5|Additionneur BCD, seconde version.]]
Une version alternative du circuit précédent est la suivante. Il contient deux additionneurs : un pour additionner les deux chiffres BCD, un autre pour additionner 6 si besoin. Le résultat du comparateur est directement utilisé pour générer l'opérande du second additionneur : 0 ou 6. Le circuit est simple à concevoir, mais gaspille beaucoup de circuit. Idéalement, il vaudrait mieux utiliser un circuit combinatoire d'addition avec une constante.
[[File:Adder 4 Bit BCD.svg|centre|vignette|upright=2|Additionneur BCD, circuit complet.]]
Pour obtenir un additionneur BCD complet, il suffit d’enchaîner les additionneurs précédents, comme on le ferait avec les additionneurs complets dans un additionneur à propagation de retenue. Au final, l'additionneur BCD est beaucoup plus compliqué qu'un additionneur normal, car il rajoute un comparateur ">9", un petit additionneur pour ajouter 6 et éventuellement d'autres circuits. De plus, il est difficile d'appliquer les optimisations disponibles sur les additionneurs non-BCD. Notamment, les circuits d'anticipation de retenue sont totalement à refaire et le résultat est relativement compliqué. C'est ce qui explique pourquoi le BCD a progressivement été abandonné au profit du binaire simple.
La soustraction en BCD se fait comme en binaire : le nombre à soustraire est remplacé par son complément, le circuit additionne le complément et l'autre opérande, le débordement d'entier fait que le résultat marche. Sauf qu'ici, le complément est un complément à 9. Il se calcule chiffre par chiffre : chaque chiffre est remplacé par (9 - le chiffre en question).
===L'additionneur BCD par ajustement décimal===
L'additionneur BCD précédent effectuait son travail chiffre BCD par chiffre BCD, mais il existe des additionneurs BCD qui font autrement. Sur les premiers processeurs x86, il n'y avait pas d'opération d'addition BCD proprement dit, seulement une addition binaire normale de 8, 16 ou 32 bits. Par contre, elle était secondée par une opération dite d''''ajustement décimal''' qui transformait un nombre binaire en nombre codé en BCD. L'opération d'ajustement décimal prenait un opérande de 8 bits codé en binaire et fournissait un résultat de la même taille, c'est à dire deux chiffres BCD. Effectuer une addition BCD demandait donc de faire deux opérations à la suite : une addition binaire simple, suivie par l'opération d'ajustement décimal. Cela permettait de gérer des nombres entiers en binaire usuel et des entiers BCD sans avoir deux instructions d'addition séparées pour les deux, sans compter que cela simplifiait aussi les circuits d'addition.
L'ajustement décimal s'effectue en ajoutant une constante bien précise à l'opérande à convertir en BCD. L'idée est que la constante est découpée en morceaux de 4 bits, correspondant chacun à un chiffre BCD de l'opérande, chaque morceau contenant soit un 0, soit 6. Cela permet d'ajouter soit 0, soit 6, à chaque chiffre BCD, et donc de le corriger. La propagation des retenues d'un chiffre à l'autre est effectuée automatiquement par l'addition binaire de la constante. L'opération d'ajustement décimal calcule automatiquement la constante. Elle découpe l'opérande en ''nibbles'', vérifie si chaque ''nibble'' est supérieur ou égal à 10, puis détermine la valeur de chaque ''nibble'' de la constante finale. Par exemple, si je prends l'opérande 1001 1110, le ''nibble'' de poids faible déborde, alors que celui de poids fort non. La constante sera donc 0000 0110 : 0x06. Inversement, si le ''nibble'' de poids fort déborde et pas celui de poids faible, la constante sera alors 0x60. Et la constante est de 0x66 si les deux ''nibbles'' débordent, de 0x00 si aucun ne déborde.
Le circuit d’ajustement décimal est donc composé de trois étapes : deux étapes pour calculer la constante, et un circuit d'addition pour additionner cette constante au nombre de départ. La première étape découpe l'opérande en morceaux de 4 bits, en chiffres BCD, et vérifie si chacun d'entre eux vaut 10 ou plus. La seconde étape prend les résultats de la première étape, et les combine pour calculer la constante. Enfin, on trouve l'addition finale, qui était réalisée par un circuit d'addition utilisé à la fois pour l'ajustement décimal et l'addition binaire. La différence entre une addition normale et une opération d'ajustement décimal tient dans le fait que les deux premières étapes sont désactivées dans une addition normale.
[[File:Additionneur BCD parallèle.png|centre|vignette|upright=1.5|Additionneur BCD parallèle]]
===L'additionneur biquinaire===
Les entiers BCD ne sont qu'un des encodages hybrides entre décimal et binaire. L'encodage biquinaire est l'un d'entre eux et nous allons faire un rappel rapide à ce sujet. Pour simplifier, un chiffre encodé en biquinaire est composé de deux parties : un bit, couplé à une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits (0, 1, 2, 3 et 4). Le bit indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9.
Additionner deux nombres de biquinaire demande donc d'additionner deux parties quinaires encodées en ''one-hot'' et d'additionner deux bits. Mais attention : il faut tenir compte de la retenue de l'addition des parties quinaires. Et idéalement, il faut aussi tenir compte d'une retenue entrante, provenant de l'addition de la colonne de chiffres précédente. Toute la difficulté vient de la création de l'additionneur ''one-hot''. Heureusement, vu qu'il n'y a que 4-5 bits à additionner, il est souvent fabriqué à partir de sa table de vérité.
[[File:Additionneur bi-quinaire.png|centre|vignette|upright=2|Additionneur bi-quinaire]]
Un avantage du biquinaire est que le calcul du complément à 9 est très simple. Il faut pour cela : inverser la partie binaire avec une porte NON, puis inverser l'ordre des bits de la partie quinaire. Concrètement, le bit de poids faible devient le bit de poids fort, et ainsi de suite. Par exemple, une partie quinaire 01000 devient 00010, 10000 devient 00001, 00100 ne change pas, etc. Le tout peut se calculer avec une porte NON et 5 multiplexeurs.
===L'additionneur BCD avec calculs intermédiaires en biquinaire===
L'ordinateur IBM 1401, un ancien ''mainframe'' des années 60, utilisait un additionneur BCD un peu particulier. Les nombres étaient encodés en BCD dans la mémoire de l'ordinateur, mais les circuits de calcul utilisaient la représentation biquinaire. Lors d'un calcul, le processeur de l'ordinateur traduisait les chiffres BCD en représentation biquinaire, faisait une addition en biquinaire, avant de traduire le résultat en BCD normal.
Pour être précis, l'IBM 1401 utilisait une variante du biquinaire. L'encodage biquinaire de l'IBM 1401 est le suivant : la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre pair encodé par la partie quinaire. Si l'IBM 1401 utilisait cette variante du biquinaire, c'est car elle donnait des circuits de conversion BCD-biquinaire plus économes en portes logiques et plus rapides.
La partie binaire est le bit de poids faible du chiffre BCD, la partie biquinaire est calculée par un simple décodeur qui prend en entrée le chiffre BCD, amputé de son bit de poids faible. La traduction inverse demande d'utiliser un encodeur, à la place du décodeur. Par contre, le circuit d'addition biquinaire était plus compliqué du fait de la gestion des retenues. L'addition des parties binaires et quinaires se faisait en parallèle, dans deux additionneurs séparés. Cependant, l'addition des parties binaire fournit une retenue, qu'il faut prendre en compte. Pour cela, l'IBM 1401 disposait d'un troisième additionneur qui fournissait le résultat final, encodé en biquinaire.
[[File:Additionneur bi-quinaire de l'IBM 1401.png|centre|vignette|upright=2|Additionneur biquinaire de l'IBM 1401]]
Une implémentation moderne demanderait d'utiliser des portes ET combinées à des portes OU, le circuit pouvant être construit simplement à partir de sa table de vérité. Sur l'IBM 1401, le circuit était cependant différent, en raison de l'utilisation de OU câblés, des croisements de fils qui fonctionnent comme des portes OU, que nous n'avons pas encore vu pour le moment, mais qui seront détaillés dans quelques chapitres. Les OU câblés étaient utilisés pour simplifier le design du circuit, mais demandaient des portes logiques spécifiques, ce qui collait avec le fait que ce ''mainframe'' utilisait des transistors en Germanium. L'implémentation exacte est décrite dans cet article de blog, mais je ne recommande sa lecture qu'à ceux qui savent ce qu'est un OU câblé :
* [https://www.righto.com/2015/10/qui-binary-arithmetic-how-1960s-ibm.html Qui-binary arithmetic: how a 1960s IBM mainframe does math].
==Les débordements d'entier lors d'une addition/soustraction==
Les instructions arithmétiques manipulent des entiers codés sur un nombre fixe de bits, qui ne peuvent prendre leurs valeurs que dans un intervalle. Pour les nombres positifs, un ordinateur qui code ses entiers sur n bits pourra coder tous les entiers allant de 0 à <math>2^n -1</math>. Pour les nombres négatifs, l'intervalle est différent et dépend de la représentation utilisée. Dans le cas général, l'ordinateur peut coder les valeurs comprises de <math>N_\text{min}</math> à <math>N_\text{max}</math>. Si le résultat d'un calcul sort de cet intervalle, il ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math> , alors que la valeur basse vaut <math>N_\text{min} - 1</math> (avec <math>N_\text{max}</math> et <math>N_\text{min}</math> respectivement la plus grande et la plus petite valeur codable par l'ordinateur).
===La correction des débordements d'entier : l'arithmétique saturée===
Quand un débordement d'entier survient, tous les circuits de calcul ne procèdent pas de la même manière. Dans les grandes lignes, il y a deux réactions possibles : soit on corrige automatiquement le résultat du débordement, soit on ne fait rien et on se contente de détecter le débordement.
Si le débordement n'est pas corrigé automatiquement, le circuit ne conserve que les bits de poids faibles du résultat. Les bits en trop sont simplement ignorés. On dit qu'on utilise l''''arithmétique modulaire'''. Le problème avec ce genre d'arithmétique, c'est qu'une opération entre deux grands nombres peut donner un résultat très petit. Par exemple, si je dispose de registres 4 bits et que je souhaite faire l'addition 1111 + 0010 (ce qui donne 15 + 2), le résultat est censé être 10001 (17), ce qui est un résultat plus grand que la taille d'un registre. En conservant les 4 bits de poids faible, j’obtiens 0001 (1). En clair, un résultat très grand est transformé en un résultat très petit. Cela peut poser problèmes si on travaille uniquement avec des nombres positifs, mais c'est aussi utilisé pour coder des nombres en complément à deux.
D'autres circuits utilisent ce qu'on appelle l''''arithmétique saturée''' : si un calcul génère un débordement, on arrondi le résultat au plus grand entier supporté par le circuit. Les circuits capables de calculer en arithmétique saturée sont un peu plus complexes, vu qu'il faut rajouter des circuits pour corriger le résultat en cas de débordement. Il suffit généralement de rajouter un ''circuit de saturation'', qui prend en entrée le résultat et le corrige en cas de débordement. Ce circuit de saturation met la valeur maximale en sortie si un débordement survient, mais se contente de recopier le résultat du calcul sur sa sortie s'il n'y a pas de débordement. Typiquement, il est composé d'une couche de multiplexeurs, qui sélectionnent quelle valeur mettre sur la sortie : soit le résultat du calcul, soit le plus grand nombre entier géré par le processeur, soit le plus petit (pour les nombres négatifs/soustractions).
L'arithmétique saturée est utilisée pour les additions et soustractions, mais c'est plus rare pour les multiplications/divisions. Une des raisons est que le résultat d'une addition/soustraction prend un bit de plus que le résultat, là où les multiplications doublent le nombre de bits. Quand une addition déborde, le résultat réel est proche de la valeur maximale codable. mais quand une multiplication déborde, le résultat peut parfois valoir 200 à 60000 fois plus que la valeur maximale codable. Les calculs avec une valeur saturée/corrigée sont donc crédibles pour une suite d'additions, mais pas pour une suite de multiplications.
===La détection des débordements entiers===
Quand un débordement d'entier a eu lieu, il vaut mieux que l'additionneur prévienne ! Pour cela, l'additionneur a une '''sortie de débordement''', parfois nommée ''Overflow'', dont la valeur indique si l'addition a généré un débordement d'entier ou non. Reste que détecter un débordement ne se fait pas de la même manière selon que l'on parle d'un additionneur non-signé ou signé.
Pour les additionneur non-signés, l'additionneur calcule un bit de plus que ce qui est supporté par l'ordinateur. Par exemple, un additionneur 32 bits fournit un résultat sur 33 bits, un débordement d'entier a lieu quand le 33ème bit est à 1. Précisément, la sortie de débordement n'est autre que la retenue finale, celle fournie par le dernier additionneur complet. Le seul type de débordement possible est un débordement par le haut, où le résultat dépasse la valeur maximale. Avec l'arithmétique saturée, le circuit de saturation consiste en une seule couche de multiplexeurs, voire en un circuit de mise à la valeur maximale tel que vu dans le chapitre sur les opérations bits à bits.
[[File:Gestion des débordements d'entiers.png|centre|vignette|upright=2.5|Gestion des débordements d'entiers lors d'une addition non-signée.]]
Pour les additionneurs non-signés, la gestion des débordements d'entiers dépend fortement de la représentation signée. Nous allons étudier le cas du complément à deux. Si vous vous rappelez le chapitre 1, les calculs sur des nombres en complètement à deux utilisent les règles de l'arithmétique modulaire, c'est une condition nécessaire. À priori, on peut penser que dans ces conditions, les débordements d'entiers sont une chose parfaitement normale, qui nous permet d'avoir des résultats corrects. Néanmoins, certains débordements d'entiers peuvent survenir malgré tout et produire des bugs assez ennuyeux.
Si l'on tient en compte les règles du complément à deux, on sait que le bit de poids fort (le plus à gauche) permet de déterminer si le nombre est positif ou négatif : il indique le signe du nombre. Tout se passe comme si les entiers en complément à deux étaient codés sur un bit de moins, et avaient leur longueur amputé du bit de poids fort. Si le résultat d'un calcul écrase le bit de poids fort, il y a un débordement d'entiers. Il existe une règle simple qui permet de détecter ces débordements d'entiers. L'addition de deux nombres positifs ne peut pas être un nombre négatif. Si on additionne deux nombres dont le bit de signe est à 0 et que le bit de signe du résultat est à 1, on est en face d'un débordement d'entiers. Même chose pour deux nombres négatifs : le résultat de l'addition ne peut pas être positif. On peut résumer cela en une phrase : si deux nombres de même signe sont ajoutés, un débordement a lieu quand le bit du signe du résultat a le signe opposé.
Modifier les circuits d'au-dessus pour qu'ils détectent les débordements en complément à deux est simple comme bonjour : il suffit créer un petit circuit combinatoire qui prenne en entrée les bits de signe des opérandes et du résultat, et qui fasse le calcul de l'indicateur de débordements. Si l'on rédige sa table de vérité, on doit se retrouver avec la table suivante :
{|class="wikitable"
|-
!Entrées
!Sortie
|-
|000||0
|-
|001||1
|-
|010||0
|-
|011||0
|-
|100||0
|-
|101||0
|-
|110||1
|-
|111||0
|}
L'équation de ce circuit est la suivante, avec <math>S_a</math> et <math>S_b</math> les signes des deux opérandes, et <math>C_i</math> la retenue de la colonne précédente :
: <math> ( S_a . S_b . \overline{C_i} ) + ( \overline{S_a} . \overline{S_b} . C_i )</math>
En simplifiant, on obtient alors :
: <math>( S_a . S_b ) \oplus C_i</math>
Or, il se trouve que <math>S_a . S_b</math> est tout simplement la retenue en sortie du dernier additionneur, que nous noterons <math>C_o</math>. On trouve donc :
: <math>C_o \oplus C_i</math>
Il suffit donc de faire un XOR entre la dernière retenue et la précédente pour obtenir le bit de débordement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de décalage et de rotation
| prevText=Les circuits de décalage et de rotation
| next=Les circuits de comparaison
| nextText=Les circuits de comparaison
}}
</noinclude>
36rfsap776g3tx2no4ku5xvlhccxbij
768670
768669
2026-06-25T20:04:55Z
Mewtow
31375
/* L'additionneur à saut de retenue */
768670
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir les circuits capables de faire une addition ou une soustraction, ainsi que quelques circuits spécialisés. Précisons cependant que les fabricants de processeurs travaillent d'arrache-pied pour trouver des moyens de rendre ces circuits de calcul plus rapides et plus économes en énergie. Autant vous dire que les circuits que vous allez voir sont vraiment des circuits qui font pâle figure comparé à ce que l'on peut trouver dans un vrai processeur commercial !
==Les circuits pour additionner 2 ou 3 bits==
L'addition se fait en binaire de la même manière qu'en décimal. On additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. La soustraction fonctionne sur le même principe, sur le même modèle qu'en décimal.
[[File:Binary Addition Demonstration.svg|centre|vignette|Exemple d'addition en binaire.]]
En clair, additionner deux nombres demande d'additionner 2 bits et une retenue sur chaque colonne, et de propager les retenues d'une colonne à l'autre. La propagation des retenues est quelque chose de simple en apparence, mais qui est sujet à des optimisations extraordinairement nombreuses. Aussi, pour simplifier l'exposition, nous allons voir comment gérer une colonne avant de voir comment sont propagées les retenues.
[[File:Full Adder Block.svg|vignette|upright=1|class=transparent|Additionneur complet.]]
Si on effectue une addition en colonne, on doit additionner les deux bits sur la colonne, mais aussi additionner une éventuelle retenue. Il faut donc créer un circuit qui additionne trois bits : deux bits de données, plus une retenue. Ce circuit qui additionne trois bits est appelé un '''additionneur complet'''. Il fournit en sortie deux bits : un bit de somme et une retenue sortante. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|-
||0||0||0|| ||0||0
|-
||0||0||1|| ||0||1
|-
||0||1||0|| ||0||1
|-
||0||1||1|| ||1||0
|-
||1||0||0|| ||0||1
|-
||1||0||1|| ||1||0
|-
||1||1||0|| ||1||0
|-
||1||1||1|| ||1||1
|}
Il est possible d'utiliser un tableau de Karnaugh pour traduire la table de vérité, mais elle donne un résultat assez compliqué. La retenue et le bit de somme sont calculés à part. La retenue est calculée avec trois portes ET et une porte OU. Le bit de somme est calculé lui avec 4 portes ET, une porte OU, et trois portes NON.
[[File:Full Adler with 3 entries.png|centre|vignette|upright=2|Additionneur complet, fabriqué à partir de la table de vérité.]]
D'autres méthodes donnent des résultats plus compréhensibles. Nous allons les voir dans la suite de cette section.
===L'additionneur complet basé sur des demi-additionneurs===
[[File:1-bit half-adder.svg|class=transparent|vignette|Demi-addtionneur.]]
Nous avons déjà vu comment additionner deux bits dans le chapitre sur les incrémenteurs, mais quelques rappels ne seront pas de trop. Le '''demi-additionneur''' est un circuit qui additionne deux bits. Il implémente la table d'addition, qui est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Mais un demi-additionneur n'additionne que deux bits, il manque de quoi additionner la retenue. Une solution pour cela est d'enchaîner deux demi-additionneurs : un qui additionne les deux bits de données, et un second qui additionne la retenue au résultat du précédent. La retenue finale se calcule en combinant les sorties de retenue des deux demi-additionneurs, avec une porte OU. Le résultat est un additionneur complet fabriqué avec deux demi-additionneurs. Pour vous en convaincre, établissez la table de vérité de ce circuit, vous verrez que ça marche.
[[File:Full Adder Blocks.svg|centre|vignette|upright=2|Additionneur complet fabriqué avec deux demi-additionneurs.]]
[[File:Half-adder.svg|vignette|upright=1|Circuit d'un demi-additionneur naïf.]]
L'implémentation naïve d'un demi-additionneur utilise une porte XOR et une porte ET, comme illustré ci-contre. Le circuit s'obtient en faisant uen traduction littérale de la table de vérité du circuit. Si je dis qu'elle est naïve, c'est car il est possible de l'optimiser, de manière à éliminer des redondances cachées. Mais nous avions déjà vu cela dans le chapitre sur les incrémenteurs, aussi laissons cela pour plus tard.
En combinant deux demi-additionneurs de ce type, on obtient l'additionneur complet suivant. Les deux sorties de retenue des demi-additionneurs sont combinées avec une porte OU, pour calculer la retenue finale.
{| class="flexible"
|[[File:Full Adder Modules.svg|class=transparent|Composition d'un additionneur complet. On voit bien que celui-ci est composé de deux demi-additionneurs, en rouge et en bleu, auxquels on a ajouté une porte OU pour calculer la retenue finale.]]
|[[File:Full-adder.svg|300px|class=transparent|Circuit d'un additionneur complet.]]
|}
Le fait de combiner les deux retenues avec une porte OU n'est pas ce qu'il y a de plus intuitif. La table de vérité nous dit que ça fonctionne, mais on comprend mal pourquoi. Mais tout devient plus clair quand on sait que les deux retenues ne peuvent pas être à 1 en même temps. Même dans l'addition 1 + 1 + 1, seule une retenue est à 1. Soit l'addition des deux bits d'opérande donne naissance à une retenue, soit c'est l'addition ''résultat + retenue entrante''. La retenue sortant est donc à 1 quand une des deux addition donne une retenue. D'où l'usage d'une porte OU. Remarquez qu'une porte XOR donne le même résultat, vu que le cas où les deux retenues sont à 1 n'est jamais rencontré.
===La propagation et la génération des retenues===
L'additionneur complet que nous allons voir dans cette section sert à introduire les concepts de propagation, génération et absorption de retenue. Il s'agit de concepts très importants quand on étudie les additionneurs. l'idée part d'un principe très simple : la retenue sortante dépend de la retenue d'entrée. La relation entre les deux se résume à trois cas, qui dépendent de la valeur des deux bits additionnés, nommés A et B.
* Dans le premier cas, la retenue entrante est égale à la retenue sortante. On dit que la retenue entrante est propagée par l'additionneur.
* Dans le second cas, la retenue est forcée à 0 : la retenue sortante vaut 0, peu importe la valeur de la retenue entrante.
* Dans le troisième cas, la retenue est forcée à 1 : la retenue entrante vaut 1, peu importe la valeur de la retenue entrante.
[[File:Signaux P et G fournis par un demi-additionneur.png|vignette|Signaux P et G fournis par un demi-additionneur]]
Maintenant, créons un circuit qui nous dise si une retenue est propagée ou générée. Il a deux bits de sortie, nommés P et G : P pour ''Propagate'', G pour ''Generate''. Le bit P est à 1 si la retenue entrante est propagée, il est à 0 sinon. Le bit G est à 1 si une retenue est générée, à 0 sinon. Une retenue est considérée comme absorbée si elle n'est pas ni propagée ni générée, pas besoin d'un troisième bit pour gérer ce cas.
Il se trouve que ce circuit n'est autre qu'un demi-additionneur ! Pour vous en rendre compte, regardez la table de vérité d'un additionneur complet, illustrée ci-dessous. Lorsque les deux bits d'opérande sont à 0, la retenue sortante vaut toujours 0. Si ils sont tous deux à 1, alors la retenue sortante vaut 1. S'ils sont différents, alors retenues sortante et entrante sont égales. Le bit P est donc généré par une simple porte XOR. Quant au bit G, il est à 1 si les deux bits d'opérandes sont à 1, ce qui correspond à une porte ET. Il se trouve que ces deux portes forment un demi-additionneur !
{|class="wikitable"
|-
! Opérande 1 !! Opérande 2 !! Retenue entrante !! !! Retenue sortante
|- class="f_rouge"
||0||0||0|| ||0
|- class="f_rouge"
||0||0||1|| ||0
|- class="f_bleu"
||0||1||0|| ||0
|- class="f_bleu"
||0||1||1|| ||1
|- class="f_bleu"
||1||0||0|| ||0
|- class="f_bleu"
||1||0||1|| ||1
|- class="f_vert"
||1||1||0|| ||1
|- class="f_vert"
||1||1||1|| ||1
|}
Ensuite, créons un circuit qui prend ces deux sorties P et G, et calcule la retenue sortante en fonction. Le circuit en question a trois entrées : la retenue entrante, les deux bits P et G. La retenue sortante vaut 1 soit si une retenue est générée, soit si la retenue entrante est propagée et qu'elle vaut 1. En notant <math>{C_{in}}</math> et <math>{C_{out}}</math> les retenues entrantes et sortante, on a :
: <math>{C_{out}} = G + \left( P . C_{in} \right)</math>
Le circuit est donc composé d'une porte OU et d'une porte ET. En combinant un demi-additionneur avec le circuit de calcul de retenue sortante vu plus haut, on a :
[[File:Additionneur complet avec propagation et génération de retenue.png|class=transparent|centre|vignette|upright=2|Additionneur complet avec propagation et génération de retenue.]]
Certes, on retombe sur le circuit vu plus haut. Mais il est possible de remplacer le circuit en orange par un autre. Par exemple, il est possible de le remplacer par multiplexeur, qui choisit entre la retenue générée et la retenue entrante (propagée). Le choix se fait selon la valeur du bit P, qui chosiit entre propager la retenue et la générer.
[[File:Additionneur crée avec un multiplexeur.png|centre|vignette|upright=1.5|Additionneur crée avec un multiplexeur]]
Le circuit exact est illustré ci-dessous. Il semble utiliser plus de portes logiques que l'additionneur complet précédent. Cependant, nous verrons dans quelques chapitres qu'il est possible d'implémenter un multiplexeur avec seulement 6 transistors, voire moins ! L'implémentation utilise des portes à transmission, mais nous en reparlerons dans le chapitre sur les transistors, quand nous verrons les additionneurs à ''Manchester Carry Chain''. Au passage, une variante de ce circuit a été utilisée dans le processeur processeur 8086 d'Intel, comme on le verra dans le chapitre suivant.
[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
[[File:FulladderPG.png|vignette|upright=1|Additionneur complet avec deux sorties P et G.]]
Pour finir, sachez qu'il existe des additionneurs qui fournissent : le bit de résultat, deux sorties P et G qui indiquent si l'addition propage ou génère une retenue. Il ne s'agit pas d'additionneurs complets, car il manque de quoi calculer la retenue sortante. De tels additionneurs seront appelés des '''additionneurs partiels'''. De tels additionneurs sont utilisés dans certains additionneurs pour gagner en performance.
Enfin, il existe aussi ce que j'ai décidé d'appeler des '''additionneurs P/G''', qui sont des additionneurs complets auxquels on a ajouté deux sorties P et G, en plus de la sortie de retenue. Leur circuit est le même que celui d'un additionneur complet, auquel on a ajouté deux fils. Vu que ces deux sorties sont fournies par le premier demi-additionneur, ajouter ces deux sorties demande d'ajouter des fils, pas de portes logiques. De tels additionneurs sont aussi utilisés dans certains additionneurs pour gagner en performance.
===L'additionneur complet basé sur une porte à majorité===
Maintenant, voyons une dernière implémentation possible de l'additionneur complet. Mais avant de voir comment l'additionneur est implémenté, nous allons devoir faire un petit focus sur la retenue sortante. Reprenons la table de vérité, et regardons ce qu'il en est pour la retenue sortante uniquement.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante
|-
||0||0||0|| ||0
|-
||0||0||1|| ||0
|-
||0||1||0|| ||0
|-
||0||1||1|| ||1
|-
||1||0||0|| ||0
|-
||1||0||1|| ||1
|-
||1||1||0|| ||1
|-
||1||1||1|| ||1
|}
Vous remarquerez que la retenue sortante est égale au bit majoritaire, parmi les trois bits d'opérande. Si deux ou trois bits sont à 1, la retenue sortante vaut 1. Et réciproquement avec 0. Le circuit de calcul de la retenue peut donc être remplacé par une '''porte à majorité'''.
[[File:Additionneur crée avec une porte à majorité.png|centre|vignette|upright=2|Additionneur crée avec une porte à majorité]]
Mais une porte à majorité est assez gourmande en circuit, ce qui fait qu'un additionneur pareil ne serait pas pratique, ni utile. Du moins, ce serait le cas s'il n'y avait pas une possibilité d'optimisation extrêmement intéressante. Il est possible de calculer le 'bit de somme' à partir de la retenue sortante ! En effet, le bit de somme est l'inverse de la retenue sortante, sauf dans deux cas : les trois bits d'entrée sont à 0, où ils sont tous à 1. Le bit de somme vaut 0 dans le premier cas, 1 dans le second cas.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|- class="f_bleu"
||0||0||0|| ||0||0
|- class="f_vert"
||0||0||1|| ||0||1
|- class="f_vert"
||0||1||0|| ||0||1
|- class="f_vert"
||0||1||1|| ||1||0
|- class="f_vert"
||1||0||0|| ||0||1
|- class="f_vert"
||1||0||1|| ||1||0
|- class="f_vert"
||1||1||0|| ||1||0
|- class="f_rouge"
||1||1||1|| ||1||1
|}
En clair, 6 lignes de la table de vérité sur 8 peuvent se calculer avec une porte à majorité, le reste demandant quelques portes logiques pour faire la correction. L'implémentation en circuit demande donc trois choses :
* de calculer la retenue sortante et de quoi l'inverser ;
* un circuit qui vérifie si tous les bits opérande valent 0 : une porte NOR ou OU fait l'affaire ;
* un circuit qui vérifie s'ils valent tous 1, à savoir une porte ET.
Ensuite, on combine le résultat des trois circuits précédents pour obtenir le résultat final. La combinaison est le fait du circuit en jaune dans le schéma ci-dessous.
[[File:Full adder basé sur une modification de la retenue.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue]]
Le circuit en jaune est assez simple. Quand la porte ET sort un 1, la sortie doit être forcée à 1. Par contre, quand la porte OU sort un zéro, la sortie doit être forcée à 0. Il suffit donc d'enchainer un circuit de mise à 1 et un circuit de mise à 0. Nous avons vu dans le chapitre sur les opérations de masquage qu'il s'agit respectivement d'une porte OU et d'une porte ET.
[[File:Full adder basé sur une modification de la retenue interne, au complet.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue interne, au complet]]
Divers processeurs utilisaient des additionneurs complets de ce type : le fameux Z80, l'Intel 4004 et de l'Intel 8008 sont les exemples les plus notables. Pourtant, on pourrait s'étonner qu'un tel circuit ait existé. Il utilise beaucoup de portes logiques, a une profondeur logique supérieure : il n'a rien d'avantageux. Sauf qu'il était utilisé sur d'anciens processeurs, qui utilisaient la technologie dite TTL, différente de la technologie CMOS des transistors modernes. Et avec la technologie TTL, il est possible de fusionner plusieurs portes logiques ET et NOR en une seule porte logique ET/OU/NON ! Un additionneur complet construit ainsi ne prenait que deux portes logiques : une pour le calcul de la retenue sortante, une autre pour le reste du circuit.
==L'addition non signée==
Voyons maintenant un circuit capable d'additionner deux nombres entiers: l''''additionneur'''. Dans la version qu'on va voir, ce circuit manipulera des nombres strictement positifs. L'addition des nombres codés en complètement à deux sera vu dans une section ultérieure.
===L'additionneur série===
Il est possible d'additionner deux nombres bit par bit, grâce à un additionneur complet associé à plusieurs registres à décalages. Les opérandes sont placées chacune dans un registre à décalage, et l'additionneur complet utilise les bits sortants de ces registres à décalage. Le bit du résultat est envoyé au registre à décalage pour le résultat. La retenue de l'addition est stockée dans une bascule de 1 bit, en attente du prochain cycle d'horloge. Un tel additionneur est appelé un '''additionneur série'''. Il a été utilisé sur d'anciens ordinateurs dans les années 50-60, aussi bien des prototypes que des ordinateurs commerciaux.
[[File:Additionneur série.jpg|centre|vignette|upright=2.5|Additionneur série.]]
===L'additionneur à propagation de retenue===
L''''additionneur à propagation de retenue''' est aussi appelé l'additionneur ''ripple carry'', c'est son nom anglais. J'utiliserais occasionnellement le nom anglais dans ce qui suit, car celui-ci est plus court. Ilutilise un additionneur complet pour chaque colonne de l'addition. Pour gérer les retenues, il suffit ainsi de câbler des additionneurs complets les uns à la suite des autres, chacun envoyant sa retenue à la colonne suivante.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Notons la présence de la retenue sortante, qui est utilisée pour détecter les débordements d'entier, ainsi que pour d'autres opérations. Le bit de retenue final est souvent stocké dans un registre spécial du processeur (généralement appelé ''carry flag'').
Notez aussi, à droite du schéma précédent, l'entrée de retenue <math>\text{R}_\text{Entrée}</math>. Elle est très utile pour l'implémentation de certaines opérations comme l'inversion de signe, la soustraction, l'incrémentation, etc. Certains processeurs sont capables de faire une opération appelée ADC, ADDC ou autre nom signifiant ''Addition with Carry'', qui permet de faire le calcul A + B + Retenue (la retenue en question est la retenue sortante de l'addition précédente, stockée dans le registre ''carry flag''). Son utilité principale est de permettre des additions d'entiers plus grands que ceux supportés par le processeur. Par exemple, cela permet de faire des additions d'entiers 32 bits sur un processeur 16 bits.
[[File:Adder Network 3 Bit Sum Delay 003.svg|vignette|upright=0.5|Propagation de retenue dans l'additionneur.]]
Cet additionneur utilise très peu de portes logiques, ce qui fait qu'il était utilisé sur certains processeurs 8 et 16 bits, dont le budget en portes logiques était limité. Bien que très simple, cet additionneur est cependant peu performant. Son temps de calcul est proportionnel à la taille des opérandes. Par exemple, additionner deux opérandes de 32 bits prendra deux fois plus de temps que pour des opérandes de 16 bits. La raison est que les retenues doivent se propager à travers le circuit, du premier additionneur jusqu'au dernier.
L'addition étant une opération fréquente, d'autres additionneurs ont été inventés. Ils utilisent des optimisations qui utilisent plus de circuits pour gagner en rapidité. Mais avant de voir ces optimisations, nous allons voir s'il est possible d'optimiser les additionneurs à propagation de retenue.
===L'additionneur à saut de retenue===
L'additionneur à propagation de retenue peut être optimisé de deux manières. La première accélère la propagation de la retenue, en agissant au niveau des additionneurs complets. La ''Manchester carry chain'' est une optimisation de ce type, mais on ne peut pas encore expliquer à ce stade du cours, car elle optimisait les additionneurs complets au niveau des transistors eux-mêmes. Elle a été utilisée sur de nombreux processeurs connus, comme le 8086 d'Intel ou le Z80.
La seconde manière rend l'addition plus rapide dans certains cas. Le résultat est un additionneur dont le temps de calcul est variable. Le calcul prendra quelques cycles d'horloges avec certains opérandes, mais d'autres opérandes ne verront aucune amélioration. Il n'améliore pas le pire des cas, dans lequel la retenue doit être propagée du début à la fin, du bit de poids faible au bit de poids fort. Mais dans d'autres cas, il permet d'avoir le résultat en avance. L'optimisation en question donne l''''additionneur à saut de retenue''' (''carry-skip adder'').
Il est composé en enchainant plusieurs additionneurs plus petits, qui additionnent 4/5 bits, rarement plus. De tels additionneurs de petite taille sont appelés des '''blocs''', dans ce qui suit. Nous allons prendre un exemple où les additionneurs font 4 bits. L'idée est de détecter précocement si la retenue entrante est propagée à travers tout le bloc. Si la retenue est propagée dans tous le bloc, on peut directement l'envoyer sur la sortie de retenue, la retenue saute le bloc entier. Dans le cas contraire, on doit calculer la retenue normalement. Le choix entre les deux est le fait d'un multiplexeur. Toute la difficulté est de savoir comment commander le multiplexeur.
[[File:Carry skip adder, principe de base.png|centre|vignette|upright=2.5|Carry skip adder : principe de base]]
Pour savoir si une retenue est propagée dans un bloc entier, il faut utiliser des additionneurs complets, qui ont en plus une sortie P indiquant que l'additionneur propage la retenue. Le bloc propage une retenue si chaque additionneur complet propage la retenue, donc s'ils ont tous leur sortie P à 1. Un simple ET logique suffit à déterminer si c'est le cas. Le signal de commande du multiplexeur est donc un vulgaire ET entre toutes les sorties P des additionneurs complets du bloc.
[[File:Additionneur à saut de retenue.png|centre|vignette|upright=2.5|Calcul de la commande du MUX.]]
Voici ce que ça donne pour un bloc de 4 bits :
[[File:CSAdder4Bit.svg|centre|vignette|upright=1.5|Additionneur ''carry skip'' : bloc de 4 bits.]]
L'additionneur à saut de retenue est construit en assemblant plusieurs blocs de ce type.
[[File:BCSAdder16Bit.svg|centre|vignette|upright=3|Additionneur à saut de retenue.]]
Les blocs sont tous identiques dans le cas le plus simple, mais il est possible d'utiliser des blocs de taille variable. Par exemple, le premier bloc peut avoir des opérandes de 6 bits, le second des opérandes de 7 bits, etc. Faire ainsi permet de gagner un petit peu en performances, si la taille de chaque bloc est bien choisie. La raison est une question de temps de propagation des retenues. La retenue met plus de temps à se propager à travers 8 blocs qu'à travers 4, ce qui prend plus de temps qu'à travers 2 blocs, etc. En tenir compte fait que la taille des blocs tend à augmenter ou diminuer quand on se rapproche des bits de poids fort.
===L'additionneur à sélection de retenue===
L''''additionneur à sélection de retenue''' utilise aussi des blocs, comme les additionneurs précédents. L'addition se fait en deux versions : une avec la retenue du bloc précédent valant zéro, et une autre version avec la retenue du bloc précédent valant 1. Il suffira alors de choisir le bon résultat avec un multiplexeur, une fois cette retenue connue. On gagne ainsi du temps en calculant à l'avance les valeurs de certains bits du résultat, sans connaître la valeur de la retenue. Petit détail : sur certains additionneurs à sélection de retenue, les blocs de base n'ont pas la même taille. Cela permet de tenir compte des temps de propagation des retenues entre les blocs.
[[File:Additionneur à sélection de retenue.png|centre|vignette|upright=1.5|Additionneur à sélection de retenue avec seulement deux blocs.]]
Dans les exemples du dessus, chaque sous-additionneur étaient des additionneurs à propagation de retenue. Mais ce n'est pas une obligation, et tout autre type d’additionneur peut être utilisé. Par exemple, on peut faire en sorte que les sous-additionneurs soient eux-mêmes des additionneurs à sélection de retenue, et poursuivre ainsi de suite, récursivement. On obtient alors un '''additionneur à somme conditionnelle''', plus rapide que l'additionneur à sélection de retenue, mais qui utilise beaucoup plus de portes logiques.
===Les additionneurs à anticipation de retenue===
Les '''additionneurs à anticipation de retenue''' calculent chaque retenue sans avoir à propager les retenues précédentes. Au lieu de calculer les retenues une par une, ils calculent toutes les retenues en parallèle, à partir des bits des opérandes. Une fois les retenues pré-calculées, il suffit de les additionner avec les deux bits adéquats, pour obtenir le résultat. Ces additionneurs sont composés de deux parties :
* un circuit qui pré-calcule les retenues, sans les propager, directement à partir des opérandes ;
* d'une couche d'additionneurs complets, qui additionnent chacun deux bits d'opérande et la retenue pré-calculée.
[[File:Additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Additionneur à anticipation de retenue.]]
Le calcul des retenues se fait en deux étapes. La première détermine si les retenues sont propagées ou générées, pour chaque colonne. Elle détermine, pour chaque colonne, les bits P et G qui indiquent respectivement : qu'une retenue a été générée sur cette colonne, que la colonne propage la retenue précédente. La seconde étape utilise ces signaux P et G pour déterminer la retenue sur chaque colonne. Le circuit de calcul des retenues est donc composé de deux parties : une couche de demi-additionneurs pour générer les signaux P et G, un circuit de calcul des retenues proprement dit.
[[File:Circuit complet d'un additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Circuit complet d'un additionneur à anticipation de retenue.]]
Pour rappel, la retenue de la colonne i, notée <math>C_i</math> est égale à :
: <math>G_i + (P_i . C_{i-1})</math>, avec G le signal de génération de retenue, P le signal de propagation de retenue.
Si on utilisait cette formule sans trop réfléchir, on retomberait sur un additionneur à propagation de retenue inutilement compliqué. L'astuce des additionneurs à anticipation de retenue consiste à remplacer le terme Ci−1 par sa valeur calculée avant. Par exemple, je prends un additionneur 4 bits. Je dispose de deux nombres A et B, contenant chacun 4 bits : A3, A2, A1, et A0 pour le nombre A, et B3, B2, B1, et B0 pour le nombre B. Si j'effectue les remplacements, j'obtiens les formules suivantes :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Ces formules nous permettent de déduire la valeur d'une retenue directement : il reste alors à créer un circuit qui implémente ces formules, et le tour est joué.
[[File:Four bit adder with carry lookahead.svg|centre|vignette|upright=1.5|Additionneur à anticipation de retenue de 4 bits.]]
Plus haut, j'ai dit que le circuit était composé de trois couches : une couche de demi-additionneur pour les signaux P et G ; un circuit d'anticipation de retenue, puis l'addition finale. Mais les deux bits d'opérandes sont déjà additionnés dans la couche de demi-additionneur, autant réutiliser le résultat pour l'additionner à la retenue. Pour éliminer toute redondance de ce type, il est possible d'utiliser des additionneurs partiels (des additionneurs où la sortie de retenue a été remplacée par deux sorties P et G). L'additionneur final est alors composé de plusieurs additionneurs complets de ce type, couplé à un circuit d'anticipation de retenue.
[[File:Cla4bits.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue de 4 bits.]]
Les additionneurs à anticipation de retenue sont plus rapides que les additionneurs à propagation de retenue. La raison est que les retenues sont calculées rapidement, dans un temps plus petit qu'en propageant la retenue. Pour rentrer dans les détails mathématiques, le temps de calcul n'est pas proportionnel au nombre de bits, mais proportionnel au logarithme de ce nombre de bits. Par contre, le gain en temps de calcul s'accompagne d'un cout en portes logiques conséquent. Le circuit qui calcule les retenues anticipées utilise beaucoup de portes logiques.
===Les additionneurs à calcul parallèle de préfixe===
Du fait du cout en portes logiques, utiliser un additionneur à anticipation de retenue est une solution pratique pour additionner 4 ou 8 bits. Mais pour des opérandes plus grands, de 16 ou 32 bits, cela utiliserait trop de portes logiques. Une partie de ce cout en portes logiques est cependant lié à des redondances. Si on analyse deux retenues différentes, à des colonnes différentes. Les circuits qui calculent ces deux retenues seront différents, mais ils contiendront des sous-circuits identiques. Pour vous en rendre compte, reprenons les quatre équations obtenues précédemment et essayez de trouver des redondances :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Vous devriez trouver :
* le terme ''P0 · C0'' dans toutes les lignes ;
* les termes ''P1 · P0 · C0'' et ''P1 · P0'' dans les trois dernières lignes ;
* le terme ''P2 · P1 · P0 · C0'' ; ''P2 · P1 · G0'' et ''P2 · G1'' dans les deux dernières lignes.
Il existe plusieurs manières d'exploiter de telles redondances, exploitées dans les '''additionneurs à calcul parallèle de préfixe'''. L'optimisation apportée est de générer des signaux ''propagate'' et ''generate'' pour des groupes de 2, 3, 4, ..., N colonnes. Par exemple, il est possible de générer un signal ''P 0 vers 3'', qui indique si la retenue de la première colonne est propagée jusqu'à la 4ème colonne. Un autre exemple est un signal qui indique qu'une retenue a été générée entre les colonnes 4 à 7 génèrent une retenue ou non. En clair, les signaux P et G ont maintenant un intervalle, qui précise de quelle colonne vers quelle colonne se fait la propagation, ou entre quelles colonnes se fait la génération.
Il est possible de combiner les signaux P et G de deux groupes de colonne, s'ils sont contiguës (ils peuvent aussi se recouvrir). Par exemple, il est possible de calculer les bits P et G pour les colonnes 0 à 10, à partir des deux signaux P/G des colonnes 0-4 et 5-10. Pour cela, les équations sont assez simples. Si je prends deux groupes nommés A et B, avec A pour les colonnes de poids fort et B celles de poids faible, on a :
: <math>P_{sortie} = P_A . P_B</math>
: <math>G_{sortie} = G_A . P_A . G_B</math>
Pour les bits des colonnes 2, 4, 8, 16, 32, 64, et autres, le calcul est simple. L'idée est de grouper les colonnes par groupes de 2. Puis, on calcule chaque groupe de 4 bits à partir de deux groupes de 2. Puis les groupes de 8 bits sont calculés à partir de deux groupes de 4 bits, et ainsi de suite pour toute puissance de deux.
[[File:Squelette d'un additionneur à calcul parallèle de préfixe.jpg|centre|vignette|upright=2|Squelette d'un additionneur à calcul parallèle de préfixe]]
Les seules difficultés surviennent pour les colonnes dont le nombre n'est pas une puissance de deux. Il y a plusieurs manières pour subdiviser ces colonnes en intervalles plus petits et combiner le tout. Et elles donnent chacune des additionneurs différents. Ils portent les noms d'''additionneur de Ladner-Fisher'', d'''additionneur de Brent-Kung'', d'''additionneur de Kogge-Stone'', etc. Dans le cas général, l'additionneur de Kogge-Stone est un des plus rapide, mais aussi un des plus gourmand en circuits, alors que c'est l'inverse pour l'additionneur de Brent-Kung. Les deux additionneurs sont illustrés ci-dessous, pour des opérandes de 8 bits. Dans ceux-ci, chaque losange calculent les bits P et G, à partir des signaux P et G de deux groupes précédents.
{|
|[[File:Brent-kung-8-bit.png|vignette|Brent-kung-8-bit]]
|[[File:Kogge-stone-8-bit.png|vignette|upright=1.2|Kogge-stone-8-bit]]
|}
===Le calcul parallèle de la retenue===
L'anticipation de retenue est très rapide, mais utilise beaucoup de circuits, même en utilisant le calcul parallèle de préfixe. Les additionneurs à propagation de retenue font eux le compromis inverse. Mais il existe des intermédiaires, qui visent à obtenir un compromis entre performance et cout en portes logiques.
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Un de ces compromis découpe un additionneur de 16/32/64 bits en additionneurs de 4/5 bits, qui sont enchainés en utilisant la propagation de retenue. Par contre, ces petits additionneurs de 4/5 bits utilisent l'anticipation de retenue. Ces additionneurs simples seront nommés ''blocs'' dans ce qui suit, et l'un d'entre eux est illustré ci-contre. Vous remarquerez qu'un bloc a une retenue entrante et une retenue sortante. Par exemple, on peut utiliser 4 additionneurs à anticipation de retenue de 4 bits, et propager les retenues entre eux. Le premier bloc calcule une retenue, qui est propagée au second bloc. Le second bloc calcule lui aussi une retenue, qui est propagée au troisième bloc, etc.
[[File:4008 Functional Diagram.svg|vignette|upright=1|4008 Functional Diagram]]
Le gain en performance est significatif, mais il est possible de faire presque aussi rapide, tout en économisant beaucoup de circuits. L'idée est de simplifier les additionneurs 4 bits, afin de ne pas utiliser une anticipation de retenue complète. L'anticipation de retenue n'est utilisée que pour la retenue sortante, alors que les bits de somme sont calculées avec propagation de retenue. La retenue sortante est donc calculée en parallèle de l'addition. Chaque bloc contient, à côté d'un additionneur proprement dit, un circuit qui calcule la retenue sortante. Il existe de nombreuses manières de calculer la retenue sortante.
Le gain en portes logiques est assez significatif. En effet, on se passe de circuits d'anticipation de retenue pour les 4 bits de somme, on ne garde que celui pour la retenue sortante. Le fait d'utiliser la propagation de retenue pour calculer les 4 bits de somme est censé avoir un léger cout en performance, mais il se trouve que ce cout est très faible. En effet, le résultat n'est connu qu'une fois les retenues propagées jusqu'au dernier bloc. Et malgré l'anticipation des retenues, la propagation est assez lente. les additionneurs à propagation de retenue auront terminé leur travail bien avant.
Un exemple est celui de l'additionneur CMOS 4008, un additionneur de 4 bit. Il est composé en trois sections. Une première couche de demi-additionneurs calcule les signaux P et G utilisés à la fois pour l'anticipation de la retenue sortante, que pour calculer les bits de somme. Le circuit de calcul de la retenue sortante utilise l'anticipation de retenue. Le reste du circuit fait l'addition en propageant les retenues.
[[File:CMOS 4008, circuit découpé en sections.png|centre|vignette|upright=1.5|CMOS 4008, circuit découpé en sections]]
===Les additionneurs à anticipation de retenue multi-niveau===
Les additionneurs précédents mixent anticipation et propagation de retenue. Ceux que nous allons voir utilisent des blocs, mais utilisent l'anticipation de retenue entre les blocs. Les blocs utilisés peuvent être un additionneur à propagation de retenue, ou à anticipation de retenue, peu importe. Ce qui compte est que la retenue entrante de chaque bloc est calculée par anticipation de retenue.
Par exemple, il est possible de créer un additionneur 16 bits en utilisant 4 additionneurs/blocs de 4 bits, couplé à un circuit d’anticipation de retenue par bloc. Pour cela, les additionneurs de 4 bits doivent fournir deux sorties P et G. Les deux sorties indiquent que le bloc soit a généré une retenue, soit a propagé la retenue entrante. Par propagé, on veut dire que la retenue entrante du bloc s'est retrouvée sur la sortie de retenue du bloc. C'est le cas si les 4 colonnes propagent la retenue, ce qui signifie que :
: <math>P_\text{4 bits} = P_0 \cdot P_1 \cdot P_2 \cdot P_3</math>
Pour la génération, cela veut dire qu'une colonne a générée une retenue, qui a été propagée jusqu'à la sortie. En clair, pour chaque colonne, il faut que le G = 1, et que les signaux P des colonnes soient à 1. Pour les 4 colonnes, cela donne :
: <math>G_\text{4 bits} = G_3 + G_2 \cdot P_3 + G_1 \cdot P_3 \cdot P_2 + G_0 \cdot P_3 \cdot P_2 \cdot P_1</math>
[[File:Cla4bitsPG.png|centre|vignette|upright=2|Additionneur à anticipation de retenue de 4 bits.]]
Pour combiner 4 blocs de 4 bits, on devra donc combiner 4 bits P et G, avec un circuit d'anticipation de retenue.
[[File:16-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 16 bits.]]
Il est même possible d'aller plus loin et de combiner 4 additionneurs précédents pour obtenir un additionneur 64 bits.
[[File:64-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 64 bits.]]
Le résultat est un additionneur à plusieurs niveaux d’anticipation de retenue. Une anticipation de retenue dans un blocs de 4 bits (facultative), entre blocs de 4 bits, entre blocs de 16 bits. La même logique peut être utilisée avec des blocs de taille différente de 4, 16 et 64 bits.
==L'addition signée et la soustraction==
Après avoir vu l'addition, il est logique de passer à la soustraction, les deux opérations étant très proches. Si on sait câbler une addition entre entiers positifs, câbler une soustraction n'est pas très compliqué. De plus, la soustraction permet de faire des additions de nombres signés.
===Le soustracteur pour opérandes entiers===
Pour soustraire deux nombres entiers, on peut adapter l'algorithme de soustraction utilisé en décimal, celui que vous avez appris à l'école. Celui-ci ressemble fortement à l'algorithme d'addition : on soustrait les bits de même poids, et on propage éventuellement une retenue sur la colonne suivante. À la différence de l'addition, la retenue est soustraite, et non ajoutée. La table de soustraction nous dit quel est le résultat de la soustraction de deux bits. La voici :
* 0 - 0 = 0 ;
* 0 - 1 = 1 et une retenue ;
* 1 - 0 = 1 ;
* 1 - 1 = 0.
[[File:Algebra1 05 fig014.svg|centre|vignette|Soustraction en binaire, avec les retenues en rouge.]]
La table de soustraction peut servir de table de vérité pour construire un circuit qui soustrait deux bits. Celui-ci est appelé un '''demi-soustracteur'''. Il ressemble beaucoup à un demi-additionneur, les différences se résumant à une porte NON ajoutée pour le calcul de la retenue.
[[File:Half Subtractor Vektor.svg|centre|vignette|Demi-soustracteur.]]
Comme pour l'additionneur, deux demi-soustracteurs peuvent être combinés pour donner un '''soustracteur complet'''. Le calcul de la retenue se fait en combinant les deux retenues des demi-soustracteurs avec une porte OU. Les soustracteurs complets sont utilisés pour créer des soustracteurs à propagation de retenue ou tout autre circuit soustracteur, sur le même modèle que les additionneurs.
[[File:FullSubtractor.svg|centre|vignette|upright=2|Soustracteur complet.]]
Vous l'aurez sans doute remarqué, mais le demi-soustracteur ressemble beaucoup au demi-additionneur. La seule différence est une porte NON ajoutée au bon endroit dans le demi-soustracteur.
{|
|[[File:Half-adder.svg|vignette|Demi-additionneur]]
|[[File:Half Subtractor Vektor.svg|vignette|upright=1.3|Demi-soustracteur]]
|}
La conséquence est qu'il est possible de créer un circuit capable de faire à la fois des additions et des soustractions. L'idée est de remplacer la porte NON du demi-soustracteur par un inverseur commandable. Si on envoie un zéro sur l'entrée de commande, l'inverseur commandable se comportera comme une porte OUI. Le circuit sera alors identique à un demi-additionneur. Si on envoie un 1 sur l'entrée de commande, l'inverseur commandable se comportera comme une porte NON. le circuit sera alors un demi-soustracteur. Il faut faire cela pour chaque demi-soustracteur, ce qui donne ceci pour un soustracteur complet. le résultat est alors appelé un '''additionneur-soustracteur complet'''.
[[File:Additionneur-soustracteur complet.png|centre|vignette|upright=2|Additionneur-soustracteur complet]]
===L'additionneur-soustracteur pour opérandes codées en complément à deux===
Étudions le cas de la soustraction en complément à deux, dans l'objectif de créer un circuit soustracteur. Vous savez sûrement que a−b et a+(−b) sont deux expressions équivalentes. Et en complément à deux, − b = not(b) + 1. Dit autrement, a − b = a + not(b) + 1. On pourrait se dire qu'il faut deux additionneurs pour faire le calcul, mais la majorité des additionneurs possède une entrée de retenue pour incrémenter le résultat de l'addition. Un soustracteur en complément à deux est donc simplement composé d'un additionneur et d'un inverseur.
[[File:Additionneur soustracteur.png|centre|vignette|upright=2|Soustracteur en complément à deux.]]
Il est possible de créer un circuit capable d'effectuer soit une addition, soit une soustraction : il suffit de remplacer l'inverseur par un inverseur commandable, qui peut être désactivé. On a vu comment créer un tel inverseur commandable dans le chapitre sur les circuits combinatoires. On peut remarquer que l'entrée de retenue et l'entrée de commande de l'inverseur sont activées en même temps : on peut fusionner les deux signaux en un seul.
[[File:Additionneur-soustracteur en complément à deux.png|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux.]]
Une implémentation alternative est la suivante. Elle remplace l'inverseur commandable par un multiplexeur.
[[File:4-bit ripple carry adder-subtracter.svg|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux, version alternative.]]
===L'additionneur-soustracteur pour opérandes codées en signe-magnitude===
Passons maintenant aux nombres codés en signe-valeur absolue, les deux opérandes étant notées A et B. Suivant les signes des deux opérandes, on a quatre cas possibles : A + B, A − B (B négatif), −A + B (A négatif) et −A − B (A et B négatifs). Une astuce est que le circuit n'a besoin que de calculer A + B et A − B : il peut les inverser pour obtenir − A − B ou B − A. A + B et A − B peuvent se calculer avec un additionneur-soustracteur, reste à corriger le résultat. Il suffit de lui ajouter un inverseur commandable pour obtenir le circuit d'addition finale.
[[File:Additionneur en signe-valeur absolue.png|centre|vignette|upright=2|Additionneur en signe-valeur absolue.]]
Toute la difficulté tient dans le calcul du bit de signe du résultat, quand interviennent des soustractions. Autant l'addition de deux nombres de même signe (A + B et −A − B) ne pose aucun problème, autant les soustractions posent problème (A − B et −A + B). Suivant que <math>A<B</math> ou que <math>A>B</math>, le signe du résultat ne sera pas le même. Déterminer le signe du résultat se fait en regardant les bits de débordement d'entier, comme on le verra plus bas.
===L'additionneur-soustracteur pour opérandes codées en représentation par excès===
Passons maintenant aux nombres codés en représentation par excès. On pourrait croire que ces nombres s'additionnent comme des nombres non-signés, mais ce serait oublier la présence du biais, qui pose problème. Dans les cas de nombres signés gérés avec un biais, voyons ce que donne l'addition de deux nombres :
: <math>( n_1 + biais ) + ( n_2 + biais ) = n_1 + n_2 + 2 \times biais</math>
Or, le résultat correct serait :
: <math>n_1 + n_2 + biais</math>
En effectuant l'addition telle quelle, le biais est compté deux fois. On doit donc le soustraire après l'addition pour obtenir le résultat correct.
Même chose pour la soustraction qui donne ceci :
: <math>(n_1 + biais) - (n_2 + biais) = n_1 - n_2</math>
Or, le résultat correct serait :
: <math> ( n_1 - n_2 ) + biais </math>
Il faut rajouter le biais pour obtenir l'exposant correct.
On a donc besoin de deux additionneurs/soustracteurs : un pour additionner/soustraire les représentations binaires des opérandes, et un autre pour ajouter/retirer le biais en trop/manquant.
==L'additionneur BCD==
Maintenant, voyons un additionneur qui additionne deux entiers au format BCD. Pour cela, nous allons devoir passer par deux étapes. La première est de créer un circuit capable d'additionneur deux chiffres BCD. Ensuite, nous allons voir comment enchaîner ces circuits pour créer un additionneur BCD complet.
===L'additionneur BCD qui fait l'opération chiffre par chiffre===
Nous allons commencer par voir un additionneur qui additionne deux chiffres en BCD, une sorte d'équivalent BCD de l'additionneur complet. Il fournit un résultat sur 4 bits et une retenue qui est mise à 1 si le résultat dépasse 10 (la limite d'un chiffre BCD). Les deux opérandes sont des chiffres BCD codés sur 4 bits et sont additionnés en binaire par un additionneur des plus normaux, similaire à ceux vus plus haut. Le résultat est alors un entier codé en binaire, sur 5 bits, qu'on corrige/convertit pour obtenir un chiffre BCD et une retenue sortante.
Pour corriger le résultat, une idée intuitive serait de prendre le résultat et de faire une division par 10. Le quotient donne la retenue, alors que le reste est le résultat, le chiffre BCD. Mais un circuit diviseur par 10 utilise beaucoup de portes logiques, ce qui ne vaut pas le coup. Une autre méthode détecte si le résultat est égal ou supérieur à 10, ce qui correspond à un "débordement" (on dépasse les limites d'un chiffre BCD). Si le résultat est plus petit que 10, il n'y a rien à faire : le résultat est bon et la retenue est de zéro. Par contre, si le résultat vaut 10 ou plus, il faut corriger le résultat et générer une retenue à 1.
Il faut donc ajouter un circuit qui détecte si le résultat est supérieur à 9, qui calcule directement la retenue. Ce circuit peut se fabriquer simplement à partir de sa table de vérité, ou en utilisant les techniques que nous verrons dans un chapitre ultérieur sur les comparateurs. La solution la plus simple est clairement d'utiliser la table de vérité, ce qui est très simple, assez pour être laissé en exercice au lecteur. Pour comprendre comment corriger le résultat, établissons une table de vérité qui associe le résultat et le résultat corrigé. L'entrée vaut au minimum 10 et au maximum 9 + 9 = 18. On considère la sortie comme un tout, la retenue étant un 5ème bit, le bit de poids fort.
{|class="wikitable"
|-
! colspan="5" | Entrée
!
! rowspan="10" |
! Retenue
! Résultat corrigé (sans retenue)
! interprétation de la sortie en binaire (retenue inclue)
|-
| 0 || 1 || 0 || 1 || 0
| (10)
| 1 || 0000
| (16)
|-
| 0 || 1 || 0 || 1 || 1
| (11)
| 1 || 0001
| (17)
|-
| 0 || 1 || 1 || 0 || 0
| (12)
| 1 || 0010
| (18)
|-
| 0 || 1 || 1 || 0 || 1
| (13)
| 1 || 0011
| (19)
|-
| 0 || 1 || 1 || 1 || 0
| (14)
| 1 || 0100
| (20)
|-
| 0 || 1 || 1 || 1 || 1
| (15)
| 1 || 0101
| (21)
|-
| 1 || 0 || 0 || 0 || 0
| (16)
| 1 || 0110
| (22)
|-
| 1 || 0 || 0 || 0 || 1
| (17)
| 1 || 0111
| (23)
|-
| 1 || 0 || 0 || 1 || 0
| (18)
| 1 || 1000
| (24)
|}
En analysant le tableau, on voit que pour corriger le résultat, il suffit d'ajouter 6. La raison est que le résultat déborde d'un nibble à 16 en binaire, mais à 10 en décimal : il suffit d'ajouter la différence entre les deux, à savoir 6, et le débordement binaire fait son travail. Donc, la correction après une addition est très simple : si le résultat dépasse 9, on ajoute 6.
On peut maintenant implémenter l'additionneur BCD, en combinant le comparateur avec 10, le circuit de correction, et l'additionneur. La première solution calcule deux versions du résultat : la version corrigée, la version normale. Le choix entre les deux est réalisée par un multiplexeur, commandé par le comparateur.
[[File:Additionneur BCD.png|centre|vignette|upright=1.5|Additionneur BCD]]
L'autre solution utilise un circuit commandable qui soit additionne 6, soit ne fait rien. Le choix entre les deux est commandé par le bit calculé par le comparateur.
[[File:Additionneur BCD, seconde version.png|centre|vignette|upright=1.5|Additionneur BCD, seconde version.]]
Une version alternative du circuit précédent est la suivante. Il contient deux additionneurs : un pour additionner les deux chiffres BCD, un autre pour additionner 6 si besoin. Le résultat du comparateur est directement utilisé pour générer l'opérande du second additionneur : 0 ou 6. Le circuit est simple à concevoir, mais gaspille beaucoup de circuit. Idéalement, il vaudrait mieux utiliser un circuit combinatoire d'addition avec une constante.
[[File:Adder 4 Bit BCD.svg|centre|vignette|upright=2|Additionneur BCD, circuit complet.]]
Pour obtenir un additionneur BCD complet, il suffit d’enchaîner les additionneurs précédents, comme on le ferait avec les additionneurs complets dans un additionneur à propagation de retenue. Au final, l'additionneur BCD est beaucoup plus compliqué qu'un additionneur normal, car il rajoute un comparateur ">9", un petit additionneur pour ajouter 6 et éventuellement d'autres circuits. De plus, il est difficile d'appliquer les optimisations disponibles sur les additionneurs non-BCD. Notamment, les circuits d'anticipation de retenue sont totalement à refaire et le résultat est relativement compliqué. C'est ce qui explique pourquoi le BCD a progressivement été abandonné au profit du binaire simple.
La soustraction en BCD se fait comme en binaire : le nombre à soustraire est remplacé par son complément, le circuit additionne le complément et l'autre opérande, le débordement d'entier fait que le résultat marche. Sauf qu'ici, le complément est un complément à 9. Il se calcule chiffre par chiffre : chaque chiffre est remplacé par (9 - le chiffre en question).
===L'additionneur BCD par ajustement décimal===
L'additionneur BCD précédent effectuait son travail chiffre BCD par chiffre BCD, mais il existe des additionneurs BCD qui font autrement. Sur les premiers processeurs x86, il n'y avait pas d'opération d'addition BCD proprement dit, seulement une addition binaire normale de 8, 16 ou 32 bits. Par contre, elle était secondée par une opération dite d''''ajustement décimal''' qui transformait un nombre binaire en nombre codé en BCD. L'opération d'ajustement décimal prenait un opérande de 8 bits codé en binaire et fournissait un résultat de la même taille, c'est à dire deux chiffres BCD. Effectuer une addition BCD demandait donc de faire deux opérations à la suite : une addition binaire simple, suivie par l'opération d'ajustement décimal. Cela permettait de gérer des nombres entiers en binaire usuel et des entiers BCD sans avoir deux instructions d'addition séparées pour les deux, sans compter que cela simplifiait aussi les circuits d'addition.
L'ajustement décimal s'effectue en ajoutant une constante bien précise à l'opérande à convertir en BCD. L'idée est que la constante est découpée en morceaux de 4 bits, correspondant chacun à un chiffre BCD de l'opérande, chaque morceau contenant soit un 0, soit 6. Cela permet d'ajouter soit 0, soit 6, à chaque chiffre BCD, et donc de le corriger. La propagation des retenues d'un chiffre à l'autre est effectuée automatiquement par l'addition binaire de la constante. L'opération d'ajustement décimal calcule automatiquement la constante. Elle découpe l'opérande en ''nibbles'', vérifie si chaque ''nibble'' est supérieur ou égal à 10, puis détermine la valeur de chaque ''nibble'' de la constante finale. Par exemple, si je prends l'opérande 1001 1110, le ''nibble'' de poids faible déborde, alors que celui de poids fort non. La constante sera donc 0000 0110 : 0x06. Inversement, si le ''nibble'' de poids fort déborde et pas celui de poids faible, la constante sera alors 0x60. Et la constante est de 0x66 si les deux ''nibbles'' débordent, de 0x00 si aucun ne déborde.
Le circuit d’ajustement décimal est donc composé de trois étapes : deux étapes pour calculer la constante, et un circuit d'addition pour additionner cette constante au nombre de départ. La première étape découpe l'opérande en morceaux de 4 bits, en chiffres BCD, et vérifie si chacun d'entre eux vaut 10 ou plus. La seconde étape prend les résultats de la première étape, et les combine pour calculer la constante. Enfin, on trouve l'addition finale, qui était réalisée par un circuit d'addition utilisé à la fois pour l'ajustement décimal et l'addition binaire. La différence entre une addition normale et une opération d'ajustement décimal tient dans le fait que les deux premières étapes sont désactivées dans une addition normale.
[[File:Additionneur BCD parallèle.png|centre|vignette|upright=1.5|Additionneur BCD parallèle]]
===L'additionneur biquinaire===
Les entiers BCD ne sont qu'un des encodages hybrides entre décimal et binaire. L'encodage biquinaire est l'un d'entre eux et nous allons faire un rappel rapide à ce sujet. Pour simplifier, un chiffre encodé en biquinaire est composé de deux parties : un bit, couplé à une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits (0, 1, 2, 3 et 4). Le bit indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9.
Additionner deux nombres de biquinaire demande donc d'additionner deux parties quinaires encodées en ''one-hot'' et d'additionner deux bits. Mais attention : il faut tenir compte de la retenue de l'addition des parties quinaires. Et idéalement, il faut aussi tenir compte d'une retenue entrante, provenant de l'addition de la colonne de chiffres précédente. Toute la difficulté vient de la création de l'additionneur ''one-hot''. Heureusement, vu qu'il n'y a que 4-5 bits à additionner, il est souvent fabriqué à partir de sa table de vérité.
[[File:Additionneur bi-quinaire.png|centre|vignette|upright=2|Additionneur bi-quinaire]]
Un avantage du biquinaire est que le calcul du complément à 9 est très simple. Il faut pour cela : inverser la partie binaire avec une porte NON, puis inverser l'ordre des bits de la partie quinaire. Concrètement, le bit de poids faible devient le bit de poids fort, et ainsi de suite. Par exemple, une partie quinaire 01000 devient 00010, 10000 devient 00001, 00100 ne change pas, etc. Le tout peut se calculer avec une porte NON et 5 multiplexeurs.
===L'additionneur BCD avec calculs intermédiaires en biquinaire===
L'ordinateur IBM 1401, un ancien ''mainframe'' des années 60, utilisait un additionneur BCD un peu particulier. Les nombres étaient encodés en BCD dans la mémoire de l'ordinateur, mais les circuits de calcul utilisaient la représentation biquinaire. Lors d'un calcul, le processeur de l'ordinateur traduisait les chiffres BCD en représentation biquinaire, faisait une addition en biquinaire, avant de traduire le résultat en BCD normal.
Pour être précis, l'IBM 1401 utilisait une variante du biquinaire. L'encodage biquinaire de l'IBM 1401 est le suivant : la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre pair encodé par la partie quinaire. Si l'IBM 1401 utilisait cette variante du biquinaire, c'est car elle donnait des circuits de conversion BCD-biquinaire plus économes en portes logiques et plus rapides.
La partie binaire est le bit de poids faible du chiffre BCD, la partie biquinaire est calculée par un simple décodeur qui prend en entrée le chiffre BCD, amputé de son bit de poids faible. La traduction inverse demande d'utiliser un encodeur, à la place du décodeur. Par contre, le circuit d'addition biquinaire était plus compliqué du fait de la gestion des retenues. L'addition des parties binaires et quinaires se faisait en parallèle, dans deux additionneurs séparés. Cependant, l'addition des parties binaire fournit une retenue, qu'il faut prendre en compte. Pour cela, l'IBM 1401 disposait d'un troisième additionneur qui fournissait le résultat final, encodé en biquinaire.
[[File:Additionneur bi-quinaire de l'IBM 1401.png|centre|vignette|upright=2|Additionneur biquinaire de l'IBM 1401]]
Une implémentation moderne demanderait d'utiliser des portes ET combinées à des portes OU, le circuit pouvant être construit simplement à partir de sa table de vérité. Sur l'IBM 1401, le circuit était cependant différent, en raison de l'utilisation de OU câblés, des croisements de fils qui fonctionnent comme des portes OU, que nous n'avons pas encore vu pour le moment, mais qui seront détaillés dans quelques chapitres. Les OU câblés étaient utilisés pour simplifier le design du circuit, mais demandaient des portes logiques spécifiques, ce qui collait avec le fait que ce ''mainframe'' utilisait des transistors en Germanium. L'implémentation exacte est décrite dans cet article de blog, mais je ne recommande sa lecture qu'à ceux qui savent ce qu'est un OU câblé :
* [https://www.righto.com/2015/10/qui-binary-arithmetic-how-1960s-ibm.html Qui-binary arithmetic: how a 1960s IBM mainframe does math].
==Les débordements d'entier lors d'une addition/soustraction==
Les instructions arithmétiques manipulent des entiers codés sur un nombre fixe de bits, qui ne peuvent prendre leurs valeurs que dans un intervalle. Pour les nombres positifs, un ordinateur qui code ses entiers sur n bits pourra coder tous les entiers allant de 0 à <math>2^n -1</math>. Pour les nombres négatifs, l'intervalle est différent et dépend de la représentation utilisée. Dans le cas général, l'ordinateur peut coder les valeurs comprises de <math>N_\text{min}</math> à <math>N_\text{max}</math>. Si le résultat d'un calcul sort de cet intervalle, il ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math> , alors que la valeur basse vaut <math>N_\text{min} - 1</math> (avec <math>N_\text{max}</math> et <math>N_\text{min}</math> respectivement la plus grande et la plus petite valeur codable par l'ordinateur).
===La correction des débordements d'entier : l'arithmétique saturée===
Quand un débordement d'entier survient, tous les circuits de calcul ne procèdent pas de la même manière. Dans les grandes lignes, il y a deux réactions possibles : soit on corrige automatiquement le résultat du débordement, soit on ne fait rien et on se contente de détecter le débordement.
Si le débordement n'est pas corrigé automatiquement, le circuit ne conserve que les bits de poids faibles du résultat. Les bits en trop sont simplement ignorés. On dit qu'on utilise l''''arithmétique modulaire'''. Le problème avec ce genre d'arithmétique, c'est qu'une opération entre deux grands nombres peut donner un résultat très petit. Par exemple, si je dispose de registres 4 bits et que je souhaite faire l'addition 1111 + 0010 (ce qui donne 15 + 2), le résultat est censé être 10001 (17), ce qui est un résultat plus grand que la taille d'un registre. En conservant les 4 bits de poids faible, j’obtiens 0001 (1). En clair, un résultat très grand est transformé en un résultat très petit. Cela peut poser problèmes si on travaille uniquement avec des nombres positifs, mais c'est aussi utilisé pour coder des nombres en complément à deux.
D'autres circuits utilisent ce qu'on appelle l''''arithmétique saturée''' : si un calcul génère un débordement, on arrondi le résultat au plus grand entier supporté par le circuit. Les circuits capables de calculer en arithmétique saturée sont un peu plus complexes, vu qu'il faut rajouter des circuits pour corriger le résultat en cas de débordement. Il suffit généralement de rajouter un ''circuit de saturation'', qui prend en entrée le résultat et le corrige en cas de débordement. Ce circuit de saturation met la valeur maximale en sortie si un débordement survient, mais se contente de recopier le résultat du calcul sur sa sortie s'il n'y a pas de débordement. Typiquement, il est composé d'une couche de multiplexeurs, qui sélectionnent quelle valeur mettre sur la sortie : soit le résultat du calcul, soit le plus grand nombre entier géré par le processeur, soit le plus petit (pour les nombres négatifs/soustractions).
L'arithmétique saturée est utilisée pour les additions et soustractions, mais c'est plus rare pour les multiplications/divisions. Une des raisons est que le résultat d'une addition/soustraction prend un bit de plus que le résultat, là où les multiplications doublent le nombre de bits. Quand une addition déborde, le résultat réel est proche de la valeur maximale codable. mais quand une multiplication déborde, le résultat peut parfois valoir 200 à 60000 fois plus que la valeur maximale codable. Les calculs avec une valeur saturée/corrigée sont donc crédibles pour une suite d'additions, mais pas pour une suite de multiplications.
===La détection des débordements entiers===
Quand un débordement d'entier a eu lieu, il vaut mieux que l'additionneur prévienne ! Pour cela, l'additionneur a une '''sortie de débordement''', parfois nommée ''Overflow'', dont la valeur indique si l'addition a généré un débordement d'entier ou non. Reste que détecter un débordement ne se fait pas de la même manière selon que l'on parle d'un additionneur non-signé ou signé.
Pour les additionneur non-signés, l'additionneur calcule un bit de plus que ce qui est supporté par l'ordinateur. Par exemple, un additionneur 32 bits fournit un résultat sur 33 bits, un débordement d'entier a lieu quand le 33ème bit est à 1. Précisément, la sortie de débordement n'est autre que la retenue finale, celle fournie par le dernier additionneur complet. Le seul type de débordement possible est un débordement par le haut, où le résultat dépasse la valeur maximale. Avec l'arithmétique saturée, le circuit de saturation consiste en une seule couche de multiplexeurs, voire en un circuit de mise à la valeur maximale tel que vu dans le chapitre sur les opérations bits à bits.
[[File:Gestion des débordements d'entiers.png|centre|vignette|upright=2.5|Gestion des débordements d'entiers lors d'une addition non-signée.]]
Pour les additionneurs non-signés, la gestion des débordements d'entiers dépend fortement de la représentation signée. Nous allons étudier le cas du complément à deux. Si vous vous rappelez le chapitre 1, les calculs sur des nombres en complètement à deux utilisent les règles de l'arithmétique modulaire, c'est une condition nécessaire. À priori, on peut penser que dans ces conditions, les débordements d'entiers sont une chose parfaitement normale, qui nous permet d'avoir des résultats corrects. Néanmoins, certains débordements d'entiers peuvent survenir malgré tout et produire des bugs assez ennuyeux.
Si l'on tient en compte les règles du complément à deux, on sait que le bit de poids fort (le plus à gauche) permet de déterminer si le nombre est positif ou négatif : il indique le signe du nombre. Tout se passe comme si les entiers en complément à deux étaient codés sur un bit de moins, et avaient leur longueur amputé du bit de poids fort. Si le résultat d'un calcul écrase le bit de poids fort, il y a un débordement d'entiers. Il existe une règle simple qui permet de détecter ces débordements d'entiers. L'addition de deux nombres positifs ne peut pas être un nombre négatif. Si on additionne deux nombres dont le bit de signe est à 0 et que le bit de signe du résultat est à 1, on est en face d'un débordement d'entiers. Même chose pour deux nombres négatifs : le résultat de l'addition ne peut pas être positif. On peut résumer cela en une phrase : si deux nombres de même signe sont ajoutés, un débordement a lieu quand le bit du signe du résultat a le signe opposé.
Modifier les circuits d'au-dessus pour qu'ils détectent les débordements en complément à deux est simple comme bonjour : il suffit créer un petit circuit combinatoire qui prenne en entrée les bits de signe des opérandes et du résultat, et qui fasse le calcul de l'indicateur de débordements. Si l'on rédige sa table de vérité, on doit se retrouver avec la table suivante :
{|class="wikitable"
|-
!Entrées
!Sortie
|-
|000||0
|-
|001||1
|-
|010||0
|-
|011||0
|-
|100||0
|-
|101||0
|-
|110||1
|-
|111||0
|}
L'équation de ce circuit est la suivante, avec <math>S_a</math> et <math>S_b</math> les signes des deux opérandes, et <math>C_i</math> la retenue de la colonne précédente :
: <math> ( S_a . S_b . \overline{C_i} ) + ( \overline{S_a} . \overline{S_b} . C_i )</math>
En simplifiant, on obtient alors :
: <math>( S_a . S_b ) \oplus C_i</math>
Or, il se trouve que <math>S_a . S_b</math> est tout simplement la retenue en sortie du dernier additionneur, que nous noterons <math>C_o</math>. On trouve donc :
: <math>C_o \oplus C_i</math>
Il suffit donc de faire un XOR entre la dernière retenue et la précédente pour obtenir le bit de débordement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de décalage et de rotation
| prevText=Les circuits de décalage et de rotation
| next=Les circuits de comparaison
| nextText=Les circuits de comparaison
}}
</noinclude>
ntgjdvo21wda30a2lt0aoh0fv38kmee
768671
768670
2026-06-25T20:08:14Z
Mewtow
31375
/* L'addition non signée */
768671
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir les circuits capables de faire une addition ou une soustraction, ainsi que quelques circuits spécialisés. Précisons cependant que les fabricants de processeurs travaillent d'arrache-pied pour trouver des moyens de rendre ces circuits de calcul plus rapides et plus économes en énergie. Autant vous dire que les circuits que vous allez voir sont vraiment des circuits qui font pâle figure comparé à ce que l'on peut trouver dans un vrai processeur commercial !
==Les circuits pour additionner 2 ou 3 bits==
L'addition se fait en binaire de la même manière qu'en décimal. On additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. La soustraction fonctionne sur le même principe, sur le même modèle qu'en décimal.
[[File:Binary Addition Demonstration.svg|centre|vignette|Exemple d'addition en binaire.]]
En clair, additionner deux nombres demande d'additionner 2 bits et une retenue sur chaque colonne, et de propager les retenues d'une colonne à l'autre. La propagation des retenues est quelque chose de simple en apparence, mais qui est sujet à des optimisations extraordinairement nombreuses. Aussi, pour simplifier l'exposition, nous allons voir comment gérer une colonne avant de voir comment sont propagées les retenues.
[[File:Full Adder Block.svg|vignette|upright=1|class=transparent|Additionneur complet.]]
Si on effectue une addition en colonne, on doit additionner les deux bits sur la colonne, mais aussi additionner une éventuelle retenue. Il faut donc créer un circuit qui additionne trois bits : deux bits de données, plus une retenue. Ce circuit qui additionne trois bits est appelé un '''additionneur complet'''. Il fournit en sortie deux bits : un bit de somme et une retenue sortante. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|-
||0||0||0|| ||0||0
|-
||0||0||1|| ||0||1
|-
||0||1||0|| ||0||1
|-
||0||1||1|| ||1||0
|-
||1||0||0|| ||0||1
|-
||1||0||1|| ||1||0
|-
||1||1||0|| ||1||0
|-
||1||1||1|| ||1||1
|}
Il est possible d'utiliser un tableau de Karnaugh pour traduire la table de vérité, mais elle donne un résultat assez compliqué. La retenue et le bit de somme sont calculés à part. La retenue est calculée avec trois portes ET et une porte OU. Le bit de somme est calculé lui avec 4 portes ET, une porte OU, et trois portes NON.
[[File:Full Adler with 3 entries.png|centre|vignette|upright=2|Additionneur complet, fabriqué à partir de la table de vérité.]]
D'autres méthodes donnent des résultats plus compréhensibles. Nous allons les voir dans la suite de cette section.
===L'additionneur complet basé sur des demi-additionneurs===
[[File:1-bit half-adder.svg|class=transparent|vignette|Demi-addtionneur.]]
Nous avons déjà vu comment additionner deux bits dans le chapitre sur les incrémenteurs, mais quelques rappels ne seront pas de trop. Le '''demi-additionneur''' est un circuit qui additionne deux bits. Il implémente la table d'addition, qui est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Mais un demi-additionneur n'additionne que deux bits, il manque de quoi additionner la retenue. Une solution pour cela est d'enchaîner deux demi-additionneurs : un qui additionne les deux bits de données, et un second qui additionne la retenue au résultat du précédent. La retenue finale se calcule en combinant les sorties de retenue des deux demi-additionneurs, avec une porte OU. Le résultat est un additionneur complet fabriqué avec deux demi-additionneurs. Pour vous en convaincre, établissez la table de vérité de ce circuit, vous verrez que ça marche.
[[File:Full Adder Blocks.svg|centre|vignette|upright=2|Additionneur complet fabriqué avec deux demi-additionneurs.]]
[[File:Half-adder.svg|vignette|upright=1|Circuit d'un demi-additionneur naïf.]]
L'implémentation naïve d'un demi-additionneur utilise une porte XOR et une porte ET, comme illustré ci-contre. Le circuit s'obtient en faisant uen traduction littérale de la table de vérité du circuit. Si je dis qu'elle est naïve, c'est car il est possible de l'optimiser, de manière à éliminer des redondances cachées. Mais nous avions déjà vu cela dans le chapitre sur les incrémenteurs, aussi laissons cela pour plus tard.
En combinant deux demi-additionneurs de ce type, on obtient l'additionneur complet suivant. Les deux sorties de retenue des demi-additionneurs sont combinées avec une porte OU, pour calculer la retenue finale.
{| class="flexible"
|[[File:Full Adder Modules.svg|class=transparent|Composition d'un additionneur complet. On voit bien que celui-ci est composé de deux demi-additionneurs, en rouge et en bleu, auxquels on a ajouté une porte OU pour calculer la retenue finale.]]
|[[File:Full-adder.svg|300px|class=transparent|Circuit d'un additionneur complet.]]
|}
Le fait de combiner les deux retenues avec une porte OU n'est pas ce qu'il y a de plus intuitif. La table de vérité nous dit que ça fonctionne, mais on comprend mal pourquoi. Mais tout devient plus clair quand on sait que les deux retenues ne peuvent pas être à 1 en même temps. Même dans l'addition 1 + 1 + 1, seule une retenue est à 1. Soit l'addition des deux bits d'opérande donne naissance à une retenue, soit c'est l'addition ''résultat + retenue entrante''. La retenue sortant est donc à 1 quand une des deux addition donne une retenue. D'où l'usage d'une porte OU. Remarquez qu'une porte XOR donne le même résultat, vu que le cas où les deux retenues sont à 1 n'est jamais rencontré.
===La propagation et la génération des retenues===
L'additionneur complet que nous allons voir dans cette section sert à introduire les concepts de propagation, génération et absorption de retenue. Il s'agit de concepts très importants quand on étudie les additionneurs. l'idée part d'un principe très simple : la retenue sortante dépend de la retenue d'entrée. La relation entre les deux se résume à trois cas, qui dépendent de la valeur des deux bits additionnés, nommés A et B.
* Dans le premier cas, la retenue entrante est égale à la retenue sortante. On dit que la retenue entrante est propagée par l'additionneur.
* Dans le second cas, la retenue est forcée à 0 : la retenue sortante vaut 0, peu importe la valeur de la retenue entrante.
* Dans le troisième cas, la retenue est forcée à 1 : la retenue entrante vaut 1, peu importe la valeur de la retenue entrante.
[[File:Signaux P et G fournis par un demi-additionneur.png|vignette|Signaux P et G fournis par un demi-additionneur]]
Maintenant, créons un circuit qui nous dise si une retenue est propagée ou générée. Il a deux bits de sortie, nommés P et G : P pour ''Propagate'', G pour ''Generate''. Le bit P est à 1 si la retenue entrante est propagée, il est à 0 sinon. Le bit G est à 1 si une retenue est générée, à 0 sinon. Une retenue est considérée comme absorbée si elle n'est pas ni propagée ni générée, pas besoin d'un troisième bit pour gérer ce cas.
Il se trouve que ce circuit n'est autre qu'un demi-additionneur ! Pour vous en rendre compte, regardez la table de vérité d'un additionneur complet, illustrée ci-dessous. Lorsque les deux bits d'opérande sont à 0, la retenue sortante vaut toujours 0. Si ils sont tous deux à 1, alors la retenue sortante vaut 1. S'ils sont différents, alors retenues sortante et entrante sont égales. Le bit P est donc généré par une simple porte XOR. Quant au bit G, il est à 1 si les deux bits d'opérandes sont à 1, ce qui correspond à une porte ET. Il se trouve que ces deux portes forment un demi-additionneur !
{|class="wikitable"
|-
! Opérande 1 !! Opérande 2 !! Retenue entrante !! !! Retenue sortante
|- class="f_rouge"
||0||0||0|| ||0
|- class="f_rouge"
||0||0||1|| ||0
|- class="f_bleu"
||0||1||0|| ||0
|- class="f_bleu"
||0||1||1|| ||1
|- class="f_bleu"
||1||0||0|| ||0
|- class="f_bleu"
||1||0||1|| ||1
|- class="f_vert"
||1||1||0|| ||1
|- class="f_vert"
||1||1||1|| ||1
|}
Ensuite, créons un circuit qui prend ces deux sorties P et G, et calcule la retenue sortante en fonction. Le circuit en question a trois entrées : la retenue entrante, les deux bits P et G. La retenue sortante vaut 1 soit si une retenue est générée, soit si la retenue entrante est propagée et qu'elle vaut 1. En notant <math>{C_{in}}</math> et <math>{C_{out}}</math> les retenues entrantes et sortante, on a :
: <math>{C_{out}} = G + \left( P . C_{in} \right)</math>
Le circuit est donc composé d'une porte OU et d'une porte ET. En combinant un demi-additionneur avec le circuit de calcul de retenue sortante vu plus haut, on a :
[[File:Additionneur complet avec propagation et génération de retenue.png|class=transparent|centre|vignette|upright=2|Additionneur complet avec propagation et génération de retenue.]]
Certes, on retombe sur le circuit vu plus haut. Mais il est possible de remplacer le circuit en orange par un autre. Par exemple, il est possible de le remplacer par multiplexeur, qui choisit entre la retenue générée et la retenue entrante (propagée). Le choix se fait selon la valeur du bit P, qui chosiit entre propager la retenue et la générer.
[[File:Additionneur crée avec un multiplexeur.png|centre|vignette|upright=1.5|Additionneur crée avec un multiplexeur]]
Le circuit exact est illustré ci-dessous. Il semble utiliser plus de portes logiques que l'additionneur complet précédent. Cependant, nous verrons dans quelques chapitres qu'il est possible d'implémenter un multiplexeur avec seulement 6 transistors, voire moins ! L'implémentation utilise des portes à transmission, mais nous en reparlerons dans le chapitre sur les transistors, quand nous verrons les additionneurs à ''Manchester Carry Chain''. Au passage, une variante de ce circuit a été utilisée dans le processeur processeur 8086 d'Intel, comme on le verra dans le chapitre suivant.
[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
[[File:FulladderPG.png|vignette|upright=1|Additionneur complet avec deux sorties P et G.]]
Pour finir, sachez qu'il existe des additionneurs qui fournissent : le bit de résultat, deux sorties P et G qui indiquent si l'addition propage ou génère une retenue. Il ne s'agit pas d'additionneurs complets, car il manque de quoi calculer la retenue sortante. De tels additionneurs seront appelés des '''additionneurs partiels'''. De tels additionneurs sont utilisés dans certains additionneurs pour gagner en performance.
Enfin, il existe aussi ce que j'ai décidé d'appeler des '''additionneurs P/G''', qui sont des additionneurs complets auxquels on a ajouté deux sorties P et G, en plus de la sortie de retenue. Leur circuit est le même que celui d'un additionneur complet, auquel on a ajouté deux fils. Vu que ces deux sorties sont fournies par le premier demi-additionneur, ajouter ces deux sorties demande d'ajouter des fils, pas de portes logiques. De tels additionneurs sont aussi utilisés dans certains additionneurs pour gagner en performance.
===L'additionneur complet basé sur une porte à majorité===
Maintenant, voyons une dernière implémentation possible de l'additionneur complet. Mais avant de voir comment l'additionneur est implémenté, nous allons devoir faire un petit focus sur la retenue sortante. Reprenons la table de vérité, et regardons ce qu'il en est pour la retenue sortante uniquement.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante
|-
||0||0||0|| ||0
|-
||0||0||1|| ||0
|-
||0||1||0|| ||0
|-
||0||1||1|| ||1
|-
||1||0||0|| ||0
|-
||1||0||1|| ||1
|-
||1||1||0|| ||1
|-
||1||1||1|| ||1
|}
Vous remarquerez que la retenue sortante est égale au bit majoritaire, parmi les trois bits d'opérande. Si deux ou trois bits sont à 1, la retenue sortante vaut 1. Et réciproquement avec 0. Le circuit de calcul de la retenue peut donc être remplacé par une '''porte à majorité'''.
[[File:Additionneur crée avec une porte à majorité.png|centre|vignette|upright=2|Additionneur crée avec une porte à majorité]]
Mais une porte à majorité est assez gourmande en circuit, ce qui fait qu'un additionneur pareil ne serait pas pratique, ni utile. Du moins, ce serait le cas s'il n'y avait pas une possibilité d'optimisation extrêmement intéressante. Il est possible de calculer le 'bit de somme' à partir de la retenue sortante ! En effet, le bit de somme est l'inverse de la retenue sortante, sauf dans deux cas : les trois bits d'entrée sont à 0, où ils sont tous à 1. Le bit de somme vaut 0 dans le premier cas, 1 dans le second cas.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|- class="f_bleu"
||0||0||0|| ||0||0
|- class="f_vert"
||0||0||1|| ||0||1
|- class="f_vert"
||0||1||0|| ||0||1
|- class="f_vert"
||0||1||1|| ||1||0
|- class="f_vert"
||1||0||0|| ||0||1
|- class="f_vert"
||1||0||1|| ||1||0
|- class="f_vert"
||1||1||0|| ||1||0
|- class="f_rouge"
||1||1||1|| ||1||1
|}
En clair, 6 lignes de la table de vérité sur 8 peuvent se calculer avec une porte à majorité, le reste demandant quelques portes logiques pour faire la correction. L'implémentation en circuit demande donc trois choses :
* de calculer la retenue sortante et de quoi l'inverser ;
* un circuit qui vérifie si tous les bits opérande valent 0 : une porte NOR ou OU fait l'affaire ;
* un circuit qui vérifie s'ils valent tous 1, à savoir une porte ET.
Ensuite, on combine le résultat des trois circuits précédents pour obtenir le résultat final. La combinaison est le fait du circuit en jaune dans le schéma ci-dessous.
[[File:Full adder basé sur une modification de la retenue.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue]]
Le circuit en jaune est assez simple. Quand la porte ET sort un 1, la sortie doit être forcée à 1. Par contre, quand la porte OU sort un zéro, la sortie doit être forcée à 0. Il suffit donc d'enchainer un circuit de mise à 1 et un circuit de mise à 0. Nous avons vu dans le chapitre sur les opérations de masquage qu'il s'agit respectivement d'une porte OU et d'une porte ET.
[[File:Full adder basé sur une modification de la retenue interne, au complet.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue interne, au complet]]
Divers processeurs utilisaient des additionneurs complets de ce type : le fameux Z80, l'Intel 4004 et de l'Intel 8008 sont les exemples les plus notables. Pourtant, on pourrait s'étonner qu'un tel circuit ait existé. Il utilise beaucoup de portes logiques, a une profondeur logique supérieure : il n'a rien d'avantageux. Sauf qu'il était utilisé sur d'anciens processeurs, qui utilisaient la technologie dite TTL, différente de la technologie CMOS des transistors modernes. Et avec la technologie TTL, il est possible de fusionner plusieurs portes logiques ET et NOR en une seule porte logique ET/OU/NON ! Un additionneur complet construit ainsi ne prenait que deux portes logiques : une pour le calcul de la retenue sortante, une autre pour le reste du circuit.
==L'addition non signée==
Voyons maintenant un circuit capable d'additionner deux nombres entiers: l''''additionneur'''. Dans la version qu'on va voir, ce circuit manipulera des nombres strictement positifs. L'addition des nombres codés en complètement à deux sera vu dans une section ultérieure.
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
L'interface d'un additionneur est illustrée ci-contre. Un additionneur prend deux opérandes sur deux entrées séparées, et fournit le résultat sur une sortie dédiée. De plus, il a une entrée sur laquelle envoyer une retenue entrante, et a une sortie pour la retenue sortante. La sortie de retenue indique que le résultat de l'addition a débordé (au sens d'un débordement d'entier). Pour l'entrée de retenue, son utilité deviendra plus claire dans ce qui suit, mais souvenez-vous qu'elle existe.
===L'additionneur série===
Il est possible d'additionner deux nombres bit par bit, grâce à un additionneur complet associé à plusieurs registres à décalages. Les opérandes sont placées chacune dans un registre à décalage, et l'additionneur complet utilise les bits sortants de ces registres à décalage. Le bit du résultat est envoyé au registre à décalage pour le résultat. La retenue de l'addition est stockée dans une bascule de 1 bit, en attente du prochain cycle d'horloge. Un tel additionneur est appelé un '''additionneur série'''. Il a été utilisé sur d'anciens ordinateurs dans les années 50-60, aussi bien des prototypes que des ordinateurs commerciaux.
[[File:Additionneur série.jpg|centre|vignette|upright=2.5|Additionneur série.]]
===L'additionneur à propagation de retenue===
L''''additionneur à propagation de retenue''' est aussi appelé l'additionneur ''ripple carry'', c'est son nom anglais. J'utiliserais occasionnellement le nom anglais dans ce qui suit, car celui-ci est plus court. Ilutilise un additionneur complet pour chaque colonne de l'addition. Pour gérer les retenues, il suffit ainsi de câbler des additionneurs complets les uns à la suite des autres, chacun envoyant sa retenue à la colonne suivante.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Notons la présence de la retenue sortante, qui est utilisée pour détecter les débordements d'entier, ainsi que pour d'autres opérations. Le bit de retenue final est souvent stocké dans un registre spécial du processeur (généralement appelé ''carry flag'').
Notez aussi, à droite du schéma précédent, l'entrée de retenue <math>\text{R}_\text{Entrée}</math>. Elle est très utile pour l'implémentation de certaines opérations comme l'inversion de signe, la soustraction, l'incrémentation, etc. Certains processeurs sont capables de faire une opération appelée ADC, ADDC ou autre nom signifiant ''Addition with Carry'', qui permet de faire le calcul A + B + Retenue (la retenue en question est la retenue sortante de l'addition précédente, stockée dans le registre ''carry flag''). Son utilité principale est de permettre des additions d'entiers plus grands que ceux supportés par le processeur. Par exemple, cela permet de faire des additions d'entiers 32 bits sur un processeur 16 bits.
[[File:Adder Network 3 Bit Sum Delay 003.svg|vignette|upright=0.5|Propagation de retenue dans l'additionneur.]]
Cet additionneur utilise très peu de portes logiques, ce qui fait qu'il était utilisé sur certains processeurs 8 et 16 bits, dont le budget en portes logiques était limité. Bien que très simple, cet additionneur est cependant peu performant. Son temps de calcul est proportionnel à la taille des opérandes. Par exemple, additionner deux opérandes de 32 bits prendra deux fois plus de temps que pour des opérandes de 16 bits. La raison est que les retenues doivent se propager à travers le circuit, du premier additionneur jusqu'au dernier.
L'addition étant une opération fréquente, d'autres additionneurs ont été inventés. Ils utilisent des optimisations qui utilisent plus de circuits pour gagner en rapidité. Mais avant de voir ces optimisations, nous allons voir s'il est possible d'optimiser les additionneurs à propagation de retenue.
===L'additionneur à saut de retenue===
L'additionneur à propagation de retenue peut être optimisé de deux manières. La première accélère la propagation de la retenue, en agissant au niveau des additionneurs complets. La ''Manchester carry chain'' est une optimisation de ce type, mais on ne peut pas encore expliquer à ce stade du cours, car elle optimisait les additionneurs complets au niveau des transistors eux-mêmes. Elle a été utilisée sur de nombreux processeurs connus, comme le 8086 d'Intel ou le Z80.
La seconde manière rend l'addition plus rapide dans certains cas. Le résultat est un additionneur dont le temps de calcul est variable. Le calcul prendra quelques cycles d'horloges avec certains opérandes, mais d'autres opérandes ne verront aucune amélioration. Il n'améliore pas le pire des cas, dans lequel la retenue doit être propagée du début à la fin, du bit de poids faible au bit de poids fort. Mais dans d'autres cas, il permet d'avoir le résultat en avance. L'optimisation en question donne l''''additionneur à saut de retenue''' (''carry-skip adder'').
Il est composé en enchainant plusieurs additionneurs plus petits, qui additionnent 4/5 bits, rarement plus. De tels additionneurs de petite taille sont appelés des '''blocs''', dans ce qui suit. Nous allons prendre un exemple où les additionneurs font 4 bits. L'idée est de détecter précocement si la retenue entrante est propagée à travers tout le bloc. Si la retenue est propagée dans tous le bloc, on peut directement l'envoyer sur la sortie de retenue, la retenue saute le bloc entier. Dans le cas contraire, on doit calculer la retenue normalement. Le choix entre les deux est le fait d'un multiplexeur. Toute la difficulté est de savoir comment commander le multiplexeur.
[[File:Carry skip adder, principe de base.png|centre|vignette|upright=2.5|Carry skip adder : principe de base]]
Pour savoir si une retenue est propagée dans un bloc entier, il faut utiliser des additionneurs complets, qui ont en plus une sortie P indiquant que l'additionneur propage la retenue. Le bloc propage une retenue si chaque additionneur complet propage la retenue, donc s'ils ont tous leur sortie P à 1. Un simple ET logique suffit à déterminer si c'est le cas. Le signal de commande du multiplexeur est donc un vulgaire ET entre toutes les sorties P des additionneurs complets du bloc.
[[File:Additionneur à saut de retenue.png|centre|vignette|upright=2.5|Calcul de la commande du MUX.]]
Voici ce que ça donne pour un bloc de 4 bits :
[[File:CSAdder4Bit.svg|centre|vignette|upright=1.5|Additionneur ''carry skip'' : bloc de 4 bits.]]
L'additionneur à saut de retenue est construit en assemblant plusieurs blocs de ce type.
[[File:BCSAdder16Bit.svg|centre|vignette|upright=3|Additionneur à saut de retenue.]]
Les blocs sont tous identiques dans le cas le plus simple, mais il est possible d'utiliser des blocs de taille variable. Par exemple, le premier bloc peut avoir des opérandes de 6 bits, le second des opérandes de 7 bits, etc. Faire ainsi permet de gagner un petit peu en performances, si la taille de chaque bloc est bien choisie. La raison est une question de temps de propagation des retenues. La retenue met plus de temps à se propager à travers 8 blocs qu'à travers 4, ce qui prend plus de temps qu'à travers 2 blocs, etc. En tenir compte fait que la taille des blocs tend à augmenter ou diminuer quand on se rapproche des bits de poids fort.
===L'additionneur à sélection de retenue===
L''''additionneur à sélection de retenue''' utilise aussi des blocs, comme les additionneurs précédents. L'addition se fait en deux versions : une avec la retenue du bloc précédent valant zéro, et une autre version avec la retenue du bloc précédent valant 1. Il suffira alors de choisir le bon résultat avec un multiplexeur, une fois cette retenue connue. On gagne ainsi du temps en calculant à l'avance les valeurs de certains bits du résultat, sans connaître la valeur de la retenue. Petit détail : sur certains additionneurs à sélection de retenue, les blocs de base n'ont pas la même taille. Cela permet de tenir compte des temps de propagation des retenues entre les blocs.
[[File:Additionneur à sélection de retenue.png|centre|vignette|upright=1.5|Additionneur à sélection de retenue avec seulement deux blocs.]]
Dans les exemples du dessus, chaque sous-additionneur étaient des additionneurs à propagation de retenue. Mais ce n'est pas une obligation, et tout autre type d’additionneur peut être utilisé. Par exemple, on peut faire en sorte que les sous-additionneurs soient eux-mêmes des additionneurs à sélection de retenue, et poursuivre ainsi de suite, récursivement. On obtient alors un '''additionneur à somme conditionnelle''', plus rapide que l'additionneur à sélection de retenue, mais qui utilise beaucoup plus de portes logiques.
===Les additionneurs à anticipation de retenue===
Les '''additionneurs à anticipation de retenue''' calculent chaque retenue sans avoir à propager les retenues précédentes. Au lieu de calculer les retenues une par une, ils calculent toutes les retenues en parallèle, à partir des bits des opérandes. Une fois les retenues pré-calculées, il suffit de les additionner avec les deux bits adéquats, pour obtenir le résultat. Ces additionneurs sont composés de deux parties :
* un circuit qui pré-calcule les retenues, sans les propager, directement à partir des opérandes ;
* d'une couche d'additionneurs complets, qui additionnent chacun deux bits d'opérande et la retenue pré-calculée.
[[File:Additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Additionneur à anticipation de retenue.]]
Le calcul des retenues se fait en deux étapes. La première détermine si les retenues sont propagées ou générées, pour chaque colonne. Elle détermine, pour chaque colonne, les bits P et G qui indiquent respectivement : qu'une retenue a été générée sur cette colonne, que la colonne propage la retenue précédente. La seconde étape utilise ces signaux P et G pour déterminer la retenue sur chaque colonne. Le circuit de calcul des retenues est donc composé de deux parties : une couche de demi-additionneurs pour générer les signaux P et G, un circuit de calcul des retenues proprement dit.
[[File:Circuit complet d'un additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Circuit complet d'un additionneur à anticipation de retenue.]]
Pour rappel, la retenue de la colonne i, notée <math>C_i</math> est égale à :
: <math>G_i + (P_i . C_{i-1})</math>, avec G le signal de génération de retenue, P le signal de propagation de retenue.
Si on utilisait cette formule sans trop réfléchir, on retomberait sur un additionneur à propagation de retenue inutilement compliqué. L'astuce des additionneurs à anticipation de retenue consiste à remplacer le terme Ci−1 par sa valeur calculée avant. Par exemple, je prends un additionneur 4 bits. Je dispose de deux nombres A et B, contenant chacun 4 bits : A3, A2, A1, et A0 pour le nombre A, et B3, B2, B1, et B0 pour le nombre B. Si j'effectue les remplacements, j'obtiens les formules suivantes :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Ces formules nous permettent de déduire la valeur d'une retenue directement : il reste alors à créer un circuit qui implémente ces formules, et le tour est joué.
[[File:Four bit adder with carry lookahead.svg|centre|vignette|upright=1.5|Additionneur à anticipation de retenue de 4 bits.]]
Plus haut, j'ai dit que le circuit était composé de trois couches : une couche de demi-additionneur pour les signaux P et G ; un circuit d'anticipation de retenue, puis l'addition finale. Mais les deux bits d'opérandes sont déjà additionnés dans la couche de demi-additionneur, autant réutiliser le résultat pour l'additionner à la retenue. Pour éliminer toute redondance de ce type, il est possible d'utiliser des additionneurs partiels (des additionneurs où la sortie de retenue a été remplacée par deux sorties P et G). L'additionneur final est alors composé de plusieurs additionneurs complets de ce type, couplé à un circuit d'anticipation de retenue.
[[File:Cla4bits.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue de 4 bits.]]
Les additionneurs à anticipation de retenue sont plus rapides que les additionneurs à propagation de retenue. La raison est que les retenues sont calculées rapidement, dans un temps plus petit qu'en propageant la retenue. Pour rentrer dans les détails mathématiques, le temps de calcul n'est pas proportionnel au nombre de bits, mais proportionnel au logarithme de ce nombre de bits. Par contre, le gain en temps de calcul s'accompagne d'un cout en portes logiques conséquent. Le circuit qui calcule les retenues anticipées utilise beaucoup de portes logiques.
===Les additionneurs à calcul parallèle de préfixe===
Du fait du cout en portes logiques, utiliser un additionneur à anticipation de retenue est une solution pratique pour additionner 4 ou 8 bits. Mais pour des opérandes plus grands, de 16 ou 32 bits, cela utiliserait trop de portes logiques. Une partie de ce cout en portes logiques est cependant lié à des redondances. Si on analyse deux retenues différentes, à des colonnes différentes. Les circuits qui calculent ces deux retenues seront différents, mais ils contiendront des sous-circuits identiques. Pour vous en rendre compte, reprenons les quatre équations obtenues précédemment et essayez de trouver des redondances :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Vous devriez trouver :
* le terme ''P0 · C0'' dans toutes les lignes ;
* les termes ''P1 · P0 · C0'' et ''P1 · P0'' dans les trois dernières lignes ;
* le terme ''P2 · P1 · P0 · C0'' ; ''P2 · P1 · G0'' et ''P2 · G1'' dans les deux dernières lignes.
Il existe plusieurs manières d'exploiter de telles redondances, exploitées dans les '''additionneurs à calcul parallèle de préfixe'''. L'optimisation apportée est de générer des signaux ''propagate'' et ''generate'' pour des groupes de 2, 3, 4, ..., N colonnes. Par exemple, il est possible de générer un signal ''P 0 vers 3'', qui indique si la retenue de la première colonne est propagée jusqu'à la 4ème colonne. Un autre exemple est un signal qui indique qu'une retenue a été générée entre les colonnes 4 à 7 génèrent une retenue ou non. En clair, les signaux P et G ont maintenant un intervalle, qui précise de quelle colonne vers quelle colonne se fait la propagation, ou entre quelles colonnes se fait la génération.
Il est possible de combiner les signaux P et G de deux groupes de colonne, s'ils sont contiguës (ils peuvent aussi se recouvrir). Par exemple, il est possible de calculer les bits P et G pour les colonnes 0 à 10, à partir des deux signaux P/G des colonnes 0-4 et 5-10. Pour cela, les équations sont assez simples. Si je prends deux groupes nommés A et B, avec A pour les colonnes de poids fort et B celles de poids faible, on a :
: <math>P_{sortie} = P_A . P_B</math>
: <math>G_{sortie} = G_A . P_A . G_B</math>
Pour les bits des colonnes 2, 4, 8, 16, 32, 64, et autres, le calcul est simple. L'idée est de grouper les colonnes par groupes de 2. Puis, on calcule chaque groupe de 4 bits à partir de deux groupes de 2. Puis les groupes de 8 bits sont calculés à partir de deux groupes de 4 bits, et ainsi de suite pour toute puissance de deux.
[[File:Squelette d'un additionneur à calcul parallèle de préfixe.jpg|centre|vignette|upright=2|Squelette d'un additionneur à calcul parallèle de préfixe]]
Les seules difficultés surviennent pour les colonnes dont le nombre n'est pas une puissance de deux. Il y a plusieurs manières pour subdiviser ces colonnes en intervalles plus petits et combiner le tout. Et elles donnent chacune des additionneurs différents. Ils portent les noms d'''additionneur de Ladner-Fisher'', d'''additionneur de Brent-Kung'', d'''additionneur de Kogge-Stone'', etc. Dans le cas général, l'additionneur de Kogge-Stone est un des plus rapide, mais aussi un des plus gourmand en circuits, alors que c'est l'inverse pour l'additionneur de Brent-Kung. Les deux additionneurs sont illustrés ci-dessous, pour des opérandes de 8 bits. Dans ceux-ci, chaque losange calculent les bits P et G, à partir des signaux P et G de deux groupes précédents.
{|
|[[File:Brent-kung-8-bit.png|vignette|Brent-kung-8-bit]]
|[[File:Kogge-stone-8-bit.png|vignette|upright=1.2|Kogge-stone-8-bit]]
|}
===Le calcul parallèle de la retenue===
L'anticipation de retenue est très rapide, mais utilise beaucoup de circuits, même en utilisant le calcul parallèle de préfixe. Les additionneurs à propagation de retenue font eux le compromis inverse. Mais il existe des intermédiaires, qui visent à obtenir un compromis entre performance et cout en portes logiques.
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Un de ces compromis découpe un additionneur de 16/32/64 bits en additionneurs de 4/5 bits, qui sont enchainés en utilisant la propagation de retenue. Par contre, ces petits additionneurs de 4/5 bits utilisent l'anticipation de retenue. Ces additionneurs simples seront nommés ''blocs'' dans ce qui suit, et l'un d'entre eux est illustré ci-contre. Vous remarquerez qu'un bloc a une retenue entrante et une retenue sortante. Par exemple, on peut utiliser 4 additionneurs à anticipation de retenue de 4 bits, et propager les retenues entre eux. Le premier bloc calcule une retenue, qui est propagée au second bloc. Le second bloc calcule lui aussi une retenue, qui est propagée au troisième bloc, etc.
[[File:4008 Functional Diagram.svg|vignette|upright=1|4008 Functional Diagram]]
Le gain en performance est significatif, mais il est possible de faire presque aussi rapide, tout en économisant beaucoup de circuits. L'idée est de simplifier les additionneurs 4 bits, afin de ne pas utiliser une anticipation de retenue complète. L'anticipation de retenue n'est utilisée que pour la retenue sortante, alors que les bits de somme sont calculées avec propagation de retenue. La retenue sortante est donc calculée en parallèle de l'addition. Chaque bloc contient, à côté d'un additionneur proprement dit, un circuit qui calcule la retenue sortante. Il existe de nombreuses manières de calculer la retenue sortante.
Le gain en portes logiques est assez significatif. En effet, on se passe de circuits d'anticipation de retenue pour les 4 bits de somme, on ne garde que celui pour la retenue sortante. Le fait d'utiliser la propagation de retenue pour calculer les 4 bits de somme est censé avoir un léger cout en performance, mais il se trouve que ce cout est très faible. En effet, le résultat n'est connu qu'une fois les retenues propagées jusqu'au dernier bloc. Et malgré l'anticipation des retenues, la propagation est assez lente. les additionneurs à propagation de retenue auront terminé leur travail bien avant.
Un exemple est celui de l'additionneur CMOS 4008, un additionneur de 4 bit. Il est composé en trois sections. Une première couche de demi-additionneurs calcule les signaux P et G utilisés à la fois pour l'anticipation de la retenue sortante, que pour calculer les bits de somme. Le circuit de calcul de la retenue sortante utilise l'anticipation de retenue. Le reste du circuit fait l'addition en propageant les retenues.
[[File:CMOS 4008, circuit découpé en sections.png|centre|vignette|upright=1.5|CMOS 4008, circuit découpé en sections]]
===Les additionneurs à anticipation de retenue multi-niveau===
Les additionneurs précédents mixent anticipation et propagation de retenue. Ceux que nous allons voir utilisent des blocs, mais utilisent l'anticipation de retenue entre les blocs. Les blocs utilisés peuvent être un additionneur à propagation de retenue, ou à anticipation de retenue, peu importe. Ce qui compte est que la retenue entrante de chaque bloc est calculée par anticipation de retenue.
Par exemple, il est possible de créer un additionneur 16 bits en utilisant 4 additionneurs/blocs de 4 bits, couplé à un circuit d’anticipation de retenue par bloc. Pour cela, les additionneurs de 4 bits doivent fournir deux sorties P et G. Les deux sorties indiquent que le bloc soit a généré une retenue, soit a propagé la retenue entrante. Par propagé, on veut dire que la retenue entrante du bloc s'est retrouvée sur la sortie de retenue du bloc. C'est le cas si les 4 colonnes propagent la retenue, ce qui signifie que :
: <math>P_\text{4 bits} = P_0 \cdot P_1 \cdot P_2 \cdot P_3</math>
Pour la génération, cela veut dire qu'une colonne a générée une retenue, qui a été propagée jusqu'à la sortie. En clair, pour chaque colonne, il faut que le G = 1, et que les signaux P des colonnes soient à 1. Pour les 4 colonnes, cela donne :
: <math>G_\text{4 bits} = G_3 + G_2 \cdot P_3 + G_1 \cdot P_3 \cdot P_2 + G_0 \cdot P_3 \cdot P_2 \cdot P_1</math>
[[File:Cla4bitsPG.png|centre|vignette|upright=2|Additionneur à anticipation de retenue de 4 bits.]]
Pour combiner 4 blocs de 4 bits, on devra donc combiner 4 bits P et G, avec un circuit d'anticipation de retenue.
[[File:16-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 16 bits.]]
Il est même possible d'aller plus loin et de combiner 4 additionneurs précédents pour obtenir un additionneur 64 bits.
[[File:64-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 64 bits.]]
Le résultat est un additionneur à plusieurs niveaux d’anticipation de retenue. Une anticipation de retenue dans un blocs de 4 bits (facultative), entre blocs de 4 bits, entre blocs de 16 bits. La même logique peut être utilisée avec des blocs de taille différente de 4, 16 et 64 bits.
==L'addition signée et la soustraction==
Après avoir vu l'addition, il est logique de passer à la soustraction, les deux opérations étant très proches. Si on sait câbler une addition entre entiers positifs, câbler une soustraction n'est pas très compliqué. De plus, la soustraction permet de faire des additions de nombres signés.
===Le soustracteur pour opérandes entiers===
Pour soustraire deux nombres entiers, on peut adapter l'algorithme de soustraction utilisé en décimal, celui que vous avez appris à l'école. Celui-ci ressemble fortement à l'algorithme d'addition : on soustrait les bits de même poids, et on propage éventuellement une retenue sur la colonne suivante. À la différence de l'addition, la retenue est soustraite, et non ajoutée. La table de soustraction nous dit quel est le résultat de la soustraction de deux bits. La voici :
* 0 - 0 = 0 ;
* 0 - 1 = 1 et une retenue ;
* 1 - 0 = 1 ;
* 1 - 1 = 0.
[[File:Algebra1 05 fig014.svg|centre|vignette|Soustraction en binaire, avec les retenues en rouge.]]
La table de soustraction peut servir de table de vérité pour construire un circuit qui soustrait deux bits. Celui-ci est appelé un '''demi-soustracteur'''. Il ressemble beaucoup à un demi-additionneur, les différences se résumant à une porte NON ajoutée pour le calcul de la retenue.
[[File:Half Subtractor Vektor.svg|centre|vignette|Demi-soustracteur.]]
Comme pour l'additionneur, deux demi-soustracteurs peuvent être combinés pour donner un '''soustracteur complet'''. Le calcul de la retenue se fait en combinant les deux retenues des demi-soustracteurs avec une porte OU. Les soustracteurs complets sont utilisés pour créer des soustracteurs à propagation de retenue ou tout autre circuit soustracteur, sur le même modèle que les additionneurs.
[[File:FullSubtractor.svg|centre|vignette|upright=2|Soustracteur complet.]]
Vous l'aurez sans doute remarqué, mais le demi-soustracteur ressemble beaucoup au demi-additionneur. La seule différence est une porte NON ajoutée au bon endroit dans le demi-soustracteur.
{|
|[[File:Half-adder.svg|vignette|Demi-additionneur]]
|[[File:Half Subtractor Vektor.svg|vignette|upright=1.3|Demi-soustracteur]]
|}
La conséquence est qu'il est possible de créer un circuit capable de faire à la fois des additions et des soustractions. L'idée est de remplacer la porte NON du demi-soustracteur par un inverseur commandable. Si on envoie un zéro sur l'entrée de commande, l'inverseur commandable se comportera comme une porte OUI. Le circuit sera alors identique à un demi-additionneur. Si on envoie un 1 sur l'entrée de commande, l'inverseur commandable se comportera comme une porte NON. le circuit sera alors un demi-soustracteur. Il faut faire cela pour chaque demi-soustracteur, ce qui donne ceci pour un soustracteur complet. le résultat est alors appelé un '''additionneur-soustracteur complet'''.
[[File:Additionneur-soustracteur complet.png|centre|vignette|upright=2|Additionneur-soustracteur complet]]
===L'additionneur-soustracteur pour opérandes codées en complément à deux===
Étudions le cas de la soustraction en complément à deux, dans l'objectif de créer un circuit soustracteur. Vous savez sûrement que a−b et a+(−b) sont deux expressions équivalentes. Et en complément à deux, − b = not(b) + 1. Dit autrement, a − b = a + not(b) + 1. On pourrait se dire qu'il faut deux additionneurs pour faire le calcul, mais la majorité des additionneurs possède une entrée de retenue pour incrémenter le résultat de l'addition. Un soustracteur en complément à deux est donc simplement composé d'un additionneur et d'un inverseur.
[[File:Additionneur soustracteur.png|centre|vignette|upright=2|Soustracteur en complément à deux.]]
Il est possible de créer un circuit capable d'effectuer soit une addition, soit une soustraction : il suffit de remplacer l'inverseur par un inverseur commandable, qui peut être désactivé. On a vu comment créer un tel inverseur commandable dans le chapitre sur les circuits combinatoires. On peut remarquer que l'entrée de retenue et l'entrée de commande de l'inverseur sont activées en même temps : on peut fusionner les deux signaux en un seul.
[[File:Additionneur-soustracteur en complément à deux.png|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux.]]
Une implémentation alternative est la suivante. Elle remplace l'inverseur commandable par un multiplexeur.
[[File:4-bit ripple carry adder-subtracter.svg|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux, version alternative.]]
===L'additionneur-soustracteur pour opérandes codées en signe-magnitude===
Passons maintenant aux nombres codés en signe-valeur absolue, les deux opérandes étant notées A et B. Suivant les signes des deux opérandes, on a quatre cas possibles : A + B, A − B (B négatif), −A + B (A négatif) et −A − B (A et B négatifs). Une astuce est que le circuit n'a besoin que de calculer A + B et A − B : il peut les inverser pour obtenir − A − B ou B − A. A + B et A − B peuvent se calculer avec un additionneur-soustracteur, reste à corriger le résultat. Il suffit de lui ajouter un inverseur commandable pour obtenir le circuit d'addition finale.
[[File:Additionneur en signe-valeur absolue.png|centre|vignette|upright=2|Additionneur en signe-valeur absolue.]]
Toute la difficulté tient dans le calcul du bit de signe du résultat, quand interviennent des soustractions. Autant l'addition de deux nombres de même signe (A + B et −A − B) ne pose aucun problème, autant les soustractions posent problème (A − B et −A + B). Suivant que <math>A<B</math> ou que <math>A>B</math>, le signe du résultat ne sera pas le même. Déterminer le signe du résultat se fait en regardant les bits de débordement d'entier, comme on le verra plus bas.
===L'additionneur-soustracteur pour opérandes codées en représentation par excès===
Passons maintenant aux nombres codés en représentation par excès. On pourrait croire que ces nombres s'additionnent comme des nombres non-signés, mais ce serait oublier la présence du biais, qui pose problème. Dans les cas de nombres signés gérés avec un biais, voyons ce que donne l'addition de deux nombres :
: <math>( n_1 + biais ) + ( n_2 + biais ) = n_1 + n_2 + 2 \times biais</math>
Or, le résultat correct serait :
: <math>n_1 + n_2 + biais</math>
En effectuant l'addition telle quelle, le biais est compté deux fois. On doit donc le soustraire après l'addition pour obtenir le résultat correct.
Même chose pour la soustraction qui donne ceci :
: <math>(n_1 + biais) - (n_2 + biais) = n_1 - n_2</math>
Or, le résultat correct serait :
: <math> ( n_1 - n_2 ) + biais </math>
Il faut rajouter le biais pour obtenir l'exposant correct.
On a donc besoin de deux additionneurs/soustracteurs : un pour additionner/soustraire les représentations binaires des opérandes, et un autre pour ajouter/retirer le biais en trop/manquant.
==L'additionneur BCD==
Maintenant, voyons un additionneur qui additionne deux entiers au format BCD. Pour cela, nous allons devoir passer par deux étapes. La première est de créer un circuit capable d'additionneur deux chiffres BCD. Ensuite, nous allons voir comment enchaîner ces circuits pour créer un additionneur BCD complet.
===L'additionneur BCD qui fait l'opération chiffre par chiffre===
Nous allons commencer par voir un additionneur qui additionne deux chiffres en BCD, une sorte d'équivalent BCD de l'additionneur complet. Il fournit un résultat sur 4 bits et une retenue qui est mise à 1 si le résultat dépasse 10 (la limite d'un chiffre BCD). Les deux opérandes sont des chiffres BCD codés sur 4 bits et sont additionnés en binaire par un additionneur des plus normaux, similaire à ceux vus plus haut. Le résultat est alors un entier codé en binaire, sur 5 bits, qu'on corrige/convertit pour obtenir un chiffre BCD et une retenue sortante.
Pour corriger le résultat, une idée intuitive serait de prendre le résultat et de faire une division par 10. Le quotient donne la retenue, alors que le reste est le résultat, le chiffre BCD. Mais un circuit diviseur par 10 utilise beaucoup de portes logiques, ce qui ne vaut pas le coup. Une autre méthode détecte si le résultat est égal ou supérieur à 10, ce qui correspond à un "débordement" (on dépasse les limites d'un chiffre BCD). Si le résultat est plus petit que 10, il n'y a rien à faire : le résultat est bon et la retenue est de zéro. Par contre, si le résultat vaut 10 ou plus, il faut corriger le résultat et générer une retenue à 1.
Il faut donc ajouter un circuit qui détecte si le résultat est supérieur à 9, qui calcule directement la retenue. Ce circuit peut se fabriquer simplement à partir de sa table de vérité, ou en utilisant les techniques que nous verrons dans un chapitre ultérieur sur les comparateurs. La solution la plus simple est clairement d'utiliser la table de vérité, ce qui est très simple, assez pour être laissé en exercice au lecteur. Pour comprendre comment corriger le résultat, établissons une table de vérité qui associe le résultat et le résultat corrigé. L'entrée vaut au minimum 10 et au maximum 9 + 9 = 18. On considère la sortie comme un tout, la retenue étant un 5ème bit, le bit de poids fort.
{|class="wikitable"
|-
! colspan="5" | Entrée
!
! rowspan="10" |
! Retenue
! Résultat corrigé (sans retenue)
! interprétation de la sortie en binaire (retenue inclue)
|-
| 0 || 1 || 0 || 1 || 0
| (10)
| 1 || 0000
| (16)
|-
| 0 || 1 || 0 || 1 || 1
| (11)
| 1 || 0001
| (17)
|-
| 0 || 1 || 1 || 0 || 0
| (12)
| 1 || 0010
| (18)
|-
| 0 || 1 || 1 || 0 || 1
| (13)
| 1 || 0011
| (19)
|-
| 0 || 1 || 1 || 1 || 0
| (14)
| 1 || 0100
| (20)
|-
| 0 || 1 || 1 || 1 || 1
| (15)
| 1 || 0101
| (21)
|-
| 1 || 0 || 0 || 0 || 0
| (16)
| 1 || 0110
| (22)
|-
| 1 || 0 || 0 || 0 || 1
| (17)
| 1 || 0111
| (23)
|-
| 1 || 0 || 0 || 1 || 0
| (18)
| 1 || 1000
| (24)
|}
En analysant le tableau, on voit que pour corriger le résultat, il suffit d'ajouter 6. La raison est que le résultat déborde d'un nibble à 16 en binaire, mais à 10 en décimal : il suffit d'ajouter la différence entre les deux, à savoir 6, et le débordement binaire fait son travail. Donc, la correction après une addition est très simple : si le résultat dépasse 9, on ajoute 6.
On peut maintenant implémenter l'additionneur BCD, en combinant le comparateur avec 10, le circuit de correction, et l'additionneur. La première solution calcule deux versions du résultat : la version corrigée, la version normale. Le choix entre les deux est réalisée par un multiplexeur, commandé par le comparateur.
[[File:Additionneur BCD.png|centre|vignette|upright=1.5|Additionneur BCD]]
L'autre solution utilise un circuit commandable qui soit additionne 6, soit ne fait rien. Le choix entre les deux est commandé par le bit calculé par le comparateur.
[[File:Additionneur BCD, seconde version.png|centre|vignette|upright=1.5|Additionneur BCD, seconde version.]]
Une version alternative du circuit précédent est la suivante. Il contient deux additionneurs : un pour additionner les deux chiffres BCD, un autre pour additionner 6 si besoin. Le résultat du comparateur est directement utilisé pour générer l'opérande du second additionneur : 0 ou 6. Le circuit est simple à concevoir, mais gaspille beaucoup de circuit. Idéalement, il vaudrait mieux utiliser un circuit combinatoire d'addition avec une constante.
[[File:Adder 4 Bit BCD.svg|centre|vignette|upright=2|Additionneur BCD, circuit complet.]]
Pour obtenir un additionneur BCD complet, il suffit d’enchaîner les additionneurs précédents, comme on le ferait avec les additionneurs complets dans un additionneur à propagation de retenue. Au final, l'additionneur BCD est beaucoup plus compliqué qu'un additionneur normal, car il rajoute un comparateur ">9", un petit additionneur pour ajouter 6 et éventuellement d'autres circuits. De plus, il est difficile d'appliquer les optimisations disponibles sur les additionneurs non-BCD. Notamment, les circuits d'anticipation de retenue sont totalement à refaire et le résultat est relativement compliqué. C'est ce qui explique pourquoi le BCD a progressivement été abandonné au profit du binaire simple.
La soustraction en BCD se fait comme en binaire : le nombre à soustraire est remplacé par son complément, le circuit additionne le complément et l'autre opérande, le débordement d'entier fait que le résultat marche. Sauf qu'ici, le complément est un complément à 9. Il se calcule chiffre par chiffre : chaque chiffre est remplacé par (9 - le chiffre en question).
===L'additionneur BCD par ajustement décimal===
L'additionneur BCD précédent effectuait son travail chiffre BCD par chiffre BCD, mais il existe des additionneurs BCD qui font autrement. Sur les premiers processeurs x86, il n'y avait pas d'opération d'addition BCD proprement dit, seulement une addition binaire normale de 8, 16 ou 32 bits. Par contre, elle était secondée par une opération dite d''''ajustement décimal''' qui transformait un nombre binaire en nombre codé en BCD. L'opération d'ajustement décimal prenait un opérande de 8 bits codé en binaire et fournissait un résultat de la même taille, c'est à dire deux chiffres BCD. Effectuer une addition BCD demandait donc de faire deux opérations à la suite : une addition binaire simple, suivie par l'opération d'ajustement décimal. Cela permettait de gérer des nombres entiers en binaire usuel et des entiers BCD sans avoir deux instructions d'addition séparées pour les deux, sans compter que cela simplifiait aussi les circuits d'addition.
L'ajustement décimal s'effectue en ajoutant une constante bien précise à l'opérande à convertir en BCD. L'idée est que la constante est découpée en morceaux de 4 bits, correspondant chacun à un chiffre BCD de l'opérande, chaque morceau contenant soit un 0, soit 6. Cela permet d'ajouter soit 0, soit 6, à chaque chiffre BCD, et donc de le corriger. La propagation des retenues d'un chiffre à l'autre est effectuée automatiquement par l'addition binaire de la constante. L'opération d'ajustement décimal calcule automatiquement la constante. Elle découpe l'opérande en ''nibbles'', vérifie si chaque ''nibble'' est supérieur ou égal à 10, puis détermine la valeur de chaque ''nibble'' de la constante finale. Par exemple, si je prends l'opérande 1001 1110, le ''nibble'' de poids faible déborde, alors que celui de poids fort non. La constante sera donc 0000 0110 : 0x06. Inversement, si le ''nibble'' de poids fort déborde et pas celui de poids faible, la constante sera alors 0x60. Et la constante est de 0x66 si les deux ''nibbles'' débordent, de 0x00 si aucun ne déborde.
Le circuit d’ajustement décimal est donc composé de trois étapes : deux étapes pour calculer la constante, et un circuit d'addition pour additionner cette constante au nombre de départ. La première étape découpe l'opérande en morceaux de 4 bits, en chiffres BCD, et vérifie si chacun d'entre eux vaut 10 ou plus. La seconde étape prend les résultats de la première étape, et les combine pour calculer la constante. Enfin, on trouve l'addition finale, qui était réalisée par un circuit d'addition utilisé à la fois pour l'ajustement décimal et l'addition binaire. La différence entre une addition normale et une opération d'ajustement décimal tient dans le fait que les deux premières étapes sont désactivées dans une addition normale.
[[File:Additionneur BCD parallèle.png|centre|vignette|upright=1.5|Additionneur BCD parallèle]]
===L'additionneur biquinaire===
Les entiers BCD ne sont qu'un des encodages hybrides entre décimal et binaire. L'encodage biquinaire est l'un d'entre eux et nous allons faire un rappel rapide à ce sujet. Pour simplifier, un chiffre encodé en biquinaire est composé de deux parties : un bit, couplé à une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits (0, 1, 2, 3 et 4). Le bit indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9.
Additionner deux nombres de biquinaire demande donc d'additionner deux parties quinaires encodées en ''one-hot'' et d'additionner deux bits. Mais attention : il faut tenir compte de la retenue de l'addition des parties quinaires. Et idéalement, il faut aussi tenir compte d'une retenue entrante, provenant de l'addition de la colonne de chiffres précédente. Toute la difficulté vient de la création de l'additionneur ''one-hot''. Heureusement, vu qu'il n'y a que 4-5 bits à additionner, il est souvent fabriqué à partir de sa table de vérité.
[[File:Additionneur bi-quinaire.png|centre|vignette|upright=2|Additionneur bi-quinaire]]
Un avantage du biquinaire est que le calcul du complément à 9 est très simple. Il faut pour cela : inverser la partie binaire avec une porte NON, puis inverser l'ordre des bits de la partie quinaire. Concrètement, le bit de poids faible devient le bit de poids fort, et ainsi de suite. Par exemple, une partie quinaire 01000 devient 00010, 10000 devient 00001, 00100 ne change pas, etc. Le tout peut se calculer avec une porte NON et 5 multiplexeurs.
===L'additionneur BCD avec calculs intermédiaires en biquinaire===
L'ordinateur IBM 1401, un ancien ''mainframe'' des années 60, utilisait un additionneur BCD un peu particulier. Les nombres étaient encodés en BCD dans la mémoire de l'ordinateur, mais les circuits de calcul utilisaient la représentation biquinaire. Lors d'un calcul, le processeur de l'ordinateur traduisait les chiffres BCD en représentation biquinaire, faisait une addition en biquinaire, avant de traduire le résultat en BCD normal.
Pour être précis, l'IBM 1401 utilisait une variante du biquinaire. L'encodage biquinaire de l'IBM 1401 est le suivant : la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre pair encodé par la partie quinaire. Si l'IBM 1401 utilisait cette variante du biquinaire, c'est car elle donnait des circuits de conversion BCD-biquinaire plus économes en portes logiques et plus rapides.
La partie binaire est le bit de poids faible du chiffre BCD, la partie biquinaire est calculée par un simple décodeur qui prend en entrée le chiffre BCD, amputé de son bit de poids faible. La traduction inverse demande d'utiliser un encodeur, à la place du décodeur. Par contre, le circuit d'addition biquinaire était plus compliqué du fait de la gestion des retenues. L'addition des parties binaires et quinaires se faisait en parallèle, dans deux additionneurs séparés. Cependant, l'addition des parties binaire fournit une retenue, qu'il faut prendre en compte. Pour cela, l'IBM 1401 disposait d'un troisième additionneur qui fournissait le résultat final, encodé en biquinaire.
[[File:Additionneur bi-quinaire de l'IBM 1401.png|centre|vignette|upright=2|Additionneur biquinaire de l'IBM 1401]]
Une implémentation moderne demanderait d'utiliser des portes ET combinées à des portes OU, le circuit pouvant être construit simplement à partir de sa table de vérité. Sur l'IBM 1401, le circuit était cependant différent, en raison de l'utilisation de OU câblés, des croisements de fils qui fonctionnent comme des portes OU, que nous n'avons pas encore vu pour le moment, mais qui seront détaillés dans quelques chapitres. Les OU câblés étaient utilisés pour simplifier le design du circuit, mais demandaient des portes logiques spécifiques, ce qui collait avec le fait que ce ''mainframe'' utilisait des transistors en Germanium. L'implémentation exacte est décrite dans cet article de blog, mais je ne recommande sa lecture qu'à ceux qui savent ce qu'est un OU câblé :
* [https://www.righto.com/2015/10/qui-binary-arithmetic-how-1960s-ibm.html Qui-binary arithmetic: how a 1960s IBM mainframe does math].
==Les débordements d'entier lors d'une addition/soustraction==
Les instructions arithmétiques manipulent des entiers codés sur un nombre fixe de bits, qui ne peuvent prendre leurs valeurs que dans un intervalle. Pour les nombres positifs, un ordinateur qui code ses entiers sur n bits pourra coder tous les entiers allant de 0 à <math>2^n -1</math>. Pour les nombres négatifs, l'intervalle est différent et dépend de la représentation utilisée. Dans le cas général, l'ordinateur peut coder les valeurs comprises de <math>N_\text{min}</math> à <math>N_\text{max}</math>. Si le résultat d'un calcul sort de cet intervalle, il ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math> , alors que la valeur basse vaut <math>N_\text{min} - 1</math> (avec <math>N_\text{max}</math> et <math>N_\text{min}</math> respectivement la plus grande et la plus petite valeur codable par l'ordinateur).
===La correction des débordements d'entier : l'arithmétique saturée===
Quand un débordement d'entier survient, tous les circuits de calcul ne procèdent pas de la même manière. Dans les grandes lignes, il y a deux réactions possibles : soit on corrige automatiquement le résultat du débordement, soit on ne fait rien et on se contente de détecter le débordement.
Si le débordement n'est pas corrigé automatiquement, le circuit ne conserve que les bits de poids faibles du résultat. Les bits en trop sont simplement ignorés. On dit qu'on utilise l''''arithmétique modulaire'''. Le problème avec ce genre d'arithmétique, c'est qu'une opération entre deux grands nombres peut donner un résultat très petit. Par exemple, si je dispose de registres 4 bits et que je souhaite faire l'addition 1111 + 0010 (ce qui donne 15 + 2), le résultat est censé être 10001 (17), ce qui est un résultat plus grand que la taille d'un registre. En conservant les 4 bits de poids faible, j’obtiens 0001 (1). En clair, un résultat très grand est transformé en un résultat très petit. Cela peut poser problèmes si on travaille uniquement avec des nombres positifs, mais c'est aussi utilisé pour coder des nombres en complément à deux.
D'autres circuits utilisent ce qu'on appelle l''''arithmétique saturée''' : si un calcul génère un débordement, on arrondi le résultat au plus grand entier supporté par le circuit. Les circuits capables de calculer en arithmétique saturée sont un peu plus complexes, vu qu'il faut rajouter des circuits pour corriger le résultat en cas de débordement. Il suffit généralement de rajouter un ''circuit de saturation'', qui prend en entrée le résultat et le corrige en cas de débordement. Ce circuit de saturation met la valeur maximale en sortie si un débordement survient, mais se contente de recopier le résultat du calcul sur sa sortie s'il n'y a pas de débordement. Typiquement, il est composé d'une couche de multiplexeurs, qui sélectionnent quelle valeur mettre sur la sortie : soit le résultat du calcul, soit le plus grand nombre entier géré par le processeur, soit le plus petit (pour les nombres négatifs/soustractions).
L'arithmétique saturée est utilisée pour les additions et soustractions, mais c'est plus rare pour les multiplications/divisions. Une des raisons est que le résultat d'une addition/soustraction prend un bit de plus que le résultat, là où les multiplications doublent le nombre de bits. Quand une addition déborde, le résultat réel est proche de la valeur maximale codable. mais quand une multiplication déborde, le résultat peut parfois valoir 200 à 60000 fois plus que la valeur maximale codable. Les calculs avec une valeur saturée/corrigée sont donc crédibles pour une suite d'additions, mais pas pour une suite de multiplications.
===La détection des débordements entiers===
Quand un débordement d'entier a eu lieu, il vaut mieux que l'additionneur prévienne ! Pour cela, l'additionneur a une '''sortie de débordement''', parfois nommée ''Overflow'', dont la valeur indique si l'addition a généré un débordement d'entier ou non. Reste que détecter un débordement ne se fait pas de la même manière selon que l'on parle d'un additionneur non-signé ou signé.
Pour les additionneur non-signés, l'additionneur calcule un bit de plus que ce qui est supporté par l'ordinateur. Par exemple, un additionneur 32 bits fournit un résultat sur 33 bits, un débordement d'entier a lieu quand le 33ème bit est à 1. Précisément, la sortie de débordement n'est autre que la retenue finale, celle fournie par le dernier additionneur complet. Le seul type de débordement possible est un débordement par le haut, où le résultat dépasse la valeur maximale. Avec l'arithmétique saturée, le circuit de saturation consiste en une seule couche de multiplexeurs, voire en un circuit de mise à la valeur maximale tel que vu dans le chapitre sur les opérations bits à bits.
[[File:Gestion des débordements d'entiers.png|centre|vignette|upright=2.5|Gestion des débordements d'entiers lors d'une addition non-signée.]]
Pour les additionneurs non-signés, la gestion des débordements d'entiers dépend fortement de la représentation signée. Nous allons étudier le cas du complément à deux. Si vous vous rappelez le chapitre 1, les calculs sur des nombres en complètement à deux utilisent les règles de l'arithmétique modulaire, c'est une condition nécessaire. À priori, on peut penser que dans ces conditions, les débordements d'entiers sont une chose parfaitement normale, qui nous permet d'avoir des résultats corrects. Néanmoins, certains débordements d'entiers peuvent survenir malgré tout et produire des bugs assez ennuyeux.
Si l'on tient en compte les règles du complément à deux, on sait que le bit de poids fort (le plus à gauche) permet de déterminer si le nombre est positif ou négatif : il indique le signe du nombre. Tout se passe comme si les entiers en complément à deux étaient codés sur un bit de moins, et avaient leur longueur amputé du bit de poids fort. Si le résultat d'un calcul écrase le bit de poids fort, il y a un débordement d'entiers. Il existe une règle simple qui permet de détecter ces débordements d'entiers. L'addition de deux nombres positifs ne peut pas être un nombre négatif. Si on additionne deux nombres dont le bit de signe est à 0 et que le bit de signe du résultat est à 1, on est en face d'un débordement d'entiers. Même chose pour deux nombres négatifs : le résultat de l'addition ne peut pas être positif. On peut résumer cela en une phrase : si deux nombres de même signe sont ajoutés, un débordement a lieu quand le bit du signe du résultat a le signe opposé.
Modifier les circuits d'au-dessus pour qu'ils détectent les débordements en complément à deux est simple comme bonjour : il suffit créer un petit circuit combinatoire qui prenne en entrée les bits de signe des opérandes et du résultat, et qui fasse le calcul de l'indicateur de débordements. Si l'on rédige sa table de vérité, on doit se retrouver avec la table suivante :
{|class="wikitable"
|-
!Entrées
!Sortie
|-
|000||0
|-
|001||1
|-
|010||0
|-
|011||0
|-
|100||0
|-
|101||0
|-
|110||1
|-
|111||0
|}
L'équation de ce circuit est la suivante, avec <math>S_a</math> et <math>S_b</math> les signes des deux opérandes, et <math>C_i</math> la retenue de la colonne précédente :
: <math> ( S_a . S_b . \overline{C_i} ) + ( \overline{S_a} . \overline{S_b} . C_i )</math>
En simplifiant, on obtient alors :
: <math>( S_a . S_b ) \oplus C_i</math>
Or, il se trouve que <math>S_a . S_b</math> est tout simplement la retenue en sortie du dernier additionneur, que nous noterons <math>C_o</math>. On trouve donc :
: <math>C_o \oplus C_i</math>
Il suffit donc de faire un XOR entre la dernière retenue et la précédente pour obtenir le bit de débordement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de décalage et de rotation
| prevText=Les circuits de décalage et de rotation
| next=Les circuits de comparaison
| nextText=Les circuits de comparaison
}}
</noinclude>
iihpnhrqy5x9kilz1fcblgage4f5jff
768672
768671
2026-06-25T20:08:49Z
Mewtow
31375
/* L'additionneur à saut de retenue */
768672
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir les circuits capables de faire une addition ou une soustraction, ainsi que quelques circuits spécialisés. Précisons cependant que les fabricants de processeurs travaillent d'arrache-pied pour trouver des moyens de rendre ces circuits de calcul plus rapides et plus économes en énergie. Autant vous dire que les circuits que vous allez voir sont vraiment des circuits qui font pâle figure comparé à ce que l'on peut trouver dans un vrai processeur commercial !
==Les circuits pour additionner 2 ou 3 bits==
L'addition se fait en binaire de la même manière qu'en décimal. On additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. La soustraction fonctionne sur le même principe, sur le même modèle qu'en décimal.
[[File:Binary Addition Demonstration.svg|centre|vignette|Exemple d'addition en binaire.]]
En clair, additionner deux nombres demande d'additionner 2 bits et une retenue sur chaque colonne, et de propager les retenues d'une colonne à l'autre. La propagation des retenues est quelque chose de simple en apparence, mais qui est sujet à des optimisations extraordinairement nombreuses. Aussi, pour simplifier l'exposition, nous allons voir comment gérer une colonne avant de voir comment sont propagées les retenues.
[[File:Full Adder Block.svg|vignette|upright=1|class=transparent|Additionneur complet.]]
Si on effectue une addition en colonne, on doit additionner les deux bits sur la colonne, mais aussi additionner une éventuelle retenue. Il faut donc créer un circuit qui additionne trois bits : deux bits de données, plus une retenue. Ce circuit qui additionne trois bits est appelé un '''additionneur complet'''. Il fournit en sortie deux bits : un bit de somme et une retenue sortante. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|-
||0||0||0|| ||0||0
|-
||0||0||1|| ||0||1
|-
||0||1||0|| ||0||1
|-
||0||1||1|| ||1||0
|-
||1||0||0|| ||0||1
|-
||1||0||1|| ||1||0
|-
||1||1||0|| ||1||0
|-
||1||1||1|| ||1||1
|}
Il est possible d'utiliser un tableau de Karnaugh pour traduire la table de vérité, mais elle donne un résultat assez compliqué. La retenue et le bit de somme sont calculés à part. La retenue est calculée avec trois portes ET et une porte OU. Le bit de somme est calculé lui avec 4 portes ET, une porte OU, et trois portes NON.
[[File:Full Adler with 3 entries.png|centre|vignette|upright=2|Additionneur complet, fabriqué à partir de la table de vérité.]]
D'autres méthodes donnent des résultats plus compréhensibles. Nous allons les voir dans la suite de cette section.
===L'additionneur complet basé sur des demi-additionneurs===
[[File:1-bit half-adder.svg|class=transparent|vignette|Demi-addtionneur.]]
Nous avons déjà vu comment additionner deux bits dans le chapitre sur les incrémenteurs, mais quelques rappels ne seront pas de trop. Le '''demi-additionneur''' est un circuit qui additionne deux bits. Il implémente la table d'addition, qui est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Mais un demi-additionneur n'additionne que deux bits, il manque de quoi additionner la retenue. Une solution pour cela est d'enchaîner deux demi-additionneurs : un qui additionne les deux bits de données, et un second qui additionne la retenue au résultat du précédent. La retenue finale se calcule en combinant les sorties de retenue des deux demi-additionneurs, avec une porte OU. Le résultat est un additionneur complet fabriqué avec deux demi-additionneurs. Pour vous en convaincre, établissez la table de vérité de ce circuit, vous verrez que ça marche.
[[File:Full Adder Blocks.svg|centre|vignette|upright=2|Additionneur complet fabriqué avec deux demi-additionneurs.]]
[[File:Half-adder.svg|vignette|upright=1|Circuit d'un demi-additionneur naïf.]]
L'implémentation naïve d'un demi-additionneur utilise une porte XOR et une porte ET, comme illustré ci-contre. Le circuit s'obtient en faisant uen traduction littérale de la table de vérité du circuit. Si je dis qu'elle est naïve, c'est car il est possible de l'optimiser, de manière à éliminer des redondances cachées. Mais nous avions déjà vu cela dans le chapitre sur les incrémenteurs, aussi laissons cela pour plus tard.
En combinant deux demi-additionneurs de ce type, on obtient l'additionneur complet suivant. Les deux sorties de retenue des demi-additionneurs sont combinées avec une porte OU, pour calculer la retenue finale.
{| class="flexible"
|[[File:Full Adder Modules.svg|class=transparent|Composition d'un additionneur complet. On voit bien que celui-ci est composé de deux demi-additionneurs, en rouge et en bleu, auxquels on a ajouté une porte OU pour calculer la retenue finale.]]
|[[File:Full-adder.svg|300px|class=transparent|Circuit d'un additionneur complet.]]
|}
Le fait de combiner les deux retenues avec une porte OU n'est pas ce qu'il y a de plus intuitif. La table de vérité nous dit que ça fonctionne, mais on comprend mal pourquoi. Mais tout devient plus clair quand on sait que les deux retenues ne peuvent pas être à 1 en même temps. Même dans l'addition 1 + 1 + 1, seule une retenue est à 1. Soit l'addition des deux bits d'opérande donne naissance à une retenue, soit c'est l'addition ''résultat + retenue entrante''. La retenue sortant est donc à 1 quand une des deux addition donne une retenue. D'où l'usage d'une porte OU. Remarquez qu'une porte XOR donne le même résultat, vu que le cas où les deux retenues sont à 1 n'est jamais rencontré.
===La propagation et la génération des retenues===
L'additionneur complet que nous allons voir dans cette section sert à introduire les concepts de propagation, génération et absorption de retenue. Il s'agit de concepts très importants quand on étudie les additionneurs. l'idée part d'un principe très simple : la retenue sortante dépend de la retenue d'entrée. La relation entre les deux se résume à trois cas, qui dépendent de la valeur des deux bits additionnés, nommés A et B.
* Dans le premier cas, la retenue entrante est égale à la retenue sortante. On dit que la retenue entrante est propagée par l'additionneur.
* Dans le second cas, la retenue est forcée à 0 : la retenue sortante vaut 0, peu importe la valeur de la retenue entrante.
* Dans le troisième cas, la retenue est forcée à 1 : la retenue entrante vaut 1, peu importe la valeur de la retenue entrante.
[[File:Signaux P et G fournis par un demi-additionneur.png|vignette|Signaux P et G fournis par un demi-additionneur]]
Maintenant, créons un circuit qui nous dise si une retenue est propagée ou générée. Il a deux bits de sortie, nommés P et G : P pour ''Propagate'', G pour ''Generate''. Le bit P est à 1 si la retenue entrante est propagée, il est à 0 sinon. Le bit G est à 1 si une retenue est générée, à 0 sinon. Une retenue est considérée comme absorbée si elle n'est pas ni propagée ni générée, pas besoin d'un troisième bit pour gérer ce cas.
Il se trouve que ce circuit n'est autre qu'un demi-additionneur ! Pour vous en rendre compte, regardez la table de vérité d'un additionneur complet, illustrée ci-dessous. Lorsque les deux bits d'opérande sont à 0, la retenue sortante vaut toujours 0. Si ils sont tous deux à 1, alors la retenue sortante vaut 1. S'ils sont différents, alors retenues sortante et entrante sont égales. Le bit P est donc généré par une simple porte XOR. Quant au bit G, il est à 1 si les deux bits d'opérandes sont à 1, ce qui correspond à une porte ET. Il se trouve que ces deux portes forment un demi-additionneur !
{|class="wikitable"
|-
! Opérande 1 !! Opérande 2 !! Retenue entrante !! !! Retenue sortante
|- class="f_rouge"
||0||0||0|| ||0
|- class="f_rouge"
||0||0||1|| ||0
|- class="f_bleu"
||0||1||0|| ||0
|- class="f_bleu"
||0||1||1|| ||1
|- class="f_bleu"
||1||0||0|| ||0
|- class="f_bleu"
||1||0||1|| ||1
|- class="f_vert"
||1||1||0|| ||1
|- class="f_vert"
||1||1||1|| ||1
|}
Ensuite, créons un circuit qui prend ces deux sorties P et G, et calcule la retenue sortante en fonction. Le circuit en question a trois entrées : la retenue entrante, les deux bits P et G. La retenue sortante vaut 1 soit si une retenue est générée, soit si la retenue entrante est propagée et qu'elle vaut 1. En notant <math>{C_{in}}</math> et <math>{C_{out}}</math> les retenues entrantes et sortante, on a :
: <math>{C_{out}} = G + \left( P . C_{in} \right)</math>
Le circuit est donc composé d'une porte OU et d'une porte ET. En combinant un demi-additionneur avec le circuit de calcul de retenue sortante vu plus haut, on a :
[[File:Additionneur complet avec propagation et génération de retenue.png|class=transparent|centre|vignette|upright=2|Additionneur complet avec propagation et génération de retenue.]]
Certes, on retombe sur le circuit vu plus haut. Mais il est possible de remplacer le circuit en orange par un autre. Par exemple, il est possible de le remplacer par multiplexeur, qui choisit entre la retenue générée et la retenue entrante (propagée). Le choix se fait selon la valeur du bit P, qui chosiit entre propager la retenue et la générer.
[[File:Additionneur crée avec un multiplexeur.png|centre|vignette|upright=1.5|Additionneur crée avec un multiplexeur]]
Le circuit exact est illustré ci-dessous. Il semble utiliser plus de portes logiques que l'additionneur complet précédent. Cependant, nous verrons dans quelques chapitres qu'il est possible d'implémenter un multiplexeur avec seulement 6 transistors, voire moins ! L'implémentation utilise des portes à transmission, mais nous en reparlerons dans le chapitre sur les transistors, quand nous verrons les additionneurs à ''Manchester Carry Chain''. Au passage, une variante de ce circuit a été utilisée dans le processeur processeur 8086 d'Intel, comme on le verra dans le chapitre suivant.
[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
[[File:FulladderPG.png|vignette|upright=1|Additionneur complet avec deux sorties P et G.]]
Pour finir, sachez qu'il existe des additionneurs qui fournissent : le bit de résultat, deux sorties P et G qui indiquent si l'addition propage ou génère une retenue. Il ne s'agit pas d'additionneurs complets, car il manque de quoi calculer la retenue sortante. De tels additionneurs seront appelés des '''additionneurs partiels'''. De tels additionneurs sont utilisés dans certains additionneurs pour gagner en performance.
Enfin, il existe aussi ce que j'ai décidé d'appeler des '''additionneurs P/G''', qui sont des additionneurs complets auxquels on a ajouté deux sorties P et G, en plus de la sortie de retenue. Leur circuit est le même que celui d'un additionneur complet, auquel on a ajouté deux fils. Vu que ces deux sorties sont fournies par le premier demi-additionneur, ajouter ces deux sorties demande d'ajouter des fils, pas de portes logiques. De tels additionneurs sont aussi utilisés dans certains additionneurs pour gagner en performance.
===L'additionneur complet basé sur une porte à majorité===
Maintenant, voyons une dernière implémentation possible de l'additionneur complet. Mais avant de voir comment l'additionneur est implémenté, nous allons devoir faire un petit focus sur la retenue sortante. Reprenons la table de vérité, et regardons ce qu'il en est pour la retenue sortante uniquement.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante
|-
||0||0||0|| ||0
|-
||0||0||1|| ||0
|-
||0||1||0|| ||0
|-
||0||1||1|| ||1
|-
||1||0||0|| ||0
|-
||1||0||1|| ||1
|-
||1||1||0|| ||1
|-
||1||1||1|| ||1
|}
Vous remarquerez que la retenue sortante est égale au bit majoritaire, parmi les trois bits d'opérande. Si deux ou trois bits sont à 1, la retenue sortante vaut 1. Et réciproquement avec 0. Le circuit de calcul de la retenue peut donc être remplacé par une '''porte à majorité'''.
[[File:Additionneur crée avec une porte à majorité.png|centre|vignette|upright=2|Additionneur crée avec une porte à majorité]]
Mais une porte à majorité est assez gourmande en circuit, ce qui fait qu'un additionneur pareil ne serait pas pratique, ni utile. Du moins, ce serait le cas s'il n'y avait pas une possibilité d'optimisation extrêmement intéressante. Il est possible de calculer le 'bit de somme' à partir de la retenue sortante ! En effet, le bit de somme est l'inverse de la retenue sortante, sauf dans deux cas : les trois bits d'entrée sont à 0, où ils sont tous à 1. Le bit de somme vaut 0 dans le premier cas, 1 dans le second cas.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|- class="f_bleu"
||0||0||0|| ||0||0
|- class="f_vert"
||0||0||1|| ||0||1
|- class="f_vert"
||0||1||0|| ||0||1
|- class="f_vert"
||0||1||1|| ||1||0
|- class="f_vert"
||1||0||0|| ||0||1
|- class="f_vert"
||1||0||1|| ||1||0
|- class="f_vert"
||1||1||0|| ||1||0
|- class="f_rouge"
||1||1||1|| ||1||1
|}
En clair, 6 lignes de la table de vérité sur 8 peuvent se calculer avec une porte à majorité, le reste demandant quelques portes logiques pour faire la correction. L'implémentation en circuit demande donc trois choses :
* de calculer la retenue sortante et de quoi l'inverser ;
* un circuit qui vérifie si tous les bits opérande valent 0 : une porte NOR ou OU fait l'affaire ;
* un circuit qui vérifie s'ils valent tous 1, à savoir une porte ET.
Ensuite, on combine le résultat des trois circuits précédents pour obtenir le résultat final. La combinaison est le fait du circuit en jaune dans le schéma ci-dessous.
[[File:Full adder basé sur une modification de la retenue.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue]]
Le circuit en jaune est assez simple. Quand la porte ET sort un 1, la sortie doit être forcée à 1. Par contre, quand la porte OU sort un zéro, la sortie doit être forcée à 0. Il suffit donc d'enchainer un circuit de mise à 1 et un circuit de mise à 0. Nous avons vu dans le chapitre sur les opérations de masquage qu'il s'agit respectivement d'une porte OU et d'une porte ET.
[[File:Full adder basé sur une modification de la retenue interne, au complet.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue interne, au complet]]
Divers processeurs utilisaient des additionneurs complets de ce type : le fameux Z80, l'Intel 4004 et de l'Intel 8008 sont les exemples les plus notables. Pourtant, on pourrait s'étonner qu'un tel circuit ait existé. Il utilise beaucoup de portes logiques, a une profondeur logique supérieure : il n'a rien d'avantageux. Sauf qu'il était utilisé sur d'anciens processeurs, qui utilisaient la technologie dite TTL, différente de la technologie CMOS des transistors modernes. Et avec la technologie TTL, il est possible de fusionner plusieurs portes logiques ET et NOR en une seule porte logique ET/OU/NON ! Un additionneur complet construit ainsi ne prenait que deux portes logiques : une pour le calcul de la retenue sortante, une autre pour le reste du circuit.
==L'addition non signée==
Voyons maintenant un circuit capable d'additionner deux nombres entiers: l''''additionneur'''. Dans la version qu'on va voir, ce circuit manipulera des nombres strictement positifs. L'addition des nombres codés en complètement à deux sera vu dans une section ultérieure.
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
L'interface d'un additionneur est illustrée ci-contre. Un additionneur prend deux opérandes sur deux entrées séparées, et fournit le résultat sur une sortie dédiée. De plus, il a une entrée sur laquelle envoyer une retenue entrante, et a une sortie pour la retenue sortante. La sortie de retenue indique que le résultat de l'addition a débordé (au sens d'un débordement d'entier). Pour l'entrée de retenue, son utilité deviendra plus claire dans ce qui suit, mais souvenez-vous qu'elle existe.
===L'additionneur série===
Il est possible d'additionner deux nombres bit par bit, grâce à un additionneur complet associé à plusieurs registres à décalages. Les opérandes sont placées chacune dans un registre à décalage, et l'additionneur complet utilise les bits sortants de ces registres à décalage. Le bit du résultat est envoyé au registre à décalage pour le résultat. La retenue de l'addition est stockée dans une bascule de 1 bit, en attente du prochain cycle d'horloge. Un tel additionneur est appelé un '''additionneur série'''. Il a été utilisé sur d'anciens ordinateurs dans les années 50-60, aussi bien des prototypes que des ordinateurs commerciaux.
[[File:Additionneur série.jpg|centre|vignette|upright=2.5|Additionneur série.]]
===L'additionneur à propagation de retenue===
L''''additionneur à propagation de retenue''' est aussi appelé l'additionneur ''ripple carry'', c'est son nom anglais. J'utiliserais occasionnellement le nom anglais dans ce qui suit, car celui-ci est plus court. Ilutilise un additionneur complet pour chaque colonne de l'addition. Pour gérer les retenues, il suffit ainsi de câbler des additionneurs complets les uns à la suite des autres, chacun envoyant sa retenue à la colonne suivante.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Notons la présence de la retenue sortante, qui est utilisée pour détecter les débordements d'entier, ainsi que pour d'autres opérations. Le bit de retenue final est souvent stocké dans un registre spécial du processeur (généralement appelé ''carry flag'').
Notez aussi, à droite du schéma précédent, l'entrée de retenue <math>\text{R}_\text{Entrée}</math>. Elle est très utile pour l'implémentation de certaines opérations comme l'inversion de signe, la soustraction, l'incrémentation, etc. Certains processeurs sont capables de faire une opération appelée ADC, ADDC ou autre nom signifiant ''Addition with Carry'', qui permet de faire le calcul A + B + Retenue (la retenue en question est la retenue sortante de l'addition précédente, stockée dans le registre ''carry flag''). Son utilité principale est de permettre des additions d'entiers plus grands que ceux supportés par le processeur. Par exemple, cela permet de faire des additions d'entiers 32 bits sur un processeur 16 bits.
[[File:Adder Network 3 Bit Sum Delay 003.svg|vignette|upright=0.5|Propagation de retenue dans l'additionneur.]]
Cet additionneur utilise très peu de portes logiques, ce qui fait qu'il était utilisé sur certains processeurs 8 et 16 bits, dont le budget en portes logiques était limité. Bien que très simple, cet additionneur est cependant peu performant. Son temps de calcul est proportionnel à la taille des opérandes. Par exemple, additionner deux opérandes de 32 bits prendra deux fois plus de temps que pour des opérandes de 16 bits. La raison est que les retenues doivent se propager à travers le circuit, du premier additionneur jusqu'au dernier.
L'addition étant une opération fréquente, d'autres additionneurs ont été inventés. Ils utilisent des optimisations qui utilisent plus de circuits pour gagner en rapidité. Mais avant de voir ces optimisations, nous allons voir s'il est possible d'optimiser les additionneurs à propagation de retenue.
===L'additionneur à saut de retenue===
L'additionneur à propagation de retenue peut être optimisé de deux manières. La première accélère la propagation de la retenue, en agissant au niveau des additionneurs complets. La ''Manchester carry chain'' est une optimisation de ce type, mais on ne peut pas encore expliquer à ce stade du cours, car elle optimisait les additionneurs complets au niveau des transistors eux-mêmes. Elle a été utilisée sur de nombreux processeurs connus, comme le 8086 d'Intel ou le Z80.
La seconde manière rend l'addition plus rapide dans certains cas. Le résultat est un additionneur dont le temps de calcul est variable. Le calcul prendra quelques cycles d'horloges avec certains opérandes, mais d'autres opérandes ne verront aucune amélioration. Il n'améliore pas le pire des cas, dans lequel la retenue doit être propagée du début à la fin, du bit de poids faible au bit de poids fort. Mais dans d'autres cas, il permet d'avoir le résultat en avance. L'optimisation en question donne l''''additionneur à saut de retenue''' (''carry-skip adder'').
Il est composé en enchainant plusieurs additionneurs plus petits, qui additionnent 4/5 bits, rarement plus. De tels additionneurs de petite taille sont appelés des '''blocs''', dans ce qui suit. L'idée est de détecter précocement si la retenue entrante est propagée à travers tout le bloc. Si la retenue est propagée dans tous le bloc, on peut directement l'envoyer sur la sortie de retenue, la retenue saute le bloc entier. Dans le cas contraire, on doit calculer la retenue normalement. Le choix entre les deux est le fait d'un multiplexeur. Toute la difficulté est de savoir comment commander le multiplexeur.
[[File:Carry skip adder, principe de base.png|centre|vignette|upright=2.5|Carry skip adder : principe de base]]
Pour savoir si une retenue est propagée dans un bloc entier, il faut utiliser des additionneurs complets, qui ont en plus une sortie P indiquant que l'additionneur propage la retenue. Le bloc propage une retenue si chaque additionneur complet propage la retenue, donc s'ils ont tous leur sortie P à 1. Un simple ET logique suffit à déterminer si c'est le cas. Le signal de commande du multiplexeur est donc un vulgaire ET entre toutes les sorties P des additionneurs complets du bloc.
[[File:Additionneur à saut de retenue.png|centre|vignette|upright=2.5|Calcul de la commande du MUX.]]
Voici ce que ça donne pour un bloc de 4 bits :
[[File:CSAdder4Bit.svg|centre|vignette|upright=1.5|Additionneur ''carry skip'' : bloc de 4 bits.]]
L'additionneur à saut de retenue est construit en assemblant plusieurs blocs de ce type.
[[File:BCSAdder16Bit.svg|centre|vignette|upright=3|Additionneur à saut de retenue.]]
Les blocs sont tous identiques dans le cas le plus simple, mais il est possible d'utiliser des blocs de taille variable. Par exemple, le premier bloc peut avoir des opérandes de 6 bits, le second des opérandes de 7 bits, etc. Faire ainsi permet de gagner un petit peu en performances, si la taille de chaque bloc est bien choisie. La raison est une question de temps de propagation des retenues. La retenue met plus de temps à se propager à travers 8 blocs qu'à travers 4, ce qui prend plus de temps qu'à travers 2 blocs, etc. En tenir compte fait que la taille des blocs tend à augmenter ou diminuer quand on se rapproche des bits de poids fort.
===L'additionneur à sélection de retenue===
L''''additionneur à sélection de retenue''' utilise aussi des blocs, comme les additionneurs précédents. L'addition se fait en deux versions : une avec la retenue du bloc précédent valant zéro, et une autre version avec la retenue du bloc précédent valant 1. Il suffira alors de choisir le bon résultat avec un multiplexeur, une fois cette retenue connue. On gagne ainsi du temps en calculant à l'avance les valeurs de certains bits du résultat, sans connaître la valeur de la retenue. Petit détail : sur certains additionneurs à sélection de retenue, les blocs de base n'ont pas la même taille. Cela permet de tenir compte des temps de propagation des retenues entre les blocs.
[[File:Additionneur à sélection de retenue.png|centre|vignette|upright=1.5|Additionneur à sélection de retenue avec seulement deux blocs.]]
Dans les exemples du dessus, chaque sous-additionneur étaient des additionneurs à propagation de retenue. Mais ce n'est pas une obligation, et tout autre type d’additionneur peut être utilisé. Par exemple, on peut faire en sorte que les sous-additionneurs soient eux-mêmes des additionneurs à sélection de retenue, et poursuivre ainsi de suite, récursivement. On obtient alors un '''additionneur à somme conditionnelle''', plus rapide que l'additionneur à sélection de retenue, mais qui utilise beaucoup plus de portes logiques.
===Les additionneurs à anticipation de retenue===
Les '''additionneurs à anticipation de retenue''' calculent chaque retenue sans avoir à propager les retenues précédentes. Au lieu de calculer les retenues une par une, ils calculent toutes les retenues en parallèle, à partir des bits des opérandes. Une fois les retenues pré-calculées, il suffit de les additionner avec les deux bits adéquats, pour obtenir le résultat. Ces additionneurs sont composés de deux parties :
* un circuit qui pré-calcule les retenues, sans les propager, directement à partir des opérandes ;
* d'une couche d'additionneurs complets, qui additionnent chacun deux bits d'opérande et la retenue pré-calculée.
[[File:Additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Additionneur à anticipation de retenue.]]
Le calcul des retenues se fait en deux étapes. La première détermine si les retenues sont propagées ou générées, pour chaque colonne. Elle détermine, pour chaque colonne, les bits P et G qui indiquent respectivement : qu'une retenue a été générée sur cette colonne, que la colonne propage la retenue précédente. La seconde étape utilise ces signaux P et G pour déterminer la retenue sur chaque colonne. Le circuit de calcul des retenues est donc composé de deux parties : une couche de demi-additionneurs pour générer les signaux P et G, un circuit de calcul des retenues proprement dit.
[[File:Circuit complet d'un additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Circuit complet d'un additionneur à anticipation de retenue.]]
Pour rappel, la retenue de la colonne i, notée <math>C_i</math> est égale à :
: <math>G_i + (P_i . C_{i-1})</math>, avec G le signal de génération de retenue, P le signal de propagation de retenue.
Si on utilisait cette formule sans trop réfléchir, on retomberait sur un additionneur à propagation de retenue inutilement compliqué. L'astuce des additionneurs à anticipation de retenue consiste à remplacer le terme Ci−1 par sa valeur calculée avant. Par exemple, je prends un additionneur 4 bits. Je dispose de deux nombres A et B, contenant chacun 4 bits : A3, A2, A1, et A0 pour le nombre A, et B3, B2, B1, et B0 pour le nombre B. Si j'effectue les remplacements, j'obtiens les formules suivantes :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Ces formules nous permettent de déduire la valeur d'une retenue directement : il reste alors à créer un circuit qui implémente ces formules, et le tour est joué.
[[File:Four bit adder with carry lookahead.svg|centre|vignette|upright=1.5|Additionneur à anticipation de retenue de 4 bits.]]
Plus haut, j'ai dit que le circuit était composé de trois couches : une couche de demi-additionneur pour les signaux P et G ; un circuit d'anticipation de retenue, puis l'addition finale. Mais les deux bits d'opérandes sont déjà additionnés dans la couche de demi-additionneur, autant réutiliser le résultat pour l'additionner à la retenue. Pour éliminer toute redondance de ce type, il est possible d'utiliser des additionneurs partiels (des additionneurs où la sortie de retenue a été remplacée par deux sorties P et G). L'additionneur final est alors composé de plusieurs additionneurs complets de ce type, couplé à un circuit d'anticipation de retenue.
[[File:Cla4bits.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue de 4 bits.]]
Les additionneurs à anticipation de retenue sont plus rapides que les additionneurs à propagation de retenue. La raison est que les retenues sont calculées rapidement, dans un temps plus petit qu'en propageant la retenue. Pour rentrer dans les détails mathématiques, le temps de calcul n'est pas proportionnel au nombre de bits, mais proportionnel au logarithme de ce nombre de bits. Par contre, le gain en temps de calcul s'accompagne d'un cout en portes logiques conséquent. Le circuit qui calcule les retenues anticipées utilise beaucoup de portes logiques.
===Les additionneurs à calcul parallèle de préfixe===
Du fait du cout en portes logiques, utiliser un additionneur à anticipation de retenue est une solution pratique pour additionner 4 ou 8 bits. Mais pour des opérandes plus grands, de 16 ou 32 bits, cela utiliserait trop de portes logiques. Une partie de ce cout en portes logiques est cependant lié à des redondances. Si on analyse deux retenues différentes, à des colonnes différentes. Les circuits qui calculent ces deux retenues seront différents, mais ils contiendront des sous-circuits identiques. Pour vous en rendre compte, reprenons les quatre équations obtenues précédemment et essayez de trouver des redondances :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Vous devriez trouver :
* le terme ''P0 · C0'' dans toutes les lignes ;
* les termes ''P1 · P0 · C0'' et ''P1 · P0'' dans les trois dernières lignes ;
* le terme ''P2 · P1 · P0 · C0'' ; ''P2 · P1 · G0'' et ''P2 · G1'' dans les deux dernières lignes.
Il existe plusieurs manières d'exploiter de telles redondances, exploitées dans les '''additionneurs à calcul parallèle de préfixe'''. L'optimisation apportée est de générer des signaux ''propagate'' et ''generate'' pour des groupes de 2, 3, 4, ..., N colonnes. Par exemple, il est possible de générer un signal ''P 0 vers 3'', qui indique si la retenue de la première colonne est propagée jusqu'à la 4ème colonne. Un autre exemple est un signal qui indique qu'une retenue a été générée entre les colonnes 4 à 7 génèrent une retenue ou non. En clair, les signaux P et G ont maintenant un intervalle, qui précise de quelle colonne vers quelle colonne se fait la propagation, ou entre quelles colonnes se fait la génération.
Il est possible de combiner les signaux P et G de deux groupes de colonne, s'ils sont contiguës (ils peuvent aussi se recouvrir). Par exemple, il est possible de calculer les bits P et G pour les colonnes 0 à 10, à partir des deux signaux P/G des colonnes 0-4 et 5-10. Pour cela, les équations sont assez simples. Si je prends deux groupes nommés A et B, avec A pour les colonnes de poids fort et B celles de poids faible, on a :
: <math>P_{sortie} = P_A . P_B</math>
: <math>G_{sortie} = G_A . P_A . G_B</math>
Pour les bits des colonnes 2, 4, 8, 16, 32, 64, et autres, le calcul est simple. L'idée est de grouper les colonnes par groupes de 2. Puis, on calcule chaque groupe de 4 bits à partir de deux groupes de 2. Puis les groupes de 8 bits sont calculés à partir de deux groupes de 4 bits, et ainsi de suite pour toute puissance de deux.
[[File:Squelette d'un additionneur à calcul parallèle de préfixe.jpg|centre|vignette|upright=2|Squelette d'un additionneur à calcul parallèle de préfixe]]
Les seules difficultés surviennent pour les colonnes dont le nombre n'est pas une puissance de deux. Il y a plusieurs manières pour subdiviser ces colonnes en intervalles plus petits et combiner le tout. Et elles donnent chacune des additionneurs différents. Ils portent les noms d'''additionneur de Ladner-Fisher'', d'''additionneur de Brent-Kung'', d'''additionneur de Kogge-Stone'', etc. Dans le cas général, l'additionneur de Kogge-Stone est un des plus rapide, mais aussi un des plus gourmand en circuits, alors que c'est l'inverse pour l'additionneur de Brent-Kung. Les deux additionneurs sont illustrés ci-dessous, pour des opérandes de 8 bits. Dans ceux-ci, chaque losange calculent les bits P et G, à partir des signaux P et G de deux groupes précédents.
{|
|[[File:Brent-kung-8-bit.png|vignette|Brent-kung-8-bit]]
|[[File:Kogge-stone-8-bit.png|vignette|upright=1.2|Kogge-stone-8-bit]]
|}
===Le calcul parallèle de la retenue===
L'anticipation de retenue est très rapide, mais utilise beaucoup de circuits, même en utilisant le calcul parallèle de préfixe. Les additionneurs à propagation de retenue font eux le compromis inverse. Mais il existe des intermédiaires, qui visent à obtenir un compromis entre performance et cout en portes logiques.
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Un de ces compromis découpe un additionneur de 16/32/64 bits en additionneurs de 4/5 bits, qui sont enchainés en utilisant la propagation de retenue. Par contre, ces petits additionneurs de 4/5 bits utilisent l'anticipation de retenue. Ces additionneurs simples seront nommés ''blocs'' dans ce qui suit, et l'un d'entre eux est illustré ci-contre. Vous remarquerez qu'un bloc a une retenue entrante et une retenue sortante. Par exemple, on peut utiliser 4 additionneurs à anticipation de retenue de 4 bits, et propager les retenues entre eux. Le premier bloc calcule une retenue, qui est propagée au second bloc. Le second bloc calcule lui aussi une retenue, qui est propagée au troisième bloc, etc.
[[File:4008 Functional Diagram.svg|vignette|upright=1|4008 Functional Diagram]]
Le gain en performance est significatif, mais il est possible de faire presque aussi rapide, tout en économisant beaucoup de circuits. L'idée est de simplifier les additionneurs 4 bits, afin de ne pas utiliser une anticipation de retenue complète. L'anticipation de retenue n'est utilisée que pour la retenue sortante, alors que les bits de somme sont calculées avec propagation de retenue. La retenue sortante est donc calculée en parallèle de l'addition. Chaque bloc contient, à côté d'un additionneur proprement dit, un circuit qui calcule la retenue sortante. Il existe de nombreuses manières de calculer la retenue sortante.
Le gain en portes logiques est assez significatif. En effet, on se passe de circuits d'anticipation de retenue pour les 4 bits de somme, on ne garde que celui pour la retenue sortante. Le fait d'utiliser la propagation de retenue pour calculer les 4 bits de somme est censé avoir un léger cout en performance, mais il se trouve que ce cout est très faible. En effet, le résultat n'est connu qu'une fois les retenues propagées jusqu'au dernier bloc. Et malgré l'anticipation des retenues, la propagation est assez lente. les additionneurs à propagation de retenue auront terminé leur travail bien avant.
Un exemple est celui de l'additionneur CMOS 4008, un additionneur de 4 bit. Il est composé en trois sections. Une première couche de demi-additionneurs calcule les signaux P et G utilisés à la fois pour l'anticipation de la retenue sortante, que pour calculer les bits de somme. Le circuit de calcul de la retenue sortante utilise l'anticipation de retenue. Le reste du circuit fait l'addition en propageant les retenues.
[[File:CMOS 4008, circuit découpé en sections.png|centre|vignette|upright=1.5|CMOS 4008, circuit découpé en sections]]
===Les additionneurs à anticipation de retenue multi-niveau===
Les additionneurs précédents mixent anticipation et propagation de retenue. Ceux que nous allons voir utilisent des blocs, mais utilisent l'anticipation de retenue entre les blocs. Les blocs utilisés peuvent être un additionneur à propagation de retenue, ou à anticipation de retenue, peu importe. Ce qui compte est que la retenue entrante de chaque bloc est calculée par anticipation de retenue.
Par exemple, il est possible de créer un additionneur 16 bits en utilisant 4 additionneurs/blocs de 4 bits, couplé à un circuit d’anticipation de retenue par bloc. Pour cela, les additionneurs de 4 bits doivent fournir deux sorties P et G. Les deux sorties indiquent que le bloc soit a généré une retenue, soit a propagé la retenue entrante. Par propagé, on veut dire que la retenue entrante du bloc s'est retrouvée sur la sortie de retenue du bloc. C'est le cas si les 4 colonnes propagent la retenue, ce qui signifie que :
: <math>P_\text{4 bits} = P_0 \cdot P_1 \cdot P_2 \cdot P_3</math>
Pour la génération, cela veut dire qu'une colonne a générée une retenue, qui a été propagée jusqu'à la sortie. En clair, pour chaque colonne, il faut que le G = 1, et que les signaux P des colonnes soient à 1. Pour les 4 colonnes, cela donne :
: <math>G_\text{4 bits} = G_3 + G_2 \cdot P_3 + G_1 \cdot P_3 \cdot P_2 + G_0 \cdot P_3 \cdot P_2 \cdot P_1</math>
[[File:Cla4bitsPG.png|centre|vignette|upright=2|Additionneur à anticipation de retenue de 4 bits.]]
Pour combiner 4 blocs de 4 bits, on devra donc combiner 4 bits P et G, avec un circuit d'anticipation de retenue.
[[File:16-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 16 bits.]]
Il est même possible d'aller plus loin et de combiner 4 additionneurs précédents pour obtenir un additionneur 64 bits.
[[File:64-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 64 bits.]]
Le résultat est un additionneur à plusieurs niveaux d’anticipation de retenue. Une anticipation de retenue dans un blocs de 4 bits (facultative), entre blocs de 4 bits, entre blocs de 16 bits. La même logique peut être utilisée avec des blocs de taille différente de 4, 16 et 64 bits.
==L'addition signée et la soustraction==
Après avoir vu l'addition, il est logique de passer à la soustraction, les deux opérations étant très proches. Si on sait câbler une addition entre entiers positifs, câbler une soustraction n'est pas très compliqué. De plus, la soustraction permet de faire des additions de nombres signés.
===Le soustracteur pour opérandes entiers===
Pour soustraire deux nombres entiers, on peut adapter l'algorithme de soustraction utilisé en décimal, celui que vous avez appris à l'école. Celui-ci ressemble fortement à l'algorithme d'addition : on soustrait les bits de même poids, et on propage éventuellement une retenue sur la colonne suivante. À la différence de l'addition, la retenue est soustraite, et non ajoutée. La table de soustraction nous dit quel est le résultat de la soustraction de deux bits. La voici :
* 0 - 0 = 0 ;
* 0 - 1 = 1 et une retenue ;
* 1 - 0 = 1 ;
* 1 - 1 = 0.
[[File:Algebra1 05 fig014.svg|centre|vignette|Soustraction en binaire, avec les retenues en rouge.]]
La table de soustraction peut servir de table de vérité pour construire un circuit qui soustrait deux bits. Celui-ci est appelé un '''demi-soustracteur'''. Il ressemble beaucoup à un demi-additionneur, les différences se résumant à une porte NON ajoutée pour le calcul de la retenue.
[[File:Half Subtractor Vektor.svg|centre|vignette|Demi-soustracteur.]]
Comme pour l'additionneur, deux demi-soustracteurs peuvent être combinés pour donner un '''soustracteur complet'''. Le calcul de la retenue se fait en combinant les deux retenues des demi-soustracteurs avec une porte OU. Les soustracteurs complets sont utilisés pour créer des soustracteurs à propagation de retenue ou tout autre circuit soustracteur, sur le même modèle que les additionneurs.
[[File:FullSubtractor.svg|centre|vignette|upright=2|Soustracteur complet.]]
Vous l'aurez sans doute remarqué, mais le demi-soustracteur ressemble beaucoup au demi-additionneur. La seule différence est une porte NON ajoutée au bon endroit dans le demi-soustracteur.
{|
|[[File:Half-adder.svg|vignette|Demi-additionneur]]
|[[File:Half Subtractor Vektor.svg|vignette|upright=1.3|Demi-soustracteur]]
|}
La conséquence est qu'il est possible de créer un circuit capable de faire à la fois des additions et des soustractions. L'idée est de remplacer la porte NON du demi-soustracteur par un inverseur commandable. Si on envoie un zéro sur l'entrée de commande, l'inverseur commandable se comportera comme une porte OUI. Le circuit sera alors identique à un demi-additionneur. Si on envoie un 1 sur l'entrée de commande, l'inverseur commandable se comportera comme une porte NON. le circuit sera alors un demi-soustracteur. Il faut faire cela pour chaque demi-soustracteur, ce qui donne ceci pour un soustracteur complet. le résultat est alors appelé un '''additionneur-soustracteur complet'''.
[[File:Additionneur-soustracteur complet.png|centre|vignette|upright=2|Additionneur-soustracteur complet]]
===L'additionneur-soustracteur pour opérandes codées en complément à deux===
Étudions le cas de la soustraction en complément à deux, dans l'objectif de créer un circuit soustracteur. Vous savez sûrement que a−b et a+(−b) sont deux expressions équivalentes. Et en complément à deux, − b = not(b) + 1. Dit autrement, a − b = a + not(b) + 1. On pourrait se dire qu'il faut deux additionneurs pour faire le calcul, mais la majorité des additionneurs possède une entrée de retenue pour incrémenter le résultat de l'addition. Un soustracteur en complément à deux est donc simplement composé d'un additionneur et d'un inverseur.
[[File:Additionneur soustracteur.png|centre|vignette|upright=2|Soustracteur en complément à deux.]]
Il est possible de créer un circuit capable d'effectuer soit une addition, soit une soustraction : il suffit de remplacer l'inverseur par un inverseur commandable, qui peut être désactivé. On a vu comment créer un tel inverseur commandable dans le chapitre sur les circuits combinatoires. On peut remarquer que l'entrée de retenue et l'entrée de commande de l'inverseur sont activées en même temps : on peut fusionner les deux signaux en un seul.
[[File:Additionneur-soustracteur en complément à deux.png|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux.]]
Une implémentation alternative est la suivante. Elle remplace l'inverseur commandable par un multiplexeur.
[[File:4-bit ripple carry adder-subtracter.svg|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux, version alternative.]]
===L'additionneur-soustracteur pour opérandes codées en signe-magnitude===
Passons maintenant aux nombres codés en signe-valeur absolue, les deux opérandes étant notées A et B. Suivant les signes des deux opérandes, on a quatre cas possibles : A + B, A − B (B négatif), −A + B (A négatif) et −A − B (A et B négatifs). Une astuce est que le circuit n'a besoin que de calculer A + B et A − B : il peut les inverser pour obtenir − A − B ou B − A. A + B et A − B peuvent se calculer avec un additionneur-soustracteur, reste à corriger le résultat. Il suffit de lui ajouter un inverseur commandable pour obtenir le circuit d'addition finale.
[[File:Additionneur en signe-valeur absolue.png|centre|vignette|upright=2|Additionneur en signe-valeur absolue.]]
Toute la difficulté tient dans le calcul du bit de signe du résultat, quand interviennent des soustractions. Autant l'addition de deux nombres de même signe (A + B et −A − B) ne pose aucun problème, autant les soustractions posent problème (A − B et −A + B). Suivant que <math>A<B</math> ou que <math>A>B</math>, le signe du résultat ne sera pas le même. Déterminer le signe du résultat se fait en regardant les bits de débordement d'entier, comme on le verra plus bas.
===L'additionneur-soustracteur pour opérandes codées en représentation par excès===
Passons maintenant aux nombres codés en représentation par excès. On pourrait croire que ces nombres s'additionnent comme des nombres non-signés, mais ce serait oublier la présence du biais, qui pose problème. Dans les cas de nombres signés gérés avec un biais, voyons ce que donne l'addition de deux nombres :
: <math>( n_1 + biais ) + ( n_2 + biais ) = n_1 + n_2 + 2 \times biais</math>
Or, le résultat correct serait :
: <math>n_1 + n_2 + biais</math>
En effectuant l'addition telle quelle, le biais est compté deux fois. On doit donc le soustraire après l'addition pour obtenir le résultat correct.
Même chose pour la soustraction qui donne ceci :
: <math>(n_1 + biais) - (n_2 + biais) = n_1 - n_2</math>
Or, le résultat correct serait :
: <math> ( n_1 - n_2 ) + biais </math>
Il faut rajouter le biais pour obtenir l'exposant correct.
On a donc besoin de deux additionneurs/soustracteurs : un pour additionner/soustraire les représentations binaires des opérandes, et un autre pour ajouter/retirer le biais en trop/manquant.
==L'additionneur BCD==
Maintenant, voyons un additionneur qui additionne deux entiers au format BCD. Pour cela, nous allons devoir passer par deux étapes. La première est de créer un circuit capable d'additionneur deux chiffres BCD. Ensuite, nous allons voir comment enchaîner ces circuits pour créer un additionneur BCD complet.
===L'additionneur BCD qui fait l'opération chiffre par chiffre===
Nous allons commencer par voir un additionneur qui additionne deux chiffres en BCD, une sorte d'équivalent BCD de l'additionneur complet. Il fournit un résultat sur 4 bits et une retenue qui est mise à 1 si le résultat dépasse 10 (la limite d'un chiffre BCD). Les deux opérandes sont des chiffres BCD codés sur 4 bits et sont additionnés en binaire par un additionneur des plus normaux, similaire à ceux vus plus haut. Le résultat est alors un entier codé en binaire, sur 5 bits, qu'on corrige/convertit pour obtenir un chiffre BCD et une retenue sortante.
Pour corriger le résultat, une idée intuitive serait de prendre le résultat et de faire une division par 10. Le quotient donne la retenue, alors que le reste est le résultat, le chiffre BCD. Mais un circuit diviseur par 10 utilise beaucoup de portes logiques, ce qui ne vaut pas le coup. Une autre méthode détecte si le résultat est égal ou supérieur à 10, ce qui correspond à un "débordement" (on dépasse les limites d'un chiffre BCD). Si le résultat est plus petit que 10, il n'y a rien à faire : le résultat est bon et la retenue est de zéro. Par contre, si le résultat vaut 10 ou plus, il faut corriger le résultat et générer une retenue à 1.
Il faut donc ajouter un circuit qui détecte si le résultat est supérieur à 9, qui calcule directement la retenue. Ce circuit peut se fabriquer simplement à partir de sa table de vérité, ou en utilisant les techniques que nous verrons dans un chapitre ultérieur sur les comparateurs. La solution la plus simple est clairement d'utiliser la table de vérité, ce qui est très simple, assez pour être laissé en exercice au lecteur. Pour comprendre comment corriger le résultat, établissons une table de vérité qui associe le résultat et le résultat corrigé. L'entrée vaut au minimum 10 et au maximum 9 + 9 = 18. On considère la sortie comme un tout, la retenue étant un 5ème bit, le bit de poids fort.
{|class="wikitable"
|-
! colspan="5" | Entrée
!
! rowspan="10" |
! Retenue
! Résultat corrigé (sans retenue)
! interprétation de la sortie en binaire (retenue inclue)
|-
| 0 || 1 || 0 || 1 || 0
| (10)
| 1 || 0000
| (16)
|-
| 0 || 1 || 0 || 1 || 1
| (11)
| 1 || 0001
| (17)
|-
| 0 || 1 || 1 || 0 || 0
| (12)
| 1 || 0010
| (18)
|-
| 0 || 1 || 1 || 0 || 1
| (13)
| 1 || 0011
| (19)
|-
| 0 || 1 || 1 || 1 || 0
| (14)
| 1 || 0100
| (20)
|-
| 0 || 1 || 1 || 1 || 1
| (15)
| 1 || 0101
| (21)
|-
| 1 || 0 || 0 || 0 || 0
| (16)
| 1 || 0110
| (22)
|-
| 1 || 0 || 0 || 0 || 1
| (17)
| 1 || 0111
| (23)
|-
| 1 || 0 || 0 || 1 || 0
| (18)
| 1 || 1000
| (24)
|}
En analysant le tableau, on voit que pour corriger le résultat, il suffit d'ajouter 6. La raison est que le résultat déborde d'un nibble à 16 en binaire, mais à 10 en décimal : il suffit d'ajouter la différence entre les deux, à savoir 6, et le débordement binaire fait son travail. Donc, la correction après une addition est très simple : si le résultat dépasse 9, on ajoute 6.
On peut maintenant implémenter l'additionneur BCD, en combinant le comparateur avec 10, le circuit de correction, et l'additionneur. La première solution calcule deux versions du résultat : la version corrigée, la version normale. Le choix entre les deux est réalisée par un multiplexeur, commandé par le comparateur.
[[File:Additionneur BCD.png|centre|vignette|upright=1.5|Additionneur BCD]]
L'autre solution utilise un circuit commandable qui soit additionne 6, soit ne fait rien. Le choix entre les deux est commandé par le bit calculé par le comparateur.
[[File:Additionneur BCD, seconde version.png|centre|vignette|upright=1.5|Additionneur BCD, seconde version.]]
Une version alternative du circuit précédent est la suivante. Il contient deux additionneurs : un pour additionner les deux chiffres BCD, un autre pour additionner 6 si besoin. Le résultat du comparateur est directement utilisé pour générer l'opérande du second additionneur : 0 ou 6. Le circuit est simple à concevoir, mais gaspille beaucoup de circuit. Idéalement, il vaudrait mieux utiliser un circuit combinatoire d'addition avec une constante.
[[File:Adder 4 Bit BCD.svg|centre|vignette|upright=2|Additionneur BCD, circuit complet.]]
Pour obtenir un additionneur BCD complet, il suffit d’enchaîner les additionneurs précédents, comme on le ferait avec les additionneurs complets dans un additionneur à propagation de retenue. Au final, l'additionneur BCD est beaucoup plus compliqué qu'un additionneur normal, car il rajoute un comparateur ">9", un petit additionneur pour ajouter 6 et éventuellement d'autres circuits. De plus, il est difficile d'appliquer les optimisations disponibles sur les additionneurs non-BCD. Notamment, les circuits d'anticipation de retenue sont totalement à refaire et le résultat est relativement compliqué. C'est ce qui explique pourquoi le BCD a progressivement été abandonné au profit du binaire simple.
La soustraction en BCD se fait comme en binaire : le nombre à soustraire est remplacé par son complément, le circuit additionne le complément et l'autre opérande, le débordement d'entier fait que le résultat marche. Sauf qu'ici, le complément est un complément à 9. Il se calcule chiffre par chiffre : chaque chiffre est remplacé par (9 - le chiffre en question).
===L'additionneur BCD par ajustement décimal===
L'additionneur BCD précédent effectuait son travail chiffre BCD par chiffre BCD, mais il existe des additionneurs BCD qui font autrement. Sur les premiers processeurs x86, il n'y avait pas d'opération d'addition BCD proprement dit, seulement une addition binaire normale de 8, 16 ou 32 bits. Par contre, elle était secondée par une opération dite d''''ajustement décimal''' qui transformait un nombre binaire en nombre codé en BCD. L'opération d'ajustement décimal prenait un opérande de 8 bits codé en binaire et fournissait un résultat de la même taille, c'est à dire deux chiffres BCD. Effectuer une addition BCD demandait donc de faire deux opérations à la suite : une addition binaire simple, suivie par l'opération d'ajustement décimal. Cela permettait de gérer des nombres entiers en binaire usuel et des entiers BCD sans avoir deux instructions d'addition séparées pour les deux, sans compter que cela simplifiait aussi les circuits d'addition.
L'ajustement décimal s'effectue en ajoutant une constante bien précise à l'opérande à convertir en BCD. L'idée est que la constante est découpée en morceaux de 4 bits, correspondant chacun à un chiffre BCD de l'opérande, chaque morceau contenant soit un 0, soit 6. Cela permet d'ajouter soit 0, soit 6, à chaque chiffre BCD, et donc de le corriger. La propagation des retenues d'un chiffre à l'autre est effectuée automatiquement par l'addition binaire de la constante. L'opération d'ajustement décimal calcule automatiquement la constante. Elle découpe l'opérande en ''nibbles'', vérifie si chaque ''nibble'' est supérieur ou égal à 10, puis détermine la valeur de chaque ''nibble'' de la constante finale. Par exemple, si je prends l'opérande 1001 1110, le ''nibble'' de poids faible déborde, alors que celui de poids fort non. La constante sera donc 0000 0110 : 0x06. Inversement, si le ''nibble'' de poids fort déborde et pas celui de poids faible, la constante sera alors 0x60. Et la constante est de 0x66 si les deux ''nibbles'' débordent, de 0x00 si aucun ne déborde.
Le circuit d’ajustement décimal est donc composé de trois étapes : deux étapes pour calculer la constante, et un circuit d'addition pour additionner cette constante au nombre de départ. La première étape découpe l'opérande en morceaux de 4 bits, en chiffres BCD, et vérifie si chacun d'entre eux vaut 10 ou plus. La seconde étape prend les résultats de la première étape, et les combine pour calculer la constante. Enfin, on trouve l'addition finale, qui était réalisée par un circuit d'addition utilisé à la fois pour l'ajustement décimal et l'addition binaire. La différence entre une addition normale et une opération d'ajustement décimal tient dans le fait que les deux premières étapes sont désactivées dans une addition normale.
[[File:Additionneur BCD parallèle.png|centre|vignette|upright=1.5|Additionneur BCD parallèle]]
===L'additionneur biquinaire===
Les entiers BCD ne sont qu'un des encodages hybrides entre décimal et binaire. L'encodage biquinaire est l'un d'entre eux et nous allons faire un rappel rapide à ce sujet. Pour simplifier, un chiffre encodé en biquinaire est composé de deux parties : un bit, couplé à une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits (0, 1, 2, 3 et 4). Le bit indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9.
Additionner deux nombres de biquinaire demande donc d'additionner deux parties quinaires encodées en ''one-hot'' et d'additionner deux bits. Mais attention : il faut tenir compte de la retenue de l'addition des parties quinaires. Et idéalement, il faut aussi tenir compte d'une retenue entrante, provenant de l'addition de la colonne de chiffres précédente. Toute la difficulté vient de la création de l'additionneur ''one-hot''. Heureusement, vu qu'il n'y a que 4-5 bits à additionner, il est souvent fabriqué à partir de sa table de vérité.
[[File:Additionneur bi-quinaire.png|centre|vignette|upright=2|Additionneur bi-quinaire]]
Un avantage du biquinaire est que le calcul du complément à 9 est très simple. Il faut pour cela : inverser la partie binaire avec une porte NON, puis inverser l'ordre des bits de la partie quinaire. Concrètement, le bit de poids faible devient le bit de poids fort, et ainsi de suite. Par exemple, une partie quinaire 01000 devient 00010, 10000 devient 00001, 00100 ne change pas, etc. Le tout peut se calculer avec une porte NON et 5 multiplexeurs.
===L'additionneur BCD avec calculs intermédiaires en biquinaire===
L'ordinateur IBM 1401, un ancien ''mainframe'' des années 60, utilisait un additionneur BCD un peu particulier. Les nombres étaient encodés en BCD dans la mémoire de l'ordinateur, mais les circuits de calcul utilisaient la représentation biquinaire. Lors d'un calcul, le processeur de l'ordinateur traduisait les chiffres BCD en représentation biquinaire, faisait une addition en biquinaire, avant de traduire le résultat en BCD normal.
Pour être précis, l'IBM 1401 utilisait une variante du biquinaire. L'encodage biquinaire de l'IBM 1401 est le suivant : la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre pair encodé par la partie quinaire. Si l'IBM 1401 utilisait cette variante du biquinaire, c'est car elle donnait des circuits de conversion BCD-biquinaire plus économes en portes logiques et plus rapides.
La partie binaire est le bit de poids faible du chiffre BCD, la partie biquinaire est calculée par un simple décodeur qui prend en entrée le chiffre BCD, amputé de son bit de poids faible. La traduction inverse demande d'utiliser un encodeur, à la place du décodeur. Par contre, le circuit d'addition biquinaire était plus compliqué du fait de la gestion des retenues. L'addition des parties binaires et quinaires se faisait en parallèle, dans deux additionneurs séparés. Cependant, l'addition des parties binaire fournit une retenue, qu'il faut prendre en compte. Pour cela, l'IBM 1401 disposait d'un troisième additionneur qui fournissait le résultat final, encodé en biquinaire.
[[File:Additionneur bi-quinaire de l'IBM 1401.png|centre|vignette|upright=2|Additionneur biquinaire de l'IBM 1401]]
Une implémentation moderne demanderait d'utiliser des portes ET combinées à des portes OU, le circuit pouvant être construit simplement à partir de sa table de vérité. Sur l'IBM 1401, le circuit était cependant différent, en raison de l'utilisation de OU câblés, des croisements de fils qui fonctionnent comme des portes OU, que nous n'avons pas encore vu pour le moment, mais qui seront détaillés dans quelques chapitres. Les OU câblés étaient utilisés pour simplifier le design du circuit, mais demandaient des portes logiques spécifiques, ce qui collait avec le fait que ce ''mainframe'' utilisait des transistors en Germanium. L'implémentation exacte est décrite dans cet article de blog, mais je ne recommande sa lecture qu'à ceux qui savent ce qu'est un OU câblé :
* [https://www.righto.com/2015/10/qui-binary-arithmetic-how-1960s-ibm.html Qui-binary arithmetic: how a 1960s IBM mainframe does math].
==Les débordements d'entier lors d'une addition/soustraction==
Les instructions arithmétiques manipulent des entiers codés sur un nombre fixe de bits, qui ne peuvent prendre leurs valeurs que dans un intervalle. Pour les nombres positifs, un ordinateur qui code ses entiers sur n bits pourra coder tous les entiers allant de 0 à <math>2^n -1</math>. Pour les nombres négatifs, l'intervalle est différent et dépend de la représentation utilisée. Dans le cas général, l'ordinateur peut coder les valeurs comprises de <math>N_\text{min}</math> à <math>N_\text{max}</math>. Si le résultat d'un calcul sort de cet intervalle, il ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math> , alors que la valeur basse vaut <math>N_\text{min} - 1</math> (avec <math>N_\text{max}</math> et <math>N_\text{min}</math> respectivement la plus grande et la plus petite valeur codable par l'ordinateur).
===La correction des débordements d'entier : l'arithmétique saturée===
Quand un débordement d'entier survient, tous les circuits de calcul ne procèdent pas de la même manière. Dans les grandes lignes, il y a deux réactions possibles : soit on corrige automatiquement le résultat du débordement, soit on ne fait rien et on se contente de détecter le débordement.
Si le débordement n'est pas corrigé automatiquement, le circuit ne conserve que les bits de poids faibles du résultat. Les bits en trop sont simplement ignorés. On dit qu'on utilise l''''arithmétique modulaire'''. Le problème avec ce genre d'arithmétique, c'est qu'une opération entre deux grands nombres peut donner un résultat très petit. Par exemple, si je dispose de registres 4 bits et que je souhaite faire l'addition 1111 + 0010 (ce qui donne 15 + 2), le résultat est censé être 10001 (17), ce qui est un résultat plus grand que la taille d'un registre. En conservant les 4 bits de poids faible, j’obtiens 0001 (1). En clair, un résultat très grand est transformé en un résultat très petit. Cela peut poser problèmes si on travaille uniquement avec des nombres positifs, mais c'est aussi utilisé pour coder des nombres en complément à deux.
D'autres circuits utilisent ce qu'on appelle l''''arithmétique saturée''' : si un calcul génère un débordement, on arrondi le résultat au plus grand entier supporté par le circuit. Les circuits capables de calculer en arithmétique saturée sont un peu plus complexes, vu qu'il faut rajouter des circuits pour corriger le résultat en cas de débordement. Il suffit généralement de rajouter un ''circuit de saturation'', qui prend en entrée le résultat et le corrige en cas de débordement. Ce circuit de saturation met la valeur maximale en sortie si un débordement survient, mais se contente de recopier le résultat du calcul sur sa sortie s'il n'y a pas de débordement. Typiquement, il est composé d'une couche de multiplexeurs, qui sélectionnent quelle valeur mettre sur la sortie : soit le résultat du calcul, soit le plus grand nombre entier géré par le processeur, soit le plus petit (pour les nombres négatifs/soustractions).
L'arithmétique saturée est utilisée pour les additions et soustractions, mais c'est plus rare pour les multiplications/divisions. Une des raisons est que le résultat d'une addition/soustraction prend un bit de plus que le résultat, là où les multiplications doublent le nombre de bits. Quand une addition déborde, le résultat réel est proche de la valeur maximale codable. mais quand une multiplication déborde, le résultat peut parfois valoir 200 à 60000 fois plus que la valeur maximale codable. Les calculs avec une valeur saturée/corrigée sont donc crédibles pour une suite d'additions, mais pas pour une suite de multiplications.
===La détection des débordements entiers===
Quand un débordement d'entier a eu lieu, il vaut mieux que l'additionneur prévienne ! Pour cela, l'additionneur a une '''sortie de débordement''', parfois nommée ''Overflow'', dont la valeur indique si l'addition a généré un débordement d'entier ou non. Reste que détecter un débordement ne se fait pas de la même manière selon que l'on parle d'un additionneur non-signé ou signé.
Pour les additionneur non-signés, l'additionneur calcule un bit de plus que ce qui est supporté par l'ordinateur. Par exemple, un additionneur 32 bits fournit un résultat sur 33 bits, un débordement d'entier a lieu quand le 33ème bit est à 1. Précisément, la sortie de débordement n'est autre que la retenue finale, celle fournie par le dernier additionneur complet. Le seul type de débordement possible est un débordement par le haut, où le résultat dépasse la valeur maximale. Avec l'arithmétique saturée, le circuit de saturation consiste en une seule couche de multiplexeurs, voire en un circuit de mise à la valeur maximale tel que vu dans le chapitre sur les opérations bits à bits.
[[File:Gestion des débordements d'entiers.png|centre|vignette|upright=2.5|Gestion des débordements d'entiers lors d'une addition non-signée.]]
Pour les additionneurs non-signés, la gestion des débordements d'entiers dépend fortement de la représentation signée. Nous allons étudier le cas du complément à deux. Si vous vous rappelez le chapitre 1, les calculs sur des nombres en complètement à deux utilisent les règles de l'arithmétique modulaire, c'est une condition nécessaire. À priori, on peut penser que dans ces conditions, les débordements d'entiers sont une chose parfaitement normale, qui nous permet d'avoir des résultats corrects. Néanmoins, certains débordements d'entiers peuvent survenir malgré tout et produire des bugs assez ennuyeux.
Si l'on tient en compte les règles du complément à deux, on sait que le bit de poids fort (le plus à gauche) permet de déterminer si le nombre est positif ou négatif : il indique le signe du nombre. Tout se passe comme si les entiers en complément à deux étaient codés sur un bit de moins, et avaient leur longueur amputé du bit de poids fort. Si le résultat d'un calcul écrase le bit de poids fort, il y a un débordement d'entiers. Il existe une règle simple qui permet de détecter ces débordements d'entiers. L'addition de deux nombres positifs ne peut pas être un nombre négatif. Si on additionne deux nombres dont le bit de signe est à 0 et que le bit de signe du résultat est à 1, on est en face d'un débordement d'entiers. Même chose pour deux nombres négatifs : le résultat de l'addition ne peut pas être positif. On peut résumer cela en une phrase : si deux nombres de même signe sont ajoutés, un débordement a lieu quand le bit du signe du résultat a le signe opposé.
Modifier les circuits d'au-dessus pour qu'ils détectent les débordements en complément à deux est simple comme bonjour : il suffit créer un petit circuit combinatoire qui prenne en entrée les bits de signe des opérandes et du résultat, et qui fasse le calcul de l'indicateur de débordements. Si l'on rédige sa table de vérité, on doit se retrouver avec la table suivante :
{|class="wikitable"
|-
!Entrées
!Sortie
|-
|000||0
|-
|001||1
|-
|010||0
|-
|011||0
|-
|100||0
|-
|101||0
|-
|110||1
|-
|111||0
|}
L'équation de ce circuit est la suivante, avec <math>S_a</math> et <math>S_b</math> les signes des deux opérandes, et <math>C_i</math> la retenue de la colonne précédente :
: <math> ( S_a . S_b . \overline{C_i} ) + ( \overline{S_a} . \overline{S_b} . C_i )</math>
En simplifiant, on obtient alors :
: <math>( S_a . S_b ) \oplus C_i</math>
Or, il se trouve que <math>S_a . S_b</math> est tout simplement la retenue en sortie du dernier additionneur, que nous noterons <math>C_o</math>. On trouve donc :
: <math>C_o \oplus C_i</math>
Il suffit donc de faire un XOR entre la dernière retenue et la précédente pour obtenir le bit de débordement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de décalage et de rotation
| prevText=Les circuits de décalage et de rotation
| next=Les circuits de comparaison
| nextText=Les circuits de comparaison
}}
</noinclude>
fd1f8si0bfh0jr7rncmozuuo9u0z1bc
768673
768672
2026-06-25T20:09:23Z
Mewtow
31375
/* Le calcul parallèle de la retenue */
768673
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir les circuits capables de faire une addition ou une soustraction, ainsi que quelques circuits spécialisés. Précisons cependant que les fabricants de processeurs travaillent d'arrache-pied pour trouver des moyens de rendre ces circuits de calcul plus rapides et plus économes en énergie. Autant vous dire que les circuits que vous allez voir sont vraiment des circuits qui font pâle figure comparé à ce que l'on peut trouver dans un vrai processeur commercial !
==Les circuits pour additionner 2 ou 3 bits==
L'addition se fait en binaire de la même manière qu'en décimal. On additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. La soustraction fonctionne sur le même principe, sur le même modèle qu'en décimal.
[[File:Binary Addition Demonstration.svg|centre|vignette|Exemple d'addition en binaire.]]
En clair, additionner deux nombres demande d'additionner 2 bits et une retenue sur chaque colonne, et de propager les retenues d'une colonne à l'autre. La propagation des retenues est quelque chose de simple en apparence, mais qui est sujet à des optimisations extraordinairement nombreuses. Aussi, pour simplifier l'exposition, nous allons voir comment gérer une colonne avant de voir comment sont propagées les retenues.
[[File:Full Adder Block.svg|vignette|upright=1|class=transparent|Additionneur complet.]]
Si on effectue une addition en colonne, on doit additionner les deux bits sur la colonne, mais aussi additionner une éventuelle retenue. Il faut donc créer un circuit qui additionne trois bits : deux bits de données, plus une retenue. Ce circuit qui additionne trois bits est appelé un '''additionneur complet'''. Il fournit en sortie deux bits : un bit de somme et une retenue sortante. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|-
||0||0||0|| ||0||0
|-
||0||0||1|| ||0||1
|-
||0||1||0|| ||0||1
|-
||0||1||1|| ||1||0
|-
||1||0||0|| ||0||1
|-
||1||0||1|| ||1||0
|-
||1||1||0|| ||1||0
|-
||1||1||1|| ||1||1
|}
Il est possible d'utiliser un tableau de Karnaugh pour traduire la table de vérité, mais elle donne un résultat assez compliqué. La retenue et le bit de somme sont calculés à part. La retenue est calculée avec trois portes ET et une porte OU. Le bit de somme est calculé lui avec 4 portes ET, une porte OU, et trois portes NON.
[[File:Full Adler with 3 entries.png|centre|vignette|upright=2|Additionneur complet, fabriqué à partir de la table de vérité.]]
D'autres méthodes donnent des résultats plus compréhensibles. Nous allons les voir dans la suite de cette section.
===L'additionneur complet basé sur des demi-additionneurs===
[[File:1-bit half-adder.svg|class=transparent|vignette|Demi-addtionneur.]]
Nous avons déjà vu comment additionner deux bits dans le chapitre sur les incrémenteurs, mais quelques rappels ne seront pas de trop. Le '''demi-additionneur''' est un circuit qui additionne deux bits. Il implémente la table d'addition, qui est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Mais un demi-additionneur n'additionne que deux bits, il manque de quoi additionner la retenue. Une solution pour cela est d'enchaîner deux demi-additionneurs : un qui additionne les deux bits de données, et un second qui additionne la retenue au résultat du précédent. La retenue finale se calcule en combinant les sorties de retenue des deux demi-additionneurs, avec une porte OU. Le résultat est un additionneur complet fabriqué avec deux demi-additionneurs. Pour vous en convaincre, établissez la table de vérité de ce circuit, vous verrez que ça marche.
[[File:Full Adder Blocks.svg|centre|vignette|upright=2|Additionneur complet fabriqué avec deux demi-additionneurs.]]
[[File:Half-adder.svg|vignette|upright=1|Circuit d'un demi-additionneur naïf.]]
L'implémentation naïve d'un demi-additionneur utilise une porte XOR et une porte ET, comme illustré ci-contre. Le circuit s'obtient en faisant uen traduction littérale de la table de vérité du circuit. Si je dis qu'elle est naïve, c'est car il est possible de l'optimiser, de manière à éliminer des redondances cachées. Mais nous avions déjà vu cela dans le chapitre sur les incrémenteurs, aussi laissons cela pour plus tard.
En combinant deux demi-additionneurs de ce type, on obtient l'additionneur complet suivant. Les deux sorties de retenue des demi-additionneurs sont combinées avec une porte OU, pour calculer la retenue finale.
{| class="flexible"
|[[File:Full Adder Modules.svg|class=transparent|Composition d'un additionneur complet. On voit bien que celui-ci est composé de deux demi-additionneurs, en rouge et en bleu, auxquels on a ajouté une porte OU pour calculer la retenue finale.]]
|[[File:Full-adder.svg|300px|class=transparent|Circuit d'un additionneur complet.]]
|}
Le fait de combiner les deux retenues avec une porte OU n'est pas ce qu'il y a de plus intuitif. La table de vérité nous dit que ça fonctionne, mais on comprend mal pourquoi. Mais tout devient plus clair quand on sait que les deux retenues ne peuvent pas être à 1 en même temps. Même dans l'addition 1 + 1 + 1, seule une retenue est à 1. Soit l'addition des deux bits d'opérande donne naissance à une retenue, soit c'est l'addition ''résultat + retenue entrante''. La retenue sortant est donc à 1 quand une des deux addition donne une retenue. D'où l'usage d'une porte OU. Remarquez qu'une porte XOR donne le même résultat, vu que le cas où les deux retenues sont à 1 n'est jamais rencontré.
===La propagation et la génération des retenues===
L'additionneur complet que nous allons voir dans cette section sert à introduire les concepts de propagation, génération et absorption de retenue. Il s'agit de concepts très importants quand on étudie les additionneurs. l'idée part d'un principe très simple : la retenue sortante dépend de la retenue d'entrée. La relation entre les deux se résume à trois cas, qui dépendent de la valeur des deux bits additionnés, nommés A et B.
* Dans le premier cas, la retenue entrante est égale à la retenue sortante. On dit que la retenue entrante est propagée par l'additionneur.
* Dans le second cas, la retenue est forcée à 0 : la retenue sortante vaut 0, peu importe la valeur de la retenue entrante.
* Dans le troisième cas, la retenue est forcée à 1 : la retenue entrante vaut 1, peu importe la valeur de la retenue entrante.
[[File:Signaux P et G fournis par un demi-additionneur.png|vignette|Signaux P et G fournis par un demi-additionneur]]
Maintenant, créons un circuit qui nous dise si une retenue est propagée ou générée. Il a deux bits de sortie, nommés P et G : P pour ''Propagate'', G pour ''Generate''. Le bit P est à 1 si la retenue entrante est propagée, il est à 0 sinon. Le bit G est à 1 si une retenue est générée, à 0 sinon. Une retenue est considérée comme absorbée si elle n'est pas ni propagée ni générée, pas besoin d'un troisième bit pour gérer ce cas.
Il se trouve que ce circuit n'est autre qu'un demi-additionneur ! Pour vous en rendre compte, regardez la table de vérité d'un additionneur complet, illustrée ci-dessous. Lorsque les deux bits d'opérande sont à 0, la retenue sortante vaut toujours 0. Si ils sont tous deux à 1, alors la retenue sortante vaut 1. S'ils sont différents, alors retenues sortante et entrante sont égales. Le bit P est donc généré par une simple porte XOR. Quant au bit G, il est à 1 si les deux bits d'opérandes sont à 1, ce qui correspond à une porte ET. Il se trouve que ces deux portes forment un demi-additionneur !
{|class="wikitable"
|-
! Opérande 1 !! Opérande 2 !! Retenue entrante !! !! Retenue sortante
|- class="f_rouge"
||0||0||0|| ||0
|- class="f_rouge"
||0||0||1|| ||0
|- class="f_bleu"
||0||1||0|| ||0
|- class="f_bleu"
||0||1||1|| ||1
|- class="f_bleu"
||1||0||0|| ||0
|- class="f_bleu"
||1||0||1|| ||1
|- class="f_vert"
||1||1||0|| ||1
|- class="f_vert"
||1||1||1|| ||1
|}
Ensuite, créons un circuit qui prend ces deux sorties P et G, et calcule la retenue sortante en fonction. Le circuit en question a trois entrées : la retenue entrante, les deux bits P et G. La retenue sortante vaut 1 soit si une retenue est générée, soit si la retenue entrante est propagée et qu'elle vaut 1. En notant <math>{C_{in}}</math> et <math>{C_{out}}</math> les retenues entrantes et sortante, on a :
: <math>{C_{out}} = G + \left( P . C_{in} \right)</math>
Le circuit est donc composé d'une porte OU et d'une porte ET. En combinant un demi-additionneur avec le circuit de calcul de retenue sortante vu plus haut, on a :
[[File:Additionneur complet avec propagation et génération de retenue.png|class=transparent|centre|vignette|upright=2|Additionneur complet avec propagation et génération de retenue.]]
Certes, on retombe sur le circuit vu plus haut. Mais il est possible de remplacer le circuit en orange par un autre. Par exemple, il est possible de le remplacer par multiplexeur, qui choisit entre la retenue générée et la retenue entrante (propagée). Le choix se fait selon la valeur du bit P, qui chosiit entre propager la retenue et la générer.
[[File:Additionneur crée avec un multiplexeur.png|centre|vignette|upright=1.5|Additionneur crée avec un multiplexeur]]
Le circuit exact est illustré ci-dessous. Il semble utiliser plus de portes logiques que l'additionneur complet précédent. Cependant, nous verrons dans quelques chapitres qu'il est possible d'implémenter un multiplexeur avec seulement 6 transistors, voire moins ! L'implémentation utilise des portes à transmission, mais nous en reparlerons dans le chapitre sur les transistors, quand nous verrons les additionneurs à ''Manchester Carry Chain''. Au passage, une variante de ce circuit a été utilisée dans le processeur processeur 8086 d'Intel, comme on le verra dans le chapitre suivant.
[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
[[File:FulladderPG.png|vignette|upright=1|Additionneur complet avec deux sorties P et G.]]
Pour finir, sachez qu'il existe des additionneurs qui fournissent : le bit de résultat, deux sorties P et G qui indiquent si l'addition propage ou génère une retenue. Il ne s'agit pas d'additionneurs complets, car il manque de quoi calculer la retenue sortante. De tels additionneurs seront appelés des '''additionneurs partiels'''. De tels additionneurs sont utilisés dans certains additionneurs pour gagner en performance.
Enfin, il existe aussi ce que j'ai décidé d'appeler des '''additionneurs P/G''', qui sont des additionneurs complets auxquels on a ajouté deux sorties P et G, en plus de la sortie de retenue. Leur circuit est le même que celui d'un additionneur complet, auquel on a ajouté deux fils. Vu que ces deux sorties sont fournies par le premier demi-additionneur, ajouter ces deux sorties demande d'ajouter des fils, pas de portes logiques. De tels additionneurs sont aussi utilisés dans certains additionneurs pour gagner en performance.
===L'additionneur complet basé sur une porte à majorité===
Maintenant, voyons une dernière implémentation possible de l'additionneur complet. Mais avant de voir comment l'additionneur est implémenté, nous allons devoir faire un petit focus sur la retenue sortante. Reprenons la table de vérité, et regardons ce qu'il en est pour la retenue sortante uniquement.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante
|-
||0||0||0|| ||0
|-
||0||0||1|| ||0
|-
||0||1||0|| ||0
|-
||0||1||1|| ||1
|-
||1||0||0|| ||0
|-
||1||0||1|| ||1
|-
||1||1||0|| ||1
|-
||1||1||1|| ||1
|}
Vous remarquerez que la retenue sortante est égale au bit majoritaire, parmi les trois bits d'opérande. Si deux ou trois bits sont à 1, la retenue sortante vaut 1. Et réciproquement avec 0. Le circuit de calcul de la retenue peut donc être remplacé par une '''porte à majorité'''.
[[File:Additionneur crée avec une porte à majorité.png|centre|vignette|upright=2|Additionneur crée avec une porte à majorité]]
Mais une porte à majorité est assez gourmande en circuit, ce qui fait qu'un additionneur pareil ne serait pas pratique, ni utile. Du moins, ce serait le cas s'il n'y avait pas une possibilité d'optimisation extrêmement intéressante. Il est possible de calculer le 'bit de somme' à partir de la retenue sortante ! En effet, le bit de somme est l'inverse de la retenue sortante, sauf dans deux cas : les trois bits d'entrée sont à 0, où ils sont tous à 1. Le bit de somme vaut 0 dans le premier cas, 1 dans le second cas.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|- class="f_bleu"
||0||0||0|| ||0||0
|- class="f_vert"
||0||0||1|| ||0||1
|- class="f_vert"
||0||1||0|| ||0||1
|- class="f_vert"
||0||1||1|| ||1||0
|- class="f_vert"
||1||0||0|| ||0||1
|- class="f_vert"
||1||0||1|| ||1||0
|- class="f_vert"
||1||1||0|| ||1||0
|- class="f_rouge"
||1||1||1|| ||1||1
|}
En clair, 6 lignes de la table de vérité sur 8 peuvent se calculer avec une porte à majorité, le reste demandant quelques portes logiques pour faire la correction. L'implémentation en circuit demande donc trois choses :
* de calculer la retenue sortante et de quoi l'inverser ;
* un circuit qui vérifie si tous les bits opérande valent 0 : une porte NOR ou OU fait l'affaire ;
* un circuit qui vérifie s'ils valent tous 1, à savoir une porte ET.
Ensuite, on combine le résultat des trois circuits précédents pour obtenir le résultat final. La combinaison est le fait du circuit en jaune dans le schéma ci-dessous.
[[File:Full adder basé sur une modification de la retenue.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue]]
Le circuit en jaune est assez simple. Quand la porte ET sort un 1, la sortie doit être forcée à 1. Par contre, quand la porte OU sort un zéro, la sortie doit être forcée à 0. Il suffit donc d'enchainer un circuit de mise à 1 et un circuit de mise à 0. Nous avons vu dans le chapitre sur les opérations de masquage qu'il s'agit respectivement d'une porte OU et d'une porte ET.
[[File:Full adder basé sur une modification de la retenue interne, au complet.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue interne, au complet]]
Divers processeurs utilisaient des additionneurs complets de ce type : le fameux Z80, l'Intel 4004 et de l'Intel 8008 sont les exemples les plus notables. Pourtant, on pourrait s'étonner qu'un tel circuit ait existé. Il utilise beaucoup de portes logiques, a une profondeur logique supérieure : il n'a rien d'avantageux. Sauf qu'il était utilisé sur d'anciens processeurs, qui utilisaient la technologie dite TTL, différente de la technologie CMOS des transistors modernes. Et avec la technologie TTL, il est possible de fusionner plusieurs portes logiques ET et NOR en une seule porte logique ET/OU/NON ! Un additionneur complet construit ainsi ne prenait que deux portes logiques : une pour le calcul de la retenue sortante, une autre pour le reste du circuit.
==L'addition non signée==
Voyons maintenant un circuit capable d'additionner deux nombres entiers: l''''additionneur'''. Dans la version qu'on va voir, ce circuit manipulera des nombres strictement positifs. L'addition des nombres codés en complètement à deux sera vu dans une section ultérieure.
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
L'interface d'un additionneur est illustrée ci-contre. Un additionneur prend deux opérandes sur deux entrées séparées, et fournit le résultat sur une sortie dédiée. De plus, il a une entrée sur laquelle envoyer une retenue entrante, et a une sortie pour la retenue sortante. La sortie de retenue indique que le résultat de l'addition a débordé (au sens d'un débordement d'entier). Pour l'entrée de retenue, son utilité deviendra plus claire dans ce qui suit, mais souvenez-vous qu'elle existe.
===L'additionneur série===
Il est possible d'additionner deux nombres bit par bit, grâce à un additionneur complet associé à plusieurs registres à décalages. Les opérandes sont placées chacune dans un registre à décalage, et l'additionneur complet utilise les bits sortants de ces registres à décalage. Le bit du résultat est envoyé au registre à décalage pour le résultat. La retenue de l'addition est stockée dans une bascule de 1 bit, en attente du prochain cycle d'horloge. Un tel additionneur est appelé un '''additionneur série'''. Il a été utilisé sur d'anciens ordinateurs dans les années 50-60, aussi bien des prototypes que des ordinateurs commerciaux.
[[File:Additionneur série.jpg|centre|vignette|upright=2.5|Additionneur série.]]
===L'additionneur à propagation de retenue===
L''''additionneur à propagation de retenue''' est aussi appelé l'additionneur ''ripple carry'', c'est son nom anglais. J'utiliserais occasionnellement le nom anglais dans ce qui suit, car celui-ci est plus court. Ilutilise un additionneur complet pour chaque colonne de l'addition. Pour gérer les retenues, il suffit ainsi de câbler des additionneurs complets les uns à la suite des autres, chacun envoyant sa retenue à la colonne suivante.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Notons la présence de la retenue sortante, qui est utilisée pour détecter les débordements d'entier, ainsi que pour d'autres opérations. Le bit de retenue final est souvent stocké dans un registre spécial du processeur (généralement appelé ''carry flag'').
Notez aussi, à droite du schéma précédent, l'entrée de retenue <math>\text{R}_\text{Entrée}</math>. Elle est très utile pour l'implémentation de certaines opérations comme l'inversion de signe, la soustraction, l'incrémentation, etc. Certains processeurs sont capables de faire une opération appelée ADC, ADDC ou autre nom signifiant ''Addition with Carry'', qui permet de faire le calcul A + B + Retenue (la retenue en question est la retenue sortante de l'addition précédente, stockée dans le registre ''carry flag''). Son utilité principale est de permettre des additions d'entiers plus grands que ceux supportés par le processeur. Par exemple, cela permet de faire des additions d'entiers 32 bits sur un processeur 16 bits.
[[File:Adder Network 3 Bit Sum Delay 003.svg|vignette|upright=0.5|Propagation de retenue dans l'additionneur.]]
Cet additionneur utilise très peu de portes logiques, ce qui fait qu'il était utilisé sur certains processeurs 8 et 16 bits, dont le budget en portes logiques était limité. Bien que très simple, cet additionneur est cependant peu performant. Son temps de calcul est proportionnel à la taille des opérandes. Par exemple, additionner deux opérandes de 32 bits prendra deux fois plus de temps que pour des opérandes de 16 bits. La raison est que les retenues doivent se propager à travers le circuit, du premier additionneur jusqu'au dernier.
L'addition étant une opération fréquente, d'autres additionneurs ont été inventés. Ils utilisent des optimisations qui utilisent plus de circuits pour gagner en rapidité. Mais avant de voir ces optimisations, nous allons voir s'il est possible d'optimiser les additionneurs à propagation de retenue.
===L'additionneur à saut de retenue===
L'additionneur à propagation de retenue peut être optimisé de deux manières. La première accélère la propagation de la retenue, en agissant au niveau des additionneurs complets. La ''Manchester carry chain'' est une optimisation de ce type, mais on ne peut pas encore expliquer à ce stade du cours, car elle optimisait les additionneurs complets au niveau des transistors eux-mêmes. Elle a été utilisée sur de nombreux processeurs connus, comme le 8086 d'Intel ou le Z80.
La seconde manière rend l'addition plus rapide dans certains cas. Le résultat est un additionneur dont le temps de calcul est variable. Le calcul prendra quelques cycles d'horloges avec certains opérandes, mais d'autres opérandes ne verront aucune amélioration. Il n'améliore pas le pire des cas, dans lequel la retenue doit être propagée du début à la fin, du bit de poids faible au bit de poids fort. Mais dans d'autres cas, il permet d'avoir le résultat en avance. L'optimisation en question donne l''''additionneur à saut de retenue''' (''carry-skip adder'').
Il est composé en enchainant plusieurs additionneurs plus petits, qui additionnent 4/5 bits, rarement plus. De tels additionneurs de petite taille sont appelés des '''blocs''', dans ce qui suit. L'idée est de détecter précocement si la retenue entrante est propagée à travers tout le bloc. Si la retenue est propagée dans tous le bloc, on peut directement l'envoyer sur la sortie de retenue, la retenue saute le bloc entier. Dans le cas contraire, on doit calculer la retenue normalement. Le choix entre les deux est le fait d'un multiplexeur. Toute la difficulté est de savoir comment commander le multiplexeur.
[[File:Carry skip adder, principe de base.png|centre|vignette|upright=2.5|Carry skip adder : principe de base]]
Pour savoir si une retenue est propagée dans un bloc entier, il faut utiliser des additionneurs complets, qui ont en plus une sortie P indiquant que l'additionneur propage la retenue. Le bloc propage une retenue si chaque additionneur complet propage la retenue, donc s'ils ont tous leur sortie P à 1. Un simple ET logique suffit à déterminer si c'est le cas. Le signal de commande du multiplexeur est donc un vulgaire ET entre toutes les sorties P des additionneurs complets du bloc.
[[File:Additionneur à saut de retenue.png|centre|vignette|upright=2.5|Calcul de la commande du MUX.]]
Voici ce que ça donne pour un bloc de 4 bits :
[[File:CSAdder4Bit.svg|centre|vignette|upright=1.5|Additionneur ''carry skip'' : bloc de 4 bits.]]
L'additionneur à saut de retenue est construit en assemblant plusieurs blocs de ce type.
[[File:BCSAdder16Bit.svg|centre|vignette|upright=3|Additionneur à saut de retenue.]]
Les blocs sont tous identiques dans le cas le plus simple, mais il est possible d'utiliser des blocs de taille variable. Par exemple, le premier bloc peut avoir des opérandes de 6 bits, le second des opérandes de 7 bits, etc. Faire ainsi permet de gagner un petit peu en performances, si la taille de chaque bloc est bien choisie. La raison est une question de temps de propagation des retenues. La retenue met plus de temps à se propager à travers 8 blocs qu'à travers 4, ce qui prend plus de temps qu'à travers 2 blocs, etc. En tenir compte fait que la taille des blocs tend à augmenter ou diminuer quand on se rapproche des bits de poids fort.
===L'additionneur à sélection de retenue===
L''''additionneur à sélection de retenue''' utilise aussi des blocs, comme les additionneurs précédents. L'addition se fait en deux versions : une avec la retenue du bloc précédent valant zéro, et une autre version avec la retenue du bloc précédent valant 1. Il suffira alors de choisir le bon résultat avec un multiplexeur, une fois cette retenue connue. On gagne ainsi du temps en calculant à l'avance les valeurs de certains bits du résultat, sans connaître la valeur de la retenue. Petit détail : sur certains additionneurs à sélection de retenue, les blocs de base n'ont pas la même taille. Cela permet de tenir compte des temps de propagation des retenues entre les blocs.
[[File:Additionneur à sélection de retenue.png|centre|vignette|upright=1.5|Additionneur à sélection de retenue avec seulement deux blocs.]]
Dans les exemples du dessus, chaque sous-additionneur étaient des additionneurs à propagation de retenue. Mais ce n'est pas une obligation, et tout autre type d’additionneur peut être utilisé. Par exemple, on peut faire en sorte que les sous-additionneurs soient eux-mêmes des additionneurs à sélection de retenue, et poursuivre ainsi de suite, récursivement. On obtient alors un '''additionneur à somme conditionnelle''', plus rapide que l'additionneur à sélection de retenue, mais qui utilise beaucoup plus de portes logiques.
===Les additionneurs à anticipation de retenue===
Les '''additionneurs à anticipation de retenue''' calculent chaque retenue sans avoir à propager les retenues précédentes. Au lieu de calculer les retenues une par une, ils calculent toutes les retenues en parallèle, à partir des bits des opérandes. Une fois les retenues pré-calculées, il suffit de les additionner avec les deux bits adéquats, pour obtenir le résultat. Ces additionneurs sont composés de deux parties :
* un circuit qui pré-calcule les retenues, sans les propager, directement à partir des opérandes ;
* d'une couche d'additionneurs complets, qui additionnent chacun deux bits d'opérande et la retenue pré-calculée.
[[File:Additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Additionneur à anticipation de retenue.]]
Le calcul des retenues se fait en deux étapes. La première détermine si les retenues sont propagées ou générées, pour chaque colonne. Elle détermine, pour chaque colonne, les bits P et G qui indiquent respectivement : qu'une retenue a été générée sur cette colonne, que la colonne propage la retenue précédente. La seconde étape utilise ces signaux P et G pour déterminer la retenue sur chaque colonne. Le circuit de calcul des retenues est donc composé de deux parties : une couche de demi-additionneurs pour générer les signaux P et G, un circuit de calcul des retenues proprement dit.
[[File:Circuit complet d'un additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Circuit complet d'un additionneur à anticipation de retenue.]]
Pour rappel, la retenue de la colonne i, notée <math>C_i</math> est égale à :
: <math>G_i + (P_i . C_{i-1})</math>, avec G le signal de génération de retenue, P le signal de propagation de retenue.
Si on utilisait cette formule sans trop réfléchir, on retomberait sur un additionneur à propagation de retenue inutilement compliqué. L'astuce des additionneurs à anticipation de retenue consiste à remplacer le terme Ci−1 par sa valeur calculée avant. Par exemple, je prends un additionneur 4 bits. Je dispose de deux nombres A et B, contenant chacun 4 bits : A3, A2, A1, et A0 pour le nombre A, et B3, B2, B1, et B0 pour le nombre B. Si j'effectue les remplacements, j'obtiens les formules suivantes :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Ces formules nous permettent de déduire la valeur d'une retenue directement : il reste alors à créer un circuit qui implémente ces formules, et le tour est joué.
[[File:Four bit adder with carry lookahead.svg|centre|vignette|upright=1.5|Additionneur à anticipation de retenue de 4 bits.]]
Plus haut, j'ai dit que le circuit était composé de trois couches : une couche de demi-additionneur pour les signaux P et G ; un circuit d'anticipation de retenue, puis l'addition finale. Mais les deux bits d'opérandes sont déjà additionnés dans la couche de demi-additionneur, autant réutiliser le résultat pour l'additionner à la retenue. Pour éliminer toute redondance de ce type, il est possible d'utiliser des additionneurs partiels (des additionneurs où la sortie de retenue a été remplacée par deux sorties P et G). L'additionneur final est alors composé de plusieurs additionneurs complets de ce type, couplé à un circuit d'anticipation de retenue.
[[File:Cla4bits.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue de 4 bits.]]
Les additionneurs à anticipation de retenue sont plus rapides que les additionneurs à propagation de retenue. La raison est que les retenues sont calculées rapidement, dans un temps plus petit qu'en propageant la retenue. Pour rentrer dans les détails mathématiques, le temps de calcul n'est pas proportionnel au nombre de bits, mais proportionnel au logarithme de ce nombre de bits. Par contre, le gain en temps de calcul s'accompagne d'un cout en portes logiques conséquent. Le circuit qui calcule les retenues anticipées utilise beaucoup de portes logiques.
===Les additionneurs à calcul parallèle de préfixe===
Du fait du cout en portes logiques, utiliser un additionneur à anticipation de retenue est une solution pratique pour additionner 4 ou 8 bits. Mais pour des opérandes plus grands, de 16 ou 32 bits, cela utiliserait trop de portes logiques. Une partie de ce cout en portes logiques est cependant lié à des redondances. Si on analyse deux retenues différentes, à des colonnes différentes. Les circuits qui calculent ces deux retenues seront différents, mais ils contiendront des sous-circuits identiques. Pour vous en rendre compte, reprenons les quatre équations obtenues précédemment et essayez de trouver des redondances :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Vous devriez trouver :
* le terme ''P0 · C0'' dans toutes les lignes ;
* les termes ''P1 · P0 · C0'' et ''P1 · P0'' dans les trois dernières lignes ;
* le terme ''P2 · P1 · P0 · C0'' ; ''P2 · P1 · G0'' et ''P2 · G1'' dans les deux dernières lignes.
Il existe plusieurs manières d'exploiter de telles redondances, exploitées dans les '''additionneurs à calcul parallèle de préfixe'''. L'optimisation apportée est de générer des signaux ''propagate'' et ''generate'' pour des groupes de 2, 3, 4, ..., N colonnes. Par exemple, il est possible de générer un signal ''P 0 vers 3'', qui indique si la retenue de la première colonne est propagée jusqu'à la 4ème colonne. Un autre exemple est un signal qui indique qu'une retenue a été générée entre les colonnes 4 à 7 génèrent une retenue ou non. En clair, les signaux P et G ont maintenant un intervalle, qui précise de quelle colonne vers quelle colonne se fait la propagation, ou entre quelles colonnes se fait la génération.
Il est possible de combiner les signaux P et G de deux groupes de colonne, s'ils sont contiguës (ils peuvent aussi se recouvrir). Par exemple, il est possible de calculer les bits P et G pour les colonnes 0 à 10, à partir des deux signaux P/G des colonnes 0-4 et 5-10. Pour cela, les équations sont assez simples. Si je prends deux groupes nommés A et B, avec A pour les colonnes de poids fort et B celles de poids faible, on a :
: <math>P_{sortie} = P_A . P_B</math>
: <math>G_{sortie} = G_A . P_A . G_B</math>
Pour les bits des colonnes 2, 4, 8, 16, 32, 64, et autres, le calcul est simple. L'idée est de grouper les colonnes par groupes de 2. Puis, on calcule chaque groupe de 4 bits à partir de deux groupes de 2. Puis les groupes de 8 bits sont calculés à partir de deux groupes de 4 bits, et ainsi de suite pour toute puissance de deux.
[[File:Squelette d'un additionneur à calcul parallèle de préfixe.jpg|centre|vignette|upright=2|Squelette d'un additionneur à calcul parallèle de préfixe]]
Les seules difficultés surviennent pour les colonnes dont le nombre n'est pas une puissance de deux. Il y a plusieurs manières pour subdiviser ces colonnes en intervalles plus petits et combiner le tout. Et elles donnent chacune des additionneurs différents. Ils portent les noms d'''additionneur de Ladner-Fisher'', d'''additionneur de Brent-Kung'', d'''additionneur de Kogge-Stone'', etc. Dans le cas général, l'additionneur de Kogge-Stone est un des plus rapide, mais aussi un des plus gourmand en circuits, alors que c'est l'inverse pour l'additionneur de Brent-Kung. Les deux additionneurs sont illustrés ci-dessous, pour des opérandes de 8 bits. Dans ceux-ci, chaque losange calculent les bits P et G, à partir des signaux P et G de deux groupes précédents.
{|
|[[File:Brent-kung-8-bit.png|vignette|Brent-kung-8-bit]]
|[[File:Kogge-stone-8-bit.png|vignette|upright=1.2|Kogge-stone-8-bit]]
|}
===Le calcul parallèle de la retenue===
L'anticipation de retenue est très rapide, mais utilise beaucoup de circuits, même en utilisant le calcul parallèle de préfixe. Les additionneurs à propagation de retenue font eux le compromis inverse. Mais il existe des intermédiaires, qui visent à obtenir un compromis entre performance et cout en portes logiques.
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Un de ces compromis découpe un additionneur de 16/32/64 bits en additionneurs de 4/5 bits, qui sont enchainés en utilisant la propagation de retenue. Par contre, ces petits additionneurs de 4/5 bits utilisent l'anticipation de retenue. Ces additionneurs simples seront nommés ''blocs'' dans ce qui suit, et l'un d'entre eux est illustré ci-contre. Par exemple, on peut utiliser 4 additionneurs à anticipation de retenue de 4 bits, et propager les retenues entre eux. Le premier bloc calcule une retenue, qui est propagée au second bloc. Le second bloc calcule lui aussi une retenue, qui est propagée au troisième bloc, etc.
[[File:4008 Functional Diagram.svg|vignette|upright=1|4008 Functional Diagram]]
Le gain en performance est significatif, mais il est possible de faire presque aussi rapide, tout en économisant beaucoup de circuits. L'idée est de simplifier les additionneurs 4 bits, afin de ne pas utiliser une anticipation de retenue complète. L'anticipation de retenue n'est utilisée que pour la retenue sortante, alors que les bits de somme sont calculées avec propagation de retenue. La retenue sortante est donc calculée en parallèle de l'addition. Chaque bloc contient, à côté d'un additionneur proprement dit, un circuit qui calcule la retenue sortante. Il existe de nombreuses manières de calculer la retenue sortante.
Le gain en portes logiques est assez significatif. En effet, on se passe de circuits d'anticipation de retenue pour les 4 bits de somme, on ne garde que celui pour la retenue sortante. Le fait d'utiliser la propagation de retenue pour calculer les 4 bits de somme est censé avoir un léger cout en performance, mais il se trouve que ce cout est très faible. En effet, le résultat n'est connu qu'une fois les retenues propagées jusqu'au dernier bloc. Et malgré l'anticipation des retenues, la propagation est assez lente. les additionneurs à propagation de retenue auront terminé leur travail bien avant.
Un exemple est celui de l'additionneur CMOS 4008, un additionneur de 4 bit. Il est composé en trois sections. Une première couche de demi-additionneurs calcule les signaux P et G utilisés à la fois pour l'anticipation de la retenue sortante, que pour calculer les bits de somme. Le circuit de calcul de la retenue sortante utilise l'anticipation de retenue. Le reste du circuit fait l'addition en propageant les retenues.
[[File:CMOS 4008, circuit découpé en sections.png|centre|vignette|upright=1.5|CMOS 4008, circuit découpé en sections]]
===Les additionneurs à anticipation de retenue multi-niveau===
Les additionneurs précédents mixent anticipation et propagation de retenue. Ceux que nous allons voir utilisent des blocs, mais utilisent l'anticipation de retenue entre les blocs. Les blocs utilisés peuvent être un additionneur à propagation de retenue, ou à anticipation de retenue, peu importe. Ce qui compte est que la retenue entrante de chaque bloc est calculée par anticipation de retenue.
Par exemple, il est possible de créer un additionneur 16 bits en utilisant 4 additionneurs/blocs de 4 bits, couplé à un circuit d’anticipation de retenue par bloc. Pour cela, les additionneurs de 4 bits doivent fournir deux sorties P et G. Les deux sorties indiquent que le bloc soit a généré une retenue, soit a propagé la retenue entrante. Par propagé, on veut dire que la retenue entrante du bloc s'est retrouvée sur la sortie de retenue du bloc. C'est le cas si les 4 colonnes propagent la retenue, ce qui signifie que :
: <math>P_\text{4 bits} = P_0 \cdot P_1 \cdot P_2 \cdot P_3</math>
Pour la génération, cela veut dire qu'une colonne a générée une retenue, qui a été propagée jusqu'à la sortie. En clair, pour chaque colonne, il faut que le G = 1, et que les signaux P des colonnes soient à 1. Pour les 4 colonnes, cela donne :
: <math>G_\text{4 bits} = G_3 + G_2 \cdot P_3 + G_1 \cdot P_3 \cdot P_2 + G_0 \cdot P_3 \cdot P_2 \cdot P_1</math>
[[File:Cla4bitsPG.png|centre|vignette|upright=2|Additionneur à anticipation de retenue de 4 bits.]]
Pour combiner 4 blocs de 4 bits, on devra donc combiner 4 bits P et G, avec un circuit d'anticipation de retenue.
[[File:16-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 16 bits.]]
Il est même possible d'aller plus loin et de combiner 4 additionneurs précédents pour obtenir un additionneur 64 bits.
[[File:64-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 64 bits.]]
Le résultat est un additionneur à plusieurs niveaux d’anticipation de retenue. Une anticipation de retenue dans un blocs de 4 bits (facultative), entre blocs de 4 bits, entre blocs de 16 bits. La même logique peut être utilisée avec des blocs de taille différente de 4, 16 et 64 bits.
==L'addition signée et la soustraction==
Après avoir vu l'addition, il est logique de passer à la soustraction, les deux opérations étant très proches. Si on sait câbler une addition entre entiers positifs, câbler une soustraction n'est pas très compliqué. De plus, la soustraction permet de faire des additions de nombres signés.
===Le soustracteur pour opérandes entiers===
Pour soustraire deux nombres entiers, on peut adapter l'algorithme de soustraction utilisé en décimal, celui que vous avez appris à l'école. Celui-ci ressemble fortement à l'algorithme d'addition : on soustrait les bits de même poids, et on propage éventuellement une retenue sur la colonne suivante. À la différence de l'addition, la retenue est soustraite, et non ajoutée. La table de soustraction nous dit quel est le résultat de la soustraction de deux bits. La voici :
* 0 - 0 = 0 ;
* 0 - 1 = 1 et une retenue ;
* 1 - 0 = 1 ;
* 1 - 1 = 0.
[[File:Algebra1 05 fig014.svg|centre|vignette|Soustraction en binaire, avec les retenues en rouge.]]
La table de soustraction peut servir de table de vérité pour construire un circuit qui soustrait deux bits. Celui-ci est appelé un '''demi-soustracteur'''. Il ressemble beaucoup à un demi-additionneur, les différences se résumant à une porte NON ajoutée pour le calcul de la retenue.
[[File:Half Subtractor Vektor.svg|centre|vignette|Demi-soustracteur.]]
Comme pour l'additionneur, deux demi-soustracteurs peuvent être combinés pour donner un '''soustracteur complet'''. Le calcul de la retenue se fait en combinant les deux retenues des demi-soustracteurs avec une porte OU. Les soustracteurs complets sont utilisés pour créer des soustracteurs à propagation de retenue ou tout autre circuit soustracteur, sur le même modèle que les additionneurs.
[[File:FullSubtractor.svg|centre|vignette|upright=2|Soustracteur complet.]]
Vous l'aurez sans doute remarqué, mais le demi-soustracteur ressemble beaucoup au demi-additionneur. La seule différence est une porte NON ajoutée au bon endroit dans le demi-soustracteur.
{|
|[[File:Half-adder.svg|vignette|Demi-additionneur]]
|[[File:Half Subtractor Vektor.svg|vignette|upright=1.3|Demi-soustracteur]]
|}
La conséquence est qu'il est possible de créer un circuit capable de faire à la fois des additions et des soustractions. L'idée est de remplacer la porte NON du demi-soustracteur par un inverseur commandable. Si on envoie un zéro sur l'entrée de commande, l'inverseur commandable se comportera comme une porte OUI. Le circuit sera alors identique à un demi-additionneur. Si on envoie un 1 sur l'entrée de commande, l'inverseur commandable se comportera comme une porte NON. le circuit sera alors un demi-soustracteur. Il faut faire cela pour chaque demi-soustracteur, ce qui donne ceci pour un soustracteur complet. le résultat est alors appelé un '''additionneur-soustracteur complet'''.
[[File:Additionneur-soustracteur complet.png|centre|vignette|upright=2|Additionneur-soustracteur complet]]
===L'additionneur-soustracteur pour opérandes codées en complément à deux===
Étudions le cas de la soustraction en complément à deux, dans l'objectif de créer un circuit soustracteur. Vous savez sûrement que a−b et a+(−b) sont deux expressions équivalentes. Et en complément à deux, − b = not(b) + 1. Dit autrement, a − b = a + not(b) + 1. On pourrait se dire qu'il faut deux additionneurs pour faire le calcul, mais la majorité des additionneurs possède une entrée de retenue pour incrémenter le résultat de l'addition. Un soustracteur en complément à deux est donc simplement composé d'un additionneur et d'un inverseur.
[[File:Additionneur soustracteur.png|centre|vignette|upright=2|Soustracteur en complément à deux.]]
Il est possible de créer un circuit capable d'effectuer soit une addition, soit une soustraction : il suffit de remplacer l'inverseur par un inverseur commandable, qui peut être désactivé. On a vu comment créer un tel inverseur commandable dans le chapitre sur les circuits combinatoires. On peut remarquer que l'entrée de retenue et l'entrée de commande de l'inverseur sont activées en même temps : on peut fusionner les deux signaux en un seul.
[[File:Additionneur-soustracteur en complément à deux.png|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux.]]
Une implémentation alternative est la suivante. Elle remplace l'inverseur commandable par un multiplexeur.
[[File:4-bit ripple carry adder-subtracter.svg|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux, version alternative.]]
===L'additionneur-soustracteur pour opérandes codées en signe-magnitude===
Passons maintenant aux nombres codés en signe-valeur absolue, les deux opérandes étant notées A et B. Suivant les signes des deux opérandes, on a quatre cas possibles : A + B, A − B (B négatif), −A + B (A négatif) et −A − B (A et B négatifs). Une astuce est que le circuit n'a besoin que de calculer A + B et A − B : il peut les inverser pour obtenir − A − B ou B − A. A + B et A − B peuvent se calculer avec un additionneur-soustracteur, reste à corriger le résultat. Il suffit de lui ajouter un inverseur commandable pour obtenir le circuit d'addition finale.
[[File:Additionneur en signe-valeur absolue.png|centre|vignette|upright=2|Additionneur en signe-valeur absolue.]]
Toute la difficulté tient dans le calcul du bit de signe du résultat, quand interviennent des soustractions. Autant l'addition de deux nombres de même signe (A + B et −A − B) ne pose aucun problème, autant les soustractions posent problème (A − B et −A + B). Suivant que <math>A<B</math> ou que <math>A>B</math>, le signe du résultat ne sera pas le même. Déterminer le signe du résultat se fait en regardant les bits de débordement d'entier, comme on le verra plus bas.
===L'additionneur-soustracteur pour opérandes codées en représentation par excès===
Passons maintenant aux nombres codés en représentation par excès. On pourrait croire que ces nombres s'additionnent comme des nombres non-signés, mais ce serait oublier la présence du biais, qui pose problème. Dans les cas de nombres signés gérés avec un biais, voyons ce que donne l'addition de deux nombres :
: <math>( n_1 + biais ) + ( n_2 + biais ) = n_1 + n_2 + 2 \times biais</math>
Or, le résultat correct serait :
: <math>n_1 + n_2 + biais</math>
En effectuant l'addition telle quelle, le biais est compté deux fois. On doit donc le soustraire après l'addition pour obtenir le résultat correct.
Même chose pour la soustraction qui donne ceci :
: <math>(n_1 + biais) - (n_2 + biais) = n_1 - n_2</math>
Or, le résultat correct serait :
: <math> ( n_1 - n_2 ) + biais </math>
Il faut rajouter le biais pour obtenir l'exposant correct.
On a donc besoin de deux additionneurs/soustracteurs : un pour additionner/soustraire les représentations binaires des opérandes, et un autre pour ajouter/retirer le biais en trop/manquant.
==L'additionneur BCD==
Maintenant, voyons un additionneur qui additionne deux entiers au format BCD. Pour cela, nous allons devoir passer par deux étapes. La première est de créer un circuit capable d'additionneur deux chiffres BCD. Ensuite, nous allons voir comment enchaîner ces circuits pour créer un additionneur BCD complet.
===L'additionneur BCD qui fait l'opération chiffre par chiffre===
Nous allons commencer par voir un additionneur qui additionne deux chiffres en BCD, une sorte d'équivalent BCD de l'additionneur complet. Il fournit un résultat sur 4 bits et une retenue qui est mise à 1 si le résultat dépasse 10 (la limite d'un chiffre BCD). Les deux opérandes sont des chiffres BCD codés sur 4 bits et sont additionnés en binaire par un additionneur des plus normaux, similaire à ceux vus plus haut. Le résultat est alors un entier codé en binaire, sur 5 bits, qu'on corrige/convertit pour obtenir un chiffre BCD et une retenue sortante.
Pour corriger le résultat, une idée intuitive serait de prendre le résultat et de faire une division par 10. Le quotient donne la retenue, alors que le reste est le résultat, le chiffre BCD. Mais un circuit diviseur par 10 utilise beaucoup de portes logiques, ce qui ne vaut pas le coup. Une autre méthode détecte si le résultat est égal ou supérieur à 10, ce qui correspond à un "débordement" (on dépasse les limites d'un chiffre BCD). Si le résultat est plus petit que 10, il n'y a rien à faire : le résultat est bon et la retenue est de zéro. Par contre, si le résultat vaut 10 ou plus, il faut corriger le résultat et générer une retenue à 1.
Il faut donc ajouter un circuit qui détecte si le résultat est supérieur à 9, qui calcule directement la retenue. Ce circuit peut se fabriquer simplement à partir de sa table de vérité, ou en utilisant les techniques que nous verrons dans un chapitre ultérieur sur les comparateurs. La solution la plus simple est clairement d'utiliser la table de vérité, ce qui est très simple, assez pour être laissé en exercice au lecteur. Pour comprendre comment corriger le résultat, établissons une table de vérité qui associe le résultat et le résultat corrigé. L'entrée vaut au minimum 10 et au maximum 9 + 9 = 18. On considère la sortie comme un tout, la retenue étant un 5ème bit, le bit de poids fort.
{|class="wikitable"
|-
! colspan="5" | Entrée
!
! rowspan="10" |
! Retenue
! Résultat corrigé (sans retenue)
! interprétation de la sortie en binaire (retenue inclue)
|-
| 0 || 1 || 0 || 1 || 0
| (10)
| 1 || 0000
| (16)
|-
| 0 || 1 || 0 || 1 || 1
| (11)
| 1 || 0001
| (17)
|-
| 0 || 1 || 1 || 0 || 0
| (12)
| 1 || 0010
| (18)
|-
| 0 || 1 || 1 || 0 || 1
| (13)
| 1 || 0011
| (19)
|-
| 0 || 1 || 1 || 1 || 0
| (14)
| 1 || 0100
| (20)
|-
| 0 || 1 || 1 || 1 || 1
| (15)
| 1 || 0101
| (21)
|-
| 1 || 0 || 0 || 0 || 0
| (16)
| 1 || 0110
| (22)
|-
| 1 || 0 || 0 || 0 || 1
| (17)
| 1 || 0111
| (23)
|-
| 1 || 0 || 0 || 1 || 0
| (18)
| 1 || 1000
| (24)
|}
En analysant le tableau, on voit que pour corriger le résultat, il suffit d'ajouter 6. La raison est que le résultat déborde d'un nibble à 16 en binaire, mais à 10 en décimal : il suffit d'ajouter la différence entre les deux, à savoir 6, et le débordement binaire fait son travail. Donc, la correction après une addition est très simple : si le résultat dépasse 9, on ajoute 6.
On peut maintenant implémenter l'additionneur BCD, en combinant le comparateur avec 10, le circuit de correction, et l'additionneur. La première solution calcule deux versions du résultat : la version corrigée, la version normale. Le choix entre les deux est réalisée par un multiplexeur, commandé par le comparateur.
[[File:Additionneur BCD.png|centre|vignette|upright=1.5|Additionneur BCD]]
L'autre solution utilise un circuit commandable qui soit additionne 6, soit ne fait rien. Le choix entre les deux est commandé par le bit calculé par le comparateur.
[[File:Additionneur BCD, seconde version.png|centre|vignette|upright=1.5|Additionneur BCD, seconde version.]]
Une version alternative du circuit précédent est la suivante. Il contient deux additionneurs : un pour additionner les deux chiffres BCD, un autre pour additionner 6 si besoin. Le résultat du comparateur est directement utilisé pour générer l'opérande du second additionneur : 0 ou 6. Le circuit est simple à concevoir, mais gaspille beaucoup de circuit. Idéalement, il vaudrait mieux utiliser un circuit combinatoire d'addition avec une constante.
[[File:Adder 4 Bit BCD.svg|centre|vignette|upright=2|Additionneur BCD, circuit complet.]]
Pour obtenir un additionneur BCD complet, il suffit d’enchaîner les additionneurs précédents, comme on le ferait avec les additionneurs complets dans un additionneur à propagation de retenue. Au final, l'additionneur BCD est beaucoup plus compliqué qu'un additionneur normal, car il rajoute un comparateur ">9", un petit additionneur pour ajouter 6 et éventuellement d'autres circuits. De plus, il est difficile d'appliquer les optimisations disponibles sur les additionneurs non-BCD. Notamment, les circuits d'anticipation de retenue sont totalement à refaire et le résultat est relativement compliqué. C'est ce qui explique pourquoi le BCD a progressivement été abandonné au profit du binaire simple.
La soustraction en BCD se fait comme en binaire : le nombre à soustraire est remplacé par son complément, le circuit additionne le complément et l'autre opérande, le débordement d'entier fait que le résultat marche. Sauf qu'ici, le complément est un complément à 9. Il se calcule chiffre par chiffre : chaque chiffre est remplacé par (9 - le chiffre en question).
===L'additionneur BCD par ajustement décimal===
L'additionneur BCD précédent effectuait son travail chiffre BCD par chiffre BCD, mais il existe des additionneurs BCD qui font autrement. Sur les premiers processeurs x86, il n'y avait pas d'opération d'addition BCD proprement dit, seulement une addition binaire normale de 8, 16 ou 32 bits. Par contre, elle était secondée par une opération dite d''''ajustement décimal''' qui transformait un nombre binaire en nombre codé en BCD. L'opération d'ajustement décimal prenait un opérande de 8 bits codé en binaire et fournissait un résultat de la même taille, c'est à dire deux chiffres BCD. Effectuer une addition BCD demandait donc de faire deux opérations à la suite : une addition binaire simple, suivie par l'opération d'ajustement décimal. Cela permettait de gérer des nombres entiers en binaire usuel et des entiers BCD sans avoir deux instructions d'addition séparées pour les deux, sans compter que cela simplifiait aussi les circuits d'addition.
L'ajustement décimal s'effectue en ajoutant une constante bien précise à l'opérande à convertir en BCD. L'idée est que la constante est découpée en morceaux de 4 bits, correspondant chacun à un chiffre BCD de l'opérande, chaque morceau contenant soit un 0, soit 6. Cela permet d'ajouter soit 0, soit 6, à chaque chiffre BCD, et donc de le corriger. La propagation des retenues d'un chiffre à l'autre est effectuée automatiquement par l'addition binaire de la constante. L'opération d'ajustement décimal calcule automatiquement la constante. Elle découpe l'opérande en ''nibbles'', vérifie si chaque ''nibble'' est supérieur ou égal à 10, puis détermine la valeur de chaque ''nibble'' de la constante finale. Par exemple, si je prends l'opérande 1001 1110, le ''nibble'' de poids faible déborde, alors que celui de poids fort non. La constante sera donc 0000 0110 : 0x06. Inversement, si le ''nibble'' de poids fort déborde et pas celui de poids faible, la constante sera alors 0x60. Et la constante est de 0x66 si les deux ''nibbles'' débordent, de 0x00 si aucun ne déborde.
Le circuit d’ajustement décimal est donc composé de trois étapes : deux étapes pour calculer la constante, et un circuit d'addition pour additionner cette constante au nombre de départ. La première étape découpe l'opérande en morceaux de 4 bits, en chiffres BCD, et vérifie si chacun d'entre eux vaut 10 ou plus. La seconde étape prend les résultats de la première étape, et les combine pour calculer la constante. Enfin, on trouve l'addition finale, qui était réalisée par un circuit d'addition utilisé à la fois pour l'ajustement décimal et l'addition binaire. La différence entre une addition normale et une opération d'ajustement décimal tient dans le fait que les deux premières étapes sont désactivées dans une addition normale.
[[File:Additionneur BCD parallèle.png|centre|vignette|upright=1.5|Additionneur BCD parallèle]]
===L'additionneur biquinaire===
Les entiers BCD ne sont qu'un des encodages hybrides entre décimal et binaire. L'encodage biquinaire est l'un d'entre eux et nous allons faire un rappel rapide à ce sujet. Pour simplifier, un chiffre encodé en biquinaire est composé de deux parties : un bit, couplé à une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits (0, 1, 2, 3 et 4). Le bit indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9.
Additionner deux nombres de biquinaire demande donc d'additionner deux parties quinaires encodées en ''one-hot'' et d'additionner deux bits. Mais attention : il faut tenir compte de la retenue de l'addition des parties quinaires. Et idéalement, il faut aussi tenir compte d'une retenue entrante, provenant de l'addition de la colonne de chiffres précédente. Toute la difficulté vient de la création de l'additionneur ''one-hot''. Heureusement, vu qu'il n'y a que 4-5 bits à additionner, il est souvent fabriqué à partir de sa table de vérité.
[[File:Additionneur bi-quinaire.png|centre|vignette|upright=2|Additionneur bi-quinaire]]
Un avantage du biquinaire est que le calcul du complément à 9 est très simple. Il faut pour cela : inverser la partie binaire avec une porte NON, puis inverser l'ordre des bits de la partie quinaire. Concrètement, le bit de poids faible devient le bit de poids fort, et ainsi de suite. Par exemple, une partie quinaire 01000 devient 00010, 10000 devient 00001, 00100 ne change pas, etc. Le tout peut se calculer avec une porte NON et 5 multiplexeurs.
===L'additionneur BCD avec calculs intermédiaires en biquinaire===
L'ordinateur IBM 1401, un ancien ''mainframe'' des années 60, utilisait un additionneur BCD un peu particulier. Les nombres étaient encodés en BCD dans la mémoire de l'ordinateur, mais les circuits de calcul utilisaient la représentation biquinaire. Lors d'un calcul, le processeur de l'ordinateur traduisait les chiffres BCD en représentation biquinaire, faisait une addition en biquinaire, avant de traduire le résultat en BCD normal.
Pour être précis, l'IBM 1401 utilisait une variante du biquinaire. L'encodage biquinaire de l'IBM 1401 est le suivant : la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre pair encodé par la partie quinaire. Si l'IBM 1401 utilisait cette variante du biquinaire, c'est car elle donnait des circuits de conversion BCD-biquinaire plus économes en portes logiques et plus rapides.
La partie binaire est le bit de poids faible du chiffre BCD, la partie biquinaire est calculée par un simple décodeur qui prend en entrée le chiffre BCD, amputé de son bit de poids faible. La traduction inverse demande d'utiliser un encodeur, à la place du décodeur. Par contre, le circuit d'addition biquinaire était plus compliqué du fait de la gestion des retenues. L'addition des parties binaires et quinaires se faisait en parallèle, dans deux additionneurs séparés. Cependant, l'addition des parties binaire fournit une retenue, qu'il faut prendre en compte. Pour cela, l'IBM 1401 disposait d'un troisième additionneur qui fournissait le résultat final, encodé en biquinaire.
[[File:Additionneur bi-quinaire de l'IBM 1401.png|centre|vignette|upright=2|Additionneur biquinaire de l'IBM 1401]]
Une implémentation moderne demanderait d'utiliser des portes ET combinées à des portes OU, le circuit pouvant être construit simplement à partir de sa table de vérité. Sur l'IBM 1401, le circuit était cependant différent, en raison de l'utilisation de OU câblés, des croisements de fils qui fonctionnent comme des portes OU, que nous n'avons pas encore vu pour le moment, mais qui seront détaillés dans quelques chapitres. Les OU câblés étaient utilisés pour simplifier le design du circuit, mais demandaient des portes logiques spécifiques, ce qui collait avec le fait que ce ''mainframe'' utilisait des transistors en Germanium. L'implémentation exacte est décrite dans cet article de blog, mais je ne recommande sa lecture qu'à ceux qui savent ce qu'est un OU câblé :
* [https://www.righto.com/2015/10/qui-binary-arithmetic-how-1960s-ibm.html Qui-binary arithmetic: how a 1960s IBM mainframe does math].
==Les débordements d'entier lors d'une addition/soustraction==
Les instructions arithmétiques manipulent des entiers codés sur un nombre fixe de bits, qui ne peuvent prendre leurs valeurs que dans un intervalle. Pour les nombres positifs, un ordinateur qui code ses entiers sur n bits pourra coder tous les entiers allant de 0 à <math>2^n -1</math>. Pour les nombres négatifs, l'intervalle est différent et dépend de la représentation utilisée. Dans le cas général, l'ordinateur peut coder les valeurs comprises de <math>N_\text{min}</math> à <math>N_\text{max}</math>. Si le résultat d'un calcul sort de cet intervalle, il ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math> , alors que la valeur basse vaut <math>N_\text{min} - 1</math> (avec <math>N_\text{max}</math> et <math>N_\text{min}</math> respectivement la plus grande et la plus petite valeur codable par l'ordinateur).
===La correction des débordements d'entier : l'arithmétique saturée===
Quand un débordement d'entier survient, tous les circuits de calcul ne procèdent pas de la même manière. Dans les grandes lignes, il y a deux réactions possibles : soit on corrige automatiquement le résultat du débordement, soit on ne fait rien et on se contente de détecter le débordement.
Si le débordement n'est pas corrigé automatiquement, le circuit ne conserve que les bits de poids faibles du résultat. Les bits en trop sont simplement ignorés. On dit qu'on utilise l''''arithmétique modulaire'''. Le problème avec ce genre d'arithmétique, c'est qu'une opération entre deux grands nombres peut donner un résultat très petit. Par exemple, si je dispose de registres 4 bits et que je souhaite faire l'addition 1111 + 0010 (ce qui donne 15 + 2), le résultat est censé être 10001 (17), ce qui est un résultat plus grand que la taille d'un registre. En conservant les 4 bits de poids faible, j’obtiens 0001 (1). En clair, un résultat très grand est transformé en un résultat très petit. Cela peut poser problèmes si on travaille uniquement avec des nombres positifs, mais c'est aussi utilisé pour coder des nombres en complément à deux.
D'autres circuits utilisent ce qu'on appelle l''''arithmétique saturée''' : si un calcul génère un débordement, on arrondi le résultat au plus grand entier supporté par le circuit. Les circuits capables de calculer en arithmétique saturée sont un peu plus complexes, vu qu'il faut rajouter des circuits pour corriger le résultat en cas de débordement. Il suffit généralement de rajouter un ''circuit de saturation'', qui prend en entrée le résultat et le corrige en cas de débordement. Ce circuit de saturation met la valeur maximale en sortie si un débordement survient, mais se contente de recopier le résultat du calcul sur sa sortie s'il n'y a pas de débordement. Typiquement, il est composé d'une couche de multiplexeurs, qui sélectionnent quelle valeur mettre sur la sortie : soit le résultat du calcul, soit le plus grand nombre entier géré par le processeur, soit le plus petit (pour les nombres négatifs/soustractions).
L'arithmétique saturée est utilisée pour les additions et soustractions, mais c'est plus rare pour les multiplications/divisions. Une des raisons est que le résultat d'une addition/soustraction prend un bit de plus que le résultat, là où les multiplications doublent le nombre de bits. Quand une addition déborde, le résultat réel est proche de la valeur maximale codable. mais quand une multiplication déborde, le résultat peut parfois valoir 200 à 60000 fois plus que la valeur maximale codable. Les calculs avec une valeur saturée/corrigée sont donc crédibles pour une suite d'additions, mais pas pour une suite de multiplications.
===La détection des débordements entiers===
Quand un débordement d'entier a eu lieu, il vaut mieux que l'additionneur prévienne ! Pour cela, l'additionneur a une '''sortie de débordement''', parfois nommée ''Overflow'', dont la valeur indique si l'addition a généré un débordement d'entier ou non. Reste que détecter un débordement ne se fait pas de la même manière selon que l'on parle d'un additionneur non-signé ou signé.
Pour les additionneur non-signés, l'additionneur calcule un bit de plus que ce qui est supporté par l'ordinateur. Par exemple, un additionneur 32 bits fournit un résultat sur 33 bits, un débordement d'entier a lieu quand le 33ème bit est à 1. Précisément, la sortie de débordement n'est autre que la retenue finale, celle fournie par le dernier additionneur complet. Le seul type de débordement possible est un débordement par le haut, où le résultat dépasse la valeur maximale. Avec l'arithmétique saturée, le circuit de saturation consiste en une seule couche de multiplexeurs, voire en un circuit de mise à la valeur maximale tel que vu dans le chapitre sur les opérations bits à bits.
[[File:Gestion des débordements d'entiers.png|centre|vignette|upright=2.5|Gestion des débordements d'entiers lors d'une addition non-signée.]]
Pour les additionneurs non-signés, la gestion des débordements d'entiers dépend fortement de la représentation signée. Nous allons étudier le cas du complément à deux. Si vous vous rappelez le chapitre 1, les calculs sur des nombres en complètement à deux utilisent les règles de l'arithmétique modulaire, c'est une condition nécessaire. À priori, on peut penser que dans ces conditions, les débordements d'entiers sont une chose parfaitement normale, qui nous permet d'avoir des résultats corrects. Néanmoins, certains débordements d'entiers peuvent survenir malgré tout et produire des bugs assez ennuyeux.
Si l'on tient en compte les règles du complément à deux, on sait que le bit de poids fort (le plus à gauche) permet de déterminer si le nombre est positif ou négatif : il indique le signe du nombre. Tout se passe comme si les entiers en complément à deux étaient codés sur un bit de moins, et avaient leur longueur amputé du bit de poids fort. Si le résultat d'un calcul écrase le bit de poids fort, il y a un débordement d'entiers. Il existe une règle simple qui permet de détecter ces débordements d'entiers. L'addition de deux nombres positifs ne peut pas être un nombre négatif. Si on additionne deux nombres dont le bit de signe est à 0 et que le bit de signe du résultat est à 1, on est en face d'un débordement d'entiers. Même chose pour deux nombres négatifs : le résultat de l'addition ne peut pas être positif. On peut résumer cela en une phrase : si deux nombres de même signe sont ajoutés, un débordement a lieu quand le bit du signe du résultat a le signe opposé.
Modifier les circuits d'au-dessus pour qu'ils détectent les débordements en complément à deux est simple comme bonjour : il suffit créer un petit circuit combinatoire qui prenne en entrée les bits de signe des opérandes et du résultat, et qui fasse le calcul de l'indicateur de débordements. Si l'on rédige sa table de vérité, on doit se retrouver avec la table suivante :
{|class="wikitable"
|-
!Entrées
!Sortie
|-
|000||0
|-
|001||1
|-
|010||0
|-
|011||0
|-
|100||0
|-
|101||0
|-
|110||1
|-
|111||0
|}
L'équation de ce circuit est la suivante, avec <math>S_a</math> et <math>S_b</math> les signes des deux opérandes, et <math>C_i</math> la retenue de la colonne précédente :
: <math> ( S_a . S_b . \overline{C_i} ) + ( \overline{S_a} . \overline{S_b} . C_i )</math>
En simplifiant, on obtient alors :
: <math>( S_a . S_b ) \oplus C_i</math>
Or, il se trouve que <math>S_a . S_b</math> est tout simplement la retenue en sortie du dernier additionneur, que nous noterons <math>C_o</math>. On trouve donc :
: <math>C_o \oplus C_i</math>
Il suffit donc de faire un XOR entre la dernière retenue et la précédente pour obtenir le bit de débordement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de décalage et de rotation
| prevText=Les circuits de décalage et de rotation
| next=Les circuits de comparaison
| nextText=Les circuits de comparaison
}}
</noinclude>
3xaqt3aixnd581g4m3dletiffimbn5k
768674
768673
2026-06-25T20:10:45Z
Mewtow
31375
/* L'addition non signée */
768674
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir les circuits capables de faire une addition ou une soustraction, ainsi que quelques circuits spécialisés. Précisons cependant que les fabricants de processeurs travaillent d'arrache-pied pour trouver des moyens de rendre ces circuits de calcul plus rapides et plus économes en énergie. Autant vous dire que les circuits que vous allez voir sont vraiment des circuits qui font pâle figure comparé à ce que l'on peut trouver dans un vrai processeur commercial !
==Les circuits pour additionner 2 ou 3 bits==
L'addition se fait en binaire de la même manière qu'en décimal. On additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. La soustraction fonctionne sur le même principe, sur le même modèle qu'en décimal.
[[File:Binary Addition Demonstration.svg|centre|vignette|Exemple d'addition en binaire.]]
En clair, additionner deux nombres demande d'additionner 2 bits et une retenue sur chaque colonne, et de propager les retenues d'une colonne à l'autre. La propagation des retenues est quelque chose de simple en apparence, mais qui est sujet à des optimisations extraordinairement nombreuses. Aussi, pour simplifier l'exposition, nous allons voir comment gérer une colonne avant de voir comment sont propagées les retenues.
[[File:Full Adder Block.svg|vignette|upright=1|class=transparent|Additionneur complet.]]
Si on effectue une addition en colonne, on doit additionner les deux bits sur la colonne, mais aussi additionner une éventuelle retenue. Il faut donc créer un circuit qui additionne trois bits : deux bits de données, plus une retenue. Ce circuit qui additionne trois bits est appelé un '''additionneur complet'''. Il fournit en sortie deux bits : un bit de somme et une retenue sortante. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|-
||0||0||0|| ||0||0
|-
||0||0||1|| ||0||1
|-
||0||1||0|| ||0||1
|-
||0||1||1|| ||1||0
|-
||1||0||0|| ||0||1
|-
||1||0||1|| ||1||0
|-
||1||1||0|| ||1||0
|-
||1||1||1|| ||1||1
|}
Il est possible d'utiliser un tableau de Karnaugh pour traduire la table de vérité, mais elle donne un résultat assez compliqué. La retenue et le bit de somme sont calculés à part. La retenue est calculée avec trois portes ET et une porte OU. Le bit de somme est calculé lui avec 4 portes ET, une porte OU, et trois portes NON.
[[File:Full Adler with 3 entries.png|centre|vignette|upright=2|Additionneur complet, fabriqué à partir de la table de vérité.]]
D'autres méthodes donnent des résultats plus compréhensibles. Nous allons les voir dans la suite de cette section.
===L'additionneur complet basé sur des demi-additionneurs===
[[File:1-bit half-adder.svg|class=transparent|vignette|Demi-addtionneur.]]
Nous avons déjà vu comment additionner deux bits dans le chapitre sur les incrémenteurs, mais quelques rappels ne seront pas de trop. Le '''demi-additionneur''' est un circuit qui additionne deux bits. Il implémente la table d'addition, qui est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Mais un demi-additionneur n'additionne que deux bits, il manque de quoi additionner la retenue. Une solution pour cela est d'enchaîner deux demi-additionneurs : un qui additionne les deux bits de données, et un second qui additionne la retenue au résultat du précédent. La retenue finale se calcule en combinant les sorties de retenue des deux demi-additionneurs, avec une porte OU. Le résultat est un additionneur complet fabriqué avec deux demi-additionneurs. Pour vous en convaincre, établissez la table de vérité de ce circuit, vous verrez que ça marche.
[[File:Full Adder Blocks.svg|centre|vignette|upright=2|Additionneur complet fabriqué avec deux demi-additionneurs.]]
[[File:Half-adder.svg|vignette|upright=1|Circuit d'un demi-additionneur naïf.]]
L'implémentation naïve d'un demi-additionneur utilise une porte XOR et une porte ET, comme illustré ci-contre. Le circuit s'obtient en faisant uen traduction littérale de la table de vérité du circuit. Si je dis qu'elle est naïve, c'est car il est possible de l'optimiser, de manière à éliminer des redondances cachées. Mais nous avions déjà vu cela dans le chapitre sur les incrémenteurs, aussi laissons cela pour plus tard.
En combinant deux demi-additionneurs de ce type, on obtient l'additionneur complet suivant. Les deux sorties de retenue des demi-additionneurs sont combinées avec une porte OU, pour calculer la retenue finale.
{| class="flexible"
|[[File:Full Adder Modules.svg|class=transparent|Composition d'un additionneur complet. On voit bien que celui-ci est composé de deux demi-additionneurs, en rouge et en bleu, auxquels on a ajouté une porte OU pour calculer la retenue finale.]]
|[[File:Full-adder.svg|300px|class=transparent|Circuit d'un additionneur complet.]]
|}
Le fait de combiner les deux retenues avec une porte OU n'est pas ce qu'il y a de plus intuitif. La table de vérité nous dit que ça fonctionne, mais on comprend mal pourquoi. Mais tout devient plus clair quand on sait que les deux retenues ne peuvent pas être à 1 en même temps. Même dans l'addition 1 + 1 + 1, seule une retenue est à 1. Soit l'addition des deux bits d'opérande donne naissance à une retenue, soit c'est l'addition ''résultat + retenue entrante''. La retenue sortant est donc à 1 quand une des deux addition donne une retenue. D'où l'usage d'une porte OU. Remarquez qu'une porte XOR donne le même résultat, vu que le cas où les deux retenues sont à 1 n'est jamais rencontré.
===La propagation et la génération des retenues===
L'additionneur complet que nous allons voir dans cette section sert à introduire les concepts de propagation, génération et absorption de retenue. Il s'agit de concepts très importants quand on étudie les additionneurs. l'idée part d'un principe très simple : la retenue sortante dépend de la retenue d'entrée. La relation entre les deux se résume à trois cas, qui dépendent de la valeur des deux bits additionnés, nommés A et B.
* Dans le premier cas, la retenue entrante est égale à la retenue sortante. On dit que la retenue entrante est propagée par l'additionneur.
* Dans le second cas, la retenue est forcée à 0 : la retenue sortante vaut 0, peu importe la valeur de la retenue entrante.
* Dans le troisième cas, la retenue est forcée à 1 : la retenue entrante vaut 1, peu importe la valeur de la retenue entrante.
[[File:Signaux P et G fournis par un demi-additionneur.png|vignette|Signaux P et G fournis par un demi-additionneur]]
Maintenant, créons un circuit qui nous dise si une retenue est propagée ou générée. Il a deux bits de sortie, nommés P et G : P pour ''Propagate'', G pour ''Generate''. Le bit P est à 1 si la retenue entrante est propagée, il est à 0 sinon. Le bit G est à 1 si une retenue est générée, à 0 sinon. Une retenue est considérée comme absorbée si elle n'est pas ni propagée ni générée, pas besoin d'un troisième bit pour gérer ce cas.
Il se trouve que ce circuit n'est autre qu'un demi-additionneur ! Pour vous en rendre compte, regardez la table de vérité d'un additionneur complet, illustrée ci-dessous. Lorsque les deux bits d'opérande sont à 0, la retenue sortante vaut toujours 0. Si ils sont tous deux à 1, alors la retenue sortante vaut 1. S'ils sont différents, alors retenues sortante et entrante sont égales. Le bit P est donc généré par une simple porte XOR. Quant au bit G, il est à 1 si les deux bits d'opérandes sont à 1, ce qui correspond à une porte ET. Il se trouve que ces deux portes forment un demi-additionneur !
{|class="wikitable"
|-
! Opérande 1 !! Opérande 2 !! Retenue entrante !! !! Retenue sortante
|- class="f_rouge"
||0||0||0|| ||0
|- class="f_rouge"
||0||0||1|| ||0
|- class="f_bleu"
||0||1||0|| ||0
|- class="f_bleu"
||0||1||1|| ||1
|- class="f_bleu"
||1||0||0|| ||0
|- class="f_bleu"
||1||0||1|| ||1
|- class="f_vert"
||1||1||0|| ||1
|- class="f_vert"
||1||1||1|| ||1
|}
Ensuite, créons un circuit qui prend ces deux sorties P et G, et calcule la retenue sortante en fonction. Le circuit en question a trois entrées : la retenue entrante, les deux bits P et G. La retenue sortante vaut 1 soit si une retenue est générée, soit si la retenue entrante est propagée et qu'elle vaut 1. En notant <math>{C_{in}}</math> et <math>{C_{out}}</math> les retenues entrantes et sortante, on a :
: <math>{C_{out}} = G + \left( P . C_{in} \right)</math>
Le circuit est donc composé d'une porte OU et d'une porte ET. En combinant un demi-additionneur avec le circuit de calcul de retenue sortante vu plus haut, on a :
[[File:Additionneur complet avec propagation et génération de retenue.png|class=transparent|centre|vignette|upright=2|Additionneur complet avec propagation et génération de retenue.]]
Certes, on retombe sur le circuit vu plus haut. Mais il est possible de remplacer le circuit en orange par un autre. Par exemple, il est possible de le remplacer par multiplexeur, qui choisit entre la retenue générée et la retenue entrante (propagée). Le choix se fait selon la valeur du bit P, qui chosiit entre propager la retenue et la générer.
[[File:Additionneur crée avec un multiplexeur.png|centre|vignette|upright=1.5|Additionneur crée avec un multiplexeur]]
Le circuit exact est illustré ci-dessous. Il semble utiliser plus de portes logiques que l'additionneur complet précédent. Cependant, nous verrons dans quelques chapitres qu'il est possible d'implémenter un multiplexeur avec seulement 6 transistors, voire moins ! L'implémentation utilise des portes à transmission, mais nous en reparlerons dans le chapitre sur les transistors, quand nous verrons les additionneurs à ''Manchester Carry Chain''. Au passage, une variante de ce circuit a été utilisée dans le processeur processeur 8086 d'Intel, comme on le verra dans le chapitre suivant.
[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
[[File:FulladderPG.png|vignette|upright=1|Additionneur complet avec deux sorties P et G.]]
Pour finir, sachez qu'il existe des additionneurs qui fournissent : le bit de résultat, deux sorties P et G qui indiquent si l'addition propage ou génère une retenue. Il ne s'agit pas d'additionneurs complets, car il manque de quoi calculer la retenue sortante. De tels additionneurs seront appelés des '''additionneurs partiels'''. De tels additionneurs sont utilisés dans certains additionneurs pour gagner en performance.
Enfin, il existe aussi ce que j'ai décidé d'appeler des '''additionneurs P/G''', qui sont des additionneurs complets auxquels on a ajouté deux sorties P et G, en plus de la sortie de retenue. Leur circuit est le même que celui d'un additionneur complet, auquel on a ajouté deux fils. Vu que ces deux sorties sont fournies par le premier demi-additionneur, ajouter ces deux sorties demande d'ajouter des fils, pas de portes logiques. De tels additionneurs sont aussi utilisés dans certains additionneurs pour gagner en performance.
===L'additionneur complet basé sur une porte à majorité===
Maintenant, voyons une dernière implémentation possible de l'additionneur complet. Mais avant de voir comment l'additionneur est implémenté, nous allons devoir faire un petit focus sur la retenue sortante. Reprenons la table de vérité, et regardons ce qu'il en est pour la retenue sortante uniquement.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante
|-
||0||0||0|| ||0
|-
||0||0||1|| ||0
|-
||0||1||0|| ||0
|-
||0||1||1|| ||1
|-
||1||0||0|| ||0
|-
||1||0||1|| ||1
|-
||1||1||0|| ||1
|-
||1||1||1|| ||1
|}
Vous remarquerez que la retenue sortante est égale au bit majoritaire, parmi les trois bits d'opérande. Si deux ou trois bits sont à 1, la retenue sortante vaut 1. Et réciproquement avec 0. Le circuit de calcul de la retenue peut donc être remplacé par une '''porte à majorité'''.
[[File:Additionneur crée avec une porte à majorité.png|centre|vignette|upright=2|Additionneur crée avec une porte à majorité]]
Mais une porte à majorité est assez gourmande en circuit, ce qui fait qu'un additionneur pareil ne serait pas pratique, ni utile. Du moins, ce serait le cas s'il n'y avait pas une possibilité d'optimisation extrêmement intéressante. Il est possible de calculer le 'bit de somme' à partir de la retenue sortante ! En effet, le bit de somme est l'inverse de la retenue sortante, sauf dans deux cas : les trois bits d'entrée sont à 0, où ils sont tous à 1. Le bit de somme vaut 0 dans le premier cas, 1 dans le second cas.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|- class="f_bleu"
||0||0||0|| ||0||0
|- class="f_vert"
||0||0||1|| ||0||1
|- class="f_vert"
||0||1||0|| ||0||1
|- class="f_vert"
||0||1||1|| ||1||0
|- class="f_vert"
||1||0||0|| ||0||1
|- class="f_vert"
||1||0||1|| ||1||0
|- class="f_vert"
||1||1||0|| ||1||0
|- class="f_rouge"
||1||1||1|| ||1||1
|}
En clair, 6 lignes de la table de vérité sur 8 peuvent se calculer avec une porte à majorité, le reste demandant quelques portes logiques pour faire la correction. L'implémentation en circuit demande donc trois choses :
* de calculer la retenue sortante et de quoi l'inverser ;
* un circuit qui vérifie si tous les bits opérande valent 0 : une porte NOR ou OU fait l'affaire ;
* un circuit qui vérifie s'ils valent tous 1, à savoir une porte ET.
Ensuite, on combine le résultat des trois circuits précédents pour obtenir le résultat final. La combinaison est le fait du circuit en jaune dans le schéma ci-dessous.
[[File:Full adder basé sur une modification de la retenue.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue]]
Le circuit en jaune est assez simple. Quand la porte ET sort un 1, la sortie doit être forcée à 1. Par contre, quand la porte OU sort un zéro, la sortie doit être forcée à 0. Il suffit donc d'enchainer un circuit de mise à 1 et un circuit de mise à 0. Nous avons vu dans le chapitre sur les opérations de masquage qu'il s'agit respectivement d'une porte OU et d'une porte ET.
[[File:Full adder basé sur une modification de la retenue interne, au complet.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue interne, au complet]]
Divers processeurs utilisaient des additionneurs complets de ce type : le fameux Z80, l'Intel 4004 et de l'Intel 8008 sont les exemples les plus notables. Pourtant, on pourrait s'étonner qu'un tel circuit ait existé. Il utilise beaucoup de portes logiques, a une profondeur logique supérieure : il n'a rien d'avantageux. Sauf qu'il était utilisé sur d'anciens processeurs, qui utilisaient la technologie dite TTL, différente de la technologie CMOS des transistors modernes. Et avec la technologie TTL, il est possible de fusionner plusieurs portes logiques ET et NOR en une seule porte logique ET/OU/NON ! Un additionneur complet construit ainsi ne prenait que deux portes logiques : une pour le calcul de la retenue sortante, une autre pour le reste du circuit.
==L'addition non signée==
Voyons maintenant un circuit capable d'additionner deux nombres entiers: l''''additionneur'''. Dans la version qu'on va voir, ce circuit manipulera des nombres strictement positifs. L'addition des nombres codés en complètement à deux sera vu dans une section ultérieure.
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
L'interface d'un additionneur est illustrée ci-contre. Un additionneur prend deux opérandes sur deux entrées séparées, et fournit le résultat sur une sortie dédiée. De plus, il a une entrée sur laquelle envoyer une retenue entrante, et a une sortie pour la retenue sortante. La sortie de retenue indique que le résultat de l'addition a débordé (au sens d'un débordement d'entier). Pour l'entrée de retenue, son utilité deviendra plus claire dans ce qui suit, mais souvenez-vous qu'elle existe.
L'entrée et la sortie de retenue sont parfois utilisées pour combiner plusieurs petits additionneurs, pour former un additionneur plus grand. Nous verrons de nombreux exemples dans ce qui suit. Il est assez commun de créer des additionneurs pour des opérandes de 16/32/64 bits, avec des additionneurs de 4/5 bits.
===L'additionneur série===
Il est possible d'additionner deux nombres bit par bit, grâce à un additionneur complet associé à plusieurs registres à décalages. Les opérandes sont placées chacune dans un registre à décalage, et l'additionneur complet utilise les bits sortants de ces registres à décalage. Le bit du résultat est envoyé au registre à décalage pour le résultat. La retenue de l'addition est stockée dans une bascule de 1 bit, en attente du prochain cycle d'horloge. Un tel additionneur est appelé un '''additionneur série'''. Il a été utilisé sur d'anciens ordinateurs dans les années 50-60, aussi bien des prototypes que des ordinateurs commerciaux.
[[File:Additionneur série.jpg|centre|vignette|upright=2.5|Additionneur série.]]
===L'additionneur à propagation de retenue===
L''''additionneur à propagation de retenue''' est aussi appelé l'additionneur ''ripple carry'', c'est son nom anglais. J'utiliserais occasionnellement le nom anglais dans ce qui suit, car celui-ci est plus court. Ilutilise un additionneur complet pour chaque colonne de l'addition. Pour gérer les retenues, il suffit ainsi de câbler des additionneurs complets les uns à la suite des autres, chacun envoyant sa retenue à la colonne suivante.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Notons la présence de la retenue sortante, qui est utilisée pour détecter les débordements d'entier, ainsi que pour d'autres opérations. Le bit de retenue final est souvent stocké dans un registre spécial du processeur (généralement appelé ''carry flag'').
Notez aussi, à droite du schéma précédent, l'entrée de retenue <math>\text{R}_\text{Entrée}</math>. Elle est très utile pour l'implémentation de certaines opérations comme l'inversion de signe, la soustraction, l'incrémentation, etc. Certains processeurs sont capables de faire une opération appelée ADC, ADDC ou autre nom signifiant ''Addition with Carry'', qui permet de faire le calcul A + B + Retenue (la retenue en question est la retenue sortante de l'addition précédente, stockée dans le registre ''carry flag''). Son utilité principale est de permettre des additions d'entiers plus grands que ceux supportés par le processeur. Par exemple, cela permet de faire des additions d'entiers 32 bits sur un processeur 16 bits.
[[File:Adder Network 3 Bit Sum Delay 003.svg|vignette|upright=0.5|Propagation de retenue dans l'additionneur.]]
Cet additionneur utilise très peu de portes logiques, ce qui fait qu'il était utilisé sur certains processeurs 8 et 16 bits, dont le budget en portes logiques était limité. Bien que très simple, cet additionneur est cependant peu performant. Son temps de calcul est proportionnel à la taille des opérandes. Par exemple, additionner deux opérandes de 32 bits prendra deux fois plus de temps que pour des opérandes de 16 bits. La raison est que les retenues doivent se propager à travers le circuit, du premier additionneur jusqu'au dernier.
L'addition étant une opération fréquente, d'autres additionneurs ont été inventés. Ils utilisent des optimisations qui utilisent plus de circuits pour gagner en rapidité. Mais avant de voir ces optimisations, nous allons voir s'il est possible d'optimiser les additionneurs à propagation de retenue.
===L'additionneur à saut de retenue===
L'additionneur à propagation de retenue peut être optimisé de deux manières. La première accélère la propagation de la retenue, en agissant au niveau des additionneurs complets. La ''Manchester carry chain'' est une optimisation de ce type, mais on ne peut pas encore expliquer à ce stade du cours, car elle optimisait les additionneurs complets au niveau des transistors eux-mêmes. Elle a été utilisée sur de nombreux processeurs connus, comme le 8086 d'Intel ou le Z80.
La seconde manière rend l'addition plus rapide dans certains cas. Le résultat est un additionneur dont le temps de calcul est variable. Le calcul prendra quelques cycles d'horloges avec certains opérandes, mais d'autres opérandes ne verront aucune amélioration. Il n'améliore pas le pire des cas, dans lequel la retenue doit être propagée du début à la fin, du bit de poids faible au bit de poids fort. Mais dans d'autres cas, il permet d'avoir le résultat en avance. L'optimisation en question donne l''''additionneur à saut de retenue''' (''carry-skip adder'').
Il est composé en enchainant plusieurs additionneurs plus petits, qui additionnent 4/5 bits, rarement plus. De tels additionneurs de petite taille sont appelés des '''blocs''', dans ce qui suit. L'idée est de détecter précocement si la retenue entrante est propagée à travers tout le bloc. Si la retenue est propagée dans tous le bloc, on peut directement l'envoyer sur la sortie de retenue, la retenue saute le bloc entier. Dans le cas contraire, on doit calculer la retenue normalement. Le choix entre les deux est le fait d'un multiplexeur. Toute la difficulté est de savoir comment commander le multiplexeur.
[[File:Carry skip adder, principe de base.png|centre|vignette|upright=2.5|Carry skip adder : principe de base]]
Pour savoir si une retenue est propagée dans un bloc entier, il faut utiliser des additionneurs complets, qui ont en plus une sortie P indiquant que l'additionneur propage la retenue. Le bloc propage une retenue si chaque additionneur complet propage la retenue, donc s'ils ont tous leur sortie P à 1. Un simple ET logique suffit à déterminer si c'est le cas. Le signal de commande du multiplexeur est donc un vulgaire ET entre toutes les sorties P des additionneurs complets du bloc.
[[File:Additionneur à saut de retenue.png|centre|vignette|upright=2.5|Calcul de la commande du MUX.]]
Voici ce que ça donne pour un bloc de 4 bits :
[[File:CSAdder4Bit.svg|centre|vignette|upright=1.5|Additionneur ''carry skip'' : bloc de 4 bits.]]
L'additionneur à saut de retenue est construit en assemblant plusieurs blocs de ce type.
[[File:BCSAdder16Bit.svg|centre|vignette|upright=3|Additionneur à saut de retenue.]]
Les blocs sont tous identiques dans le cas le plus simple, mais il est possible d'utiliser des blocs de taille variable. Par exemple, le premier bloc peut avoir des opérandes de 6 bits, le second des opérandes de 7 bits, etc. Faire ainsi permet de gagner un petit peu en performances, si la taille de chaque bloc est bien choisie. La raison est une question de temps de propagation des retenues. La retenue met plus de temps à se propager à travers 8 blocs qu'à travers 4, ce qui prend plus de temps qu'à travers 2 blocs, etc. En tenir compte fait que la taille des blocs tend à augmenter ou diminuer quand on se rapproche des bits de poids fort.
===L'additionneur à sélection de retenue===
L''''additionneur à sélection de retenue''' utilise aussi des blocs, comme les additionneurs précédents. L'addition se fait en deux versions : une avec la retenue du bloc précédent valant zéro, et une autre version avec la retenue du bloc précédent valant 1. Il suffira alors de choisir le bon résultat avec un multiplexeur, une fois cette retenue connue. On gagne ainsi du temps en calculant à l'avance les valeurs de certains bits du résultat, sans connaître la valeur de la retenue. Petit détail : sur certains additionneurs à sélection de retenue, les blocs de base n'ont pas la même taille. Cela permet de tenir compte des temps de propagation des retenues entre les blocs.
[[File:Additionneur à sélection de retenue.png|centre|vignette|upright=1.5|Additionneur à sélection de retenue avec seulement deux blocs.]]
Dans les exemples du dessus, chaque sous-additionneur étaient des additionneurs à propagation de retenue. Mais ce n'est pas une obligation, et tout autre type d’additionneur peut être utilisé. Par exemple, on peut faire en sorte que les sous-additionneurs soient eux-mêmes des additionneurs à sélection de retenue, et poursuivre ainsi de suite, récursivement. On obtient alors un '''additionneur à somme conditionnelle''', plus rapide que l'additionneur à sélection de retenue, mais qui utilise beaucoup plus de portes logiques.
===Les additionneurs à anticipation de retenue===
Les '''additionneurs à anticipation de retenue''' calculent chaque retenue sans avoir à propager les retenues précédentes. Au lieu de calculer les retenues une par une, ils calculent toutes les retenues en parallèle, à partir des bits des opérandes. Une fois les retenues pré-calculées, il suffit de les additionner avec les deux bits adéquats, pour obtenir le résultat. Ces additionneurs sont composés de deux parties :
* un circuit qui pré-calcule les retenues, sans les propager, directement à partir des opérandes ;
* d'une couche d'additionneurs complets, qui additionnent chacun deux bits d'opérande et la retenue pré-calculée.
[[File:Additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Additionneur à anticipation de retenue.]]
Le calcul des retenues se fait en deux étapes. La première détermine si les retenues sont propagées ou générées, pour chaque colonne. Elle détermine, pour chaque colonne, les bits P et G qui indiquent respectivement : qu'une retenue a été générée sur cette colonne, que la colonne propage la retenue précédente. La seconde étape utilise ces signaux P et G pour déterminer la retenue sur chaque colonne. Le circuit de calcul des retenues est donc composé de deux parties : une couche de demi-additionneurs pour générer les signaux P et G, un circuit de calcul des retenues proprement dit.
[[File:Circuit complet d'un additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Circuit complet d'un additionneur à anticipation de retenue.]]
Pour rappel, la retenue de la colonne i, notée <math>C_i</math> est égale à :
: <math>G_i + (P_i . C_{i-1})</math>, avec G le signal de génération de retenue, P le signal de propagation de retenue.
Si on utilisait cette formule sans trop réfléchir, on retomberait sur un additionneur à propagation de retenue inutilement compliqué. L'astuce des additionneurs à anticipation de retenue consiste à remplacer le terme Ci−1 par sa valeur calculée avant. Par exemple, je prends un additionneur 4 bits. Je dispose de deux nombres A et B, contenant chacun 4 bits : A3, A2, A1, et A0 pour le nombre A, et B3, B2, B1, et B0 pour le nombre B. Si j'effectue les remplacements, j'obtiens les formules suivantes :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Ces formules nous permettent de déduire la valeur d'une retenue directement : il reste alors à créer un circuit qui implémente ces formules, et le tour est joué.
[[File:Four bit adder with carry lookahead.svg|centre|vignette|upright=1.5|Additionneur à anticipation de retenue de 4 bits.]]
Plus haut, j'ai dit que le circuit était composé de trois couches : une couche de demi-additionneur pour les signaux P et G ; un circuit d'anticipation de retenue, puis l'addition finale. Mais les deux bits d'opérandes sont déjà additionnés dans la couche de demi-additionneur, autant réutiliser le résultat pour l'additionner à la retenue. Pour éliminer toute redondance de ce type, il est possible d'utiliser des additionneurs partiels (des additionneurs où la sortie de retenue a été remplacée par deux sorties P et G). L'additionneur final est alors composé de plusieurs additionneurs complets de ce type, couplé à un circuit d'anticipation de retenue.
[[File:Cla4bits.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue de 4 bits.]]
Les additionneurs à anticipation de retenue sont plus rapides que les additionneurs à propagation de retenue. La raison est que les retenues sont calculées rapidement, dans un temps plus petit qu'en propageant la retenue. Pour rentrer dans les détails mathématiques, le temps de calcul n'est pas proportionnel au nombre de bits, mais proportionnel au logarithme de ce nombre de bits. Par contre, le gain en temps de calcul s'accompagne d'un cout en portes logiques conséquent. Le circuit qui calcule les retenues anticipées utilise beaucoup de portes logiques.
===Les additionneurs à calcul parallèle de préfixe===
Du fait du cout en portes logiques, utiliser un additionneur à anticipation de retenue est une solution pratique pour additionner 4 ou 8 bits. Mais pour des opérandes plus grands, de 16 ou 32 bits, cela utiliserait trop de portes logiques. Une partie de ce cout en portes logiques est cependant lié à des redondances. Si on analyse deux retenues différentes, à des colonnes différentes. Les circuits qui calculent ces deux retenues seront différents, mais ils contiendront des sous-circuits identiques. Pour vous en rendre compte, reprenons les quatre équations obtenues précédemment et essayez de trouver des redondances :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Vous devriez trouver :
* le terme ''P0 · C0'' dans toutes les lignes ;
* les termes ''P1 · P0 · C0'' et ''P1 · P0'' dans les trois dernières lignes ;
* le terme ''P2 · P1 · P0 · C0'' ; ''P2 · P1 · G0'' et ''P2 · G1'' dans les deux dernières lignes.
Il existe plusieurs manières d'exploiter de telles redondances, exploitées dans les '''additionneurs à calcul parallèle de préfixe'''. L'optimisation apportée est de générer des signaux ''propagate'' et ''generate'' pour des groupes de 2, 3, 4, ..., N colonnes. Par exemple, il est possible de générer un signal ''P 0 vers 3'', qui indique si la retenue de la première colonne est propagée jusqu'à la 4ème colonne. Un autre exemple est un signal qui indique qu'une retenue a été générée entre les colonnes 4 à 7 génèrent une retenue ou non. En clair, les signaux P et G ont maintenant un intervalle, qui précise de quelle colonne vers quelle colonne se fait la propagation, ou entre quelles colonnes se fait la génération.
Il est possible de combiner les signaux P et G de deux groupes de colonne, s'ils sont contiguës (ils peuvent aussi se recouvrir). Par exemple, il est possible de calculer les bits P et G pour les colonnes 0 à 10, à partir des deux signaux P/G des colonnes 0-4 et 5-10. Pour cela, les équations sont assez simples. Si je prends deux groupes nommés A et B, avec A pour les colonnes de poids fort et B celles de poids faible, on a :
: <math>P_{sortie} = P_A . P_B</math>
: <math>G_{sortie} = G_A . P_A . G_B</math>
Pour les bits des colonnes 2, 4, 8, 16, 32, 64, et autres, le calcul est simple. L'idée est de grouper les colonnes par groupes de 2. Puis, on calcule chaque groupe de 4 bits à partir de deux groupes de 2. Puis les groupes de 8 bits sont calculés à partir de deux groupes de 4 bits, et ainsi de suite pour toute puissance de deux.
[[File:Squelette d'un additionneur à calcul parallèle de préfixe.jpg|centre|vignette|upright=2|Squelette d'un additionneur à calcul parallèle de préfixe]]
Les seules difficultés surviennent pour les colonnes dont le nombre n'est pas une puissance de deux. Il y a plusieurs manières pour subdiviser ces colonnes en intervalles plus petits et combiner le tout. Et elles donnent chacune des additionneurs différents. Ils portent les noms d'''additionneur de Ladner-Fisher'', d'''additionneur de Brent-Kung'', d'''additionneur de Kogge-Stone'', etc. Dans le cas général, l'additionneur de Kogge-Stone est un des plus rapide, mais aussi un des plus gourmand en circuits, alors que c'est l'inverse pour l'additionneur de Brent-Kung. Les deux additionneurs sont illustrés ci-dessous, pour des opérandes de 8 bits. Dans ceux-ci, chaque losange calculent les bits P et G, à partir des signaux P et G de deux groupes précédents.
{|
|[[File:Brent-kung-8-bit.png|vignette|Brent-kung-8-bit]]
|[[File:Kogge-stone-8-bit.png|vignette|upright=1.2|Kogge-stone-8-bit]]
|}
===Le calcul parallèle de la retenue===
L'anticipation de retenue est très rapide, mais utilise beaucoup de circuits, même en utilisant le calcul parallèle de préfixe. Les additionneurs à propagation de retenue font eux le compromis inverse. Mais il existe des intermédiaires, qui visent à obtenir un compromis entre performance et cout en portes logiques.
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Un de ces compromis découpe un additionneur de 16/32/64 bits en additionneurs de 4/5 bits, qui sont enchainés en utilisant la propagation de retenue. Par contre, ces petits additionneurs de 4/5 bits utilisent l'anticipation de retenue. Ces additionneurs simples seront nommés ''blocs'' dans ce qui suit, et l'un d'entre eux est illustré ci-contre. Par exemple, on peut utiliser 4 additionneurs à anticipation de retenue de 4 bits, et propager les retenues entre eux. Le premier bloc calcule une retenue, qui est propagée au second bloc. Le second bloc calcule lui aussi une retenue, qui est propagée au troisième bloc, etc.
[[File:4008 Functional Diagram.svg|vignette|upright=1|4008 Functional Diagram]]
Le gain en performance est significatif, mais il est possible de faire presque aussi rapide, tout en économisant beaucoup de circuits. L'idée est de simplifier les additionneurs 4 bits, afin de ne pas utiliser une anticipation de retenue complète. L'anticipation de retenue n'est utilisée que pour la retenue sortante, alors que les bits de somme sont calculées avec propagation de retenue. La retenue sortante est donc calculée en parallèle de l'addition. Chaque bloc contient, à côté d'un additionneur proprement dit, un circuit qui calcule la retenue sortante. Il existe de nombreuses manières de calculer la retenue sortante.
Le gain en portes logiques est assez significatif. En effet, on se passe de circuits d'anticipation de retenue pour les 4 bits de somme, on ne garde que celui pour la retenue sortante. Le fait d'utiliser la propagation de retenue pour calculer les 4 bits de somme est censé avoir un léger cout en performance, mais il se trouve que ce cout est très faible. En effet, le résultat n'est connu qu'une fois les retenues propagées jusqu'au dernier bloc. Et malgré l'anticipation des retenues, la propagation est assez lente. les additionneurs à propagation de retenue auront terminé leur travail bien avant.
Un exemple est celui de l'additionneur CMOS 4008, un additionneur de 4 bit. Il est composé en trois sections. Une première couche de demi-additionneurs calcule les signaux P et G utilisés à la fois pour l'anticipation de la retenue sortante, que pour calculer les bits de somme. Le circuit de calcul de la retenue sortante utilise l'anticipation de retenue. Le reste du circuit fait l'addition en propageant les retenues.
[[File:CMOS 4008, circuit découpé en sections.png|centre|vignette|upright=1.5|CMOS 4008, circuit découpé en sections]]
===Les additionneurs à anticipation de retenue multi-niveau===
Les additionneurs précédents mixent anticipation et propagation de retenue. Ceux que nous allons voir utilisent des blocs, mais utilisent l'anticipation de retenue entre les blocs. Les blocs utilisés peuvent être un additionneur à propagation de retenue, ou à anticipation de retenue, peu importe. Ce qui compte est que la retenue entrante de chaque bloc est calculée par anticipation de retenue.
Par exemple, il est possible de créer un additionneur 16 bits en utilisant 4 additionneurs/blocs de 4 bits, couplé à un circuit d’anticipation de retenue par bloc. Pour cela, les additionneurs de 4 bits doivent fournir deux sorties P et G. Les deux sorties indiquent que le bloc soit a généré une retenue, soit a propagé la retenue entrante. Par propagé, on veut dire que la retenue entrante du bloc s'est retrouvée sur la sortie de retenue du bloc. C'est le cas si les 4 colonnes propagent la retenue, ce qui signifie que :
: <math>P_\text{4 bits} = P_0 \cdot P_1 \cdot P_2 \cdot P_3</math>
Pour la génération, cela veut dire qu'une colonne a générée une retenue, qui a été propagée jusqu'à la sortie. En clair, pour chaque colonne, il faut que le G = 1, et que les signaux P des colonnes soient à 1. Pour les 4 colonnes, cela donne :
: <math>G_\text{4 bits} = G_3 + G_2 \cdot P_3 + G_1 \cdot P_3 \cdot P_2 + G_0 \cdot P_3 \cdot P_2 \cdot P_1</math>
[[File:Cla4bitsPG.png|centre|vignette|upright=2|Additionneur à anticipation de retenue de 4 bits.]]
Pour combiner 4 blocs de 4 bits, on devra donc combiner 4 bits P et G, avec un circuit d'anticipation de retenue.
[[File:16-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 16 bits.]]
Il est même possible d'aller plus loin et de combiner 4 additionneurs précédents pour obtenir un additionneur 64 bits.
[[File:64-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 64 bits.]]
Le résultat est un additionneur à plusieurs niveaux d’anticipation de retenue. Une anticipation de retenue dans un blocs de 4 bits (facultative), entre blocs de 4 bits, entre blocs de 16 bits. La même logique peut être utilisée avec des blocs de taille différente de 4, 16 et 64 bits.
==L'addition signée et la soustraction==
Après avoir vu l'addition, il est logique de passer à la soustraction, les deux opérations étant très proches. Si on sait câbler une addition entre entiers positifs, câbler une soustraction n'est pas très compliqué. De plus, la soustraction permet de faire des additions de nombres signés.
===Le soustracteur pour opérandes entiers===
Pour soustraire deux nombres entiers, on peut adapter l'algorithme de soustraction utilisé en décimal, celui que vous avez appris à l'école. Celui-ci ressemble fortement à l'algorithme d'addition : on soustrait les bits de même poids, et on propage éventuellement une retenue sur la colonne suivante. À la différence de l'addition, la retenue est soustraite, et non ajoutée. La table de soustraction nous dit quel est le résultat de la soustraction de deux bits. La voici :
* 0 - 0 = 0 ;
* 0 - 1 = 1 et une retenue ;
* 1 - 0 = 1 ;
* 1 - 1 = 0.
[[File:Algebra1 05 fig014.svg|centre|vignette|Soustraction en binaire, avec les retenues en rouge.]]
La table de soustraction peut servir de table de vérité pour construire un circuit qui soustrait deux bits. Celui-ci est appelé un '''demi-soustracteur'''. Il ressemble beaucoup à un demi-additionneur, les différences se résumant à une porte NON ajoutée pour le calcul de la retenue.
[[File:Half Subtractor Vektor.svg|centre|vignette|Demi-soustracteur.]]
Comme pour l'additionneur, deux demi-soustracteurs peuvent être combinés pour donner un '''soustracteur complet'''. Le calcul de la retenue se fait en combinant les deux retenues des demi-soustracteurs avec une porte OU. Les soustracteurs complets sont utilisés pour créer des soustracteurs à propagation de retenue ou tout autre circuit soustracteur, sur le même modèle que les additionneurs.
[[File:FullSubtractor.svg|centre|vignette|upright=2|Soustracteur complet.]]
Vous l'aurez sans doute remarqué, mais le demi-soustracteur ressemble beaucoup au demi-additionneur. La seule différence est une porte NON ajoutée au bon endroit dans le demi-soustracteur.
{|
|[[File:Half-adder.svg|vignette|Demi-additionneur]]
|[[File:Half Subtractor Vektor.svg|vignette|upright=1.3|Demi-soustracteur]]
|}
La conséquence est qu'il est possible de créer un circuit capable de faire à la fois des additions et des soustractions. L'idée est de remplacer la porte NON du demi-soustracteur par un inverseur commandable. Si on envoie un zéro sur l'entrée de commande, l'inverseur commandable se comportera comme une porte OUI. Le circuit sera alors identique à un demi-additionneur. Si on envoie un 1 sur l'entrée de commande, l'inverseur commandable se comportera comme une porte NON. le circuit sera alors un demi-soustracteur. Il faut faire cela pour chaque demi-soustracteur, ce qui donne ceci pour un soustracteur complet. le résultat est alors appelé un '''additionneur-soustracteur complet'''.
[[File:Additionneur-soustracteur complet.png|centre|vignette|upright=2|Additionneur-soustracteur complet]]
===L'additionneur-soustracteur pour opérandes codées en complément à deux===
Étudions le cas de la soustraction en complément à deux, dans l'objectif de créer un circuit soustracteur. Vous savez sûrement que a−b et a+(−b) sont deux expressions équivalentes. Et en complément à deux, − b = not(b) + 1. Dit autrement, a − b = a + not(b) + 1. On pourrait se dire qu'il faut deux additionneurs pour faire le calcul, mais la majorité des additionneurs possède une entrée de retenue pour incrémenter le résultat de l'addition. Un soustracteur en complément à deux est donc simplement composé d'un additionneur et d'un inverseur.
[[File:Additionneur soustracteur.png|centre|vignette|upright=2|Soustracteur en complément à deux.]]
Il est possible de créer un circuit capable d'effectuer soit une addition, soit une soustraction : il suffit de remplacer l'inverseur par un inverseur commandable, qui peut être désactivé. On a vu comment créer un tel inverseur commandable dans le chapitre sur les circuits combinatoires. On peut remarquer que l'entrée de retenue et l'entrée de commande de l'inverseur sont activées en même temps : on peut fusionner les deux signaux en un seul.
[[File:Additionneur-soustracteur en complément à deux.png|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux.]]
Une implémentation alternative est la suivante. Elle remplace l'inverseur commandable par un multiplexeur.
[[File:4-bit ripple carry adder-subtracter.svg|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux, version alternative.]]
===L'additionneur-soustracteur pour opérandes codées en signe-magnitude===
Passons maintenant aux nombres codés en signe-valeur absolue, les deux opérandes étant notées A et B. Suivant les signes des deux opérandes, on a quatre cas possibles : A + B, A − B (B négatif), −A + B (A négatif) et −A − B (A et B négatifs). Une astuce est que le circuit n'a besoin que de calculer A + B et A − B : il peut les inverser pour obtenir − A − B ou B − A. A + B et A − B peuvent se calculer avec un additionneur-soustracteur, reste à corriger le résultat. Il suffit de lui ajouter un inverseur commandable pour obtenir le circuit d'addition finale.
[[File:Additionneur en signe-valeur absolue.png|centre|vignette|upright=2|Additionneur en signe-valeur absolue.]]
Toute la difficulté tient dans le calcul du bit de signe du résultat, quand interviennent des soustractions. Autant l'addition de deux nombres de même signe (A + B et −A − B) ne pose aucun problème, autant les soustractions posent problème (A − B et −A + B). Suivant que <math>A<B</math> ou que <math>A>B</math>, le signe du résultat ne sera pas le même. Déterminer le signe du résultat se fait en regardant les bits de débordement d'entier, comme on le verra plus bas.
===L'additionneur-soustracteur pour opérandes codées en représentation par excès===
Passons maintenant aux nombres codés en représentation par excès. On pourrait croire que ces nombres s'additionnent comme des nombres non-signés, mais ce serait oublier la présence du biais, qui pose problème. Dans les cas de nombres signés gérés avec un biais, voyons ce que donne l'addition de deux nombres :
: <math>( n_1 + biais ) + ( n_2 + biais ) = n_1 + n_2 + 2 \times biais</math>
Or, le résultat correct serait :
: <math>n_1 + n_2 + biais</math>
En effectuant l'addition telle quelle, le biais est compté deux fois. On doit donc le soustraire après l'addition pour obtenir le résultat correct.
Même chose pour la soustraction qui donne ceci :
: <math>(n_1 + biais) - (n_2 + biais) = n_1 - n_2</math>
Or, le résultat correct serait :
: <math> ( n_1 - n_2 ) + biais </math>
Il faut rajouter le biais pour obtenir l'exposant correct.
On a donc besoin de deux additionneurs/soustracteurs : un pour additionner/soustraire les représentations binaires des opérandes, et un autre pour ajouter/retirer le biais en trop/manquant.
==L'additionneur BCD==
Maintenant, voyons un additionneur qui additionne deux entiers au format BCD. Pour cela, nous allons devoir passer par deux étapes. La première est de créer un circuit capable d'additionneur deux chiffres BCD. Ensuite, nous allons voir comment enchaîner ces circuits pour créer un additionneur BCD complet.
===L'additionneur BCD qui fait l'opération chiffre par chiffre===
Nous allons commencer par voir un additionneur qui additionne deux chiffres en BCD, une sorte d'équivalent BCD de l'additionneur complet. Il fournit un résultat sur 4 bits et une retenue qui est mise à 1 si le résultat dépasse 10 (la limite d'un chiffre BCD). Les deux opérandes sont des chiffres BCD codés sur 4 bits et sont additionnés en binaire par un additionneur des plus normaux, similaire à ceux vus plus haut. Le résultat est alors un entier codé en binaire, sur 5 bits, qu'on corrige/convertit pour obtenir un chiffre BCD et une retenue sortante.
Pour corriger le résultat, une idée intuitive serait de prendre le résultat et de faire une division par 10. Le quotient donne la retenue, alors que le reste est le résultat, le chiffre BCD. Mais un circuit diviseur par 10 utilise beaucoup de portes logiques, ce qui ne vaut pas le coup. Une autre méthode détecte si le résultat est égal ou supérieur à 10, ce qui correspond à un "débordement" (on dépasse les limites d'un chiffre BCD). Si le résultat est plus petit que 10, il n'y a rien à faire : le résultat est bon et la retenue est de zéro. Par contre, si le résultat vaut 10 ou plus, il faut corriger le résultat et générer une retenue à 1.
Il faut donc ajouter un circuit qui détecte si le résultat est supérieur à 9, qui calcule directement la retenue. Ce circuit peut se fabriquer simplement à partir de sa table de vérité, ou en utilisant les techniques que nous verrons dans un chapitre ultérieur sur les comparateurs. La solution la plus simple est clairement d'utiliser la table de vérité, ce qui est très simple, assez pour être laissé en exercice au lecteur. Pour comprendre comment corriger le résultat, établissons une table de vérité qui associe le résultat et le résultat corrigé. L'entrée vaut au minimum 10 et au maximum 9 + 9 = 18. On considère la sortie comme un tout, la retenue étant un 5ème bit, le bit de poids fort.
{|class="wikitable"
|-
! colspan="5" | Entrée
!
! rowspan="10" |
! Retenue
! Résultat corrigé (sans retenue)
! interprétation de la sortie en binaire (retenue inclue)
|-
| 0 || 1 || 0 || 1 || 0
| (10)
| 1 || 0000
| (16)
|-
| 0 || 1 || 0 || 1 || 1
| (11)
| 1 || 0001
| (17)
|-
| 0 || 1 || 1 || 0 || 0
| (12)
| 1 || 0010
| (18)
|-
| 0 || 1 || 1 || 0 || 1
| (13)
| 1 || 0011
| (19)
|-
| 0 || 1 || 1 || 1 || 0
| (14)
| 1 || 0100
| (20)
|-
| 0 || 1 || 1 || 1 || 1
| (15)
| 1 || 0101
| (21)
|-
| 1 || 0 || 0 || 0 || 0
| (16)
| 1 || 0110
| (22)
|-
| 1 || 0 || 0 || 0 || 1
| (17)
| 1 || 0111
| (23)
|-
| 1 || 0 || 0 || 1 || 0
| (18)
| 1 || 1000
| (24)
|}
En analysant le tableau, on voit que pour corriger le résultat, il suffit d'ajouter 6. La raison est que le résultat déborde d'un nibble à 16 en binaire, mais à 10 en décimal : il suffit d'ajouter la différence entre les deux, à savoir 6, et le débordement binaire fait son travail. Donc, la correction après une addition est très simple : si le résultat dépasse 9, on ajoute 6.
On peut maintenant implémenter l'additionneur BCD, en combinant le comparateur avec 10, le circuit de correction, et l'additionneur. La première solution calcule deux versions du résultat : la version corrigée, la version normale. Le choix entre les deux est réalisée par un multiplexeur, commandé par le comparateur.
[[File:Additionneur BCD.png|centre|vignette|upright=1.5|Additionneur BCD]]
L'autre solution utilise un circuit commandable qui soit additionne 6, soit ne fait rien. Le choix entre les deux est commandé par le bit calculé par le comparateur.
[[File:Additionneur BCD, seconde version.png|centre|vignette|upright=1.5|Additionneur BCD, seconde version.]]
Une version alternative du circuit précédent est la suivante. Il contient deux additionneurs : un pour additionner les deux chiffres BCD, un autre pour additionner 6 si besoin. Le résultat du comparateur est directement utilisé pour générer l'opérande du second additionneur : 0 ou 6. Le circuit est simple à concevoir, mais gaspille beaucoup de circuit. Idéalement, il vaudrait mieux utiliser un circuit combinatoire d'addition avec une constante.
[[File:Adder 4 Bit BCD.svg|centre|vignette|upright=2|Additionneur BCD, circuit complet.]]
Pour obtenir un additionneur BCD complet, il suffit d’enchaîner les additionneurs précédents, comme on le ferait avec les additionneurs complets dans un additionneur à propagation de retenue. Au final, l'additionneur BCD est beaucoup plus compliqué qu'un additionneur normal, car il rajoute un comparateur ">9", un petit additionneur pour ajouter 6 et éventuellement d'autres circuits. De plus, il est difficile d'appliquer les optimisations disponibles sur les additionneurs non-BCD. Notamment, les circuits d'anticipation de retenue sont totalement à refaire et le résultat est relativement compliqué. C'est ce qui explique pourquoi le BCD a progressivement été abandonné au profit du binaire simple.
La soustraction en BCD se fait comme en binaire : le nombre à soustraire est remplacé par son complément, le circuit additionne le complément et l'autre opérande, le débordement d'entier fait que le résultat marche. Sauf qu'ici, le complément est un complément à 9. Il se calcule chiffre par chiffre : chaque chiffre est remplacé par (9 - le chiffre en question).
===L'additionneur BCD par ajustement décimal===
L'additionneur BCD précédent effectuait son travail chiffre BCD par chiffre BCD, mais il existe des additionneurs BCD qui font autrement. Sur les premiers processeurs x86, il n'y avait pas d'opération d'addition BCD proprement dit, seulement une addition binaire normale de 8, 16 ou 32 bits. Par contre, elle était secondée par une opération dite d''''ajustement décimal''' qui transformait un nombre binaire en nombre codé en BCD. L'opération d'ajustement décimal prenait un opérande de 8 bits codé en binaire et fournissait un résultat de la même taille, c'est à dire deux chiffres BCD. Effectuer une addition BCD demandait donc de faire deux opérations à la suite : une addition binaire simple, suivie par l'opération d'ajustement décimal. Cela permettait de gérer des nombres entiers en binaire usuel et des entiers BCD sans avoir deux instructions d'addition séparées pour les deux, sans compter que cela simplifiait aussi les circuits d'addition.
L'ajustement décimal s'effectue en ajoutant une constante bien précise à l'opérande à convertir en BCD. L'idée est que la constante est découpée en morceaux de 4 bits, correspondant chacun à un chiffre BCD de l'opérande, chaque morceau contenant soit un 0, soit 6. Cela permet d'ajouter soit 0, soit 6, à chaque chiffre BCD, et donc de le corriger. La propagation des retenues d'un chiffre à l'autre est effectuée automatiquement par l'addition binaire de la constante. L'opération d'ajustement décimal calcule automatiquement la constante. Elle découpe l'opérande en ''nibbles'', vérifie si chaque ''nibble'' est supérieur ou égal à 10, puis détermine la valeur de chaque ''nibble'' de la constante finale. Par exemple, si je prends l'opérande 1001 1110, le ''nibble'' de poids faible déborde, alors que celui de poids fort non. La constante sera donc 0000 0110 : 0x06. Inversement, si le ''nibble'' de poids fort déborde et pas celui de poids faible, la constante sera alors 0x60. Et la constante est de 0x66 si les deux ''nibbles'' débordent, de 0x00 si aucun ne déborde.
Le circuit d’ajustement décimal est donc composé de trois étapes : deux étapes pour calculer la constante, et un circuit d'addition pour additionner cette constante au nombre de départ. La première étape découpe l'opérande en morceaux de 4 bits, en chiffres BCD, et vérifie si chacun d'entre eux vaut 10 ou plus. La seconde étape prend les résultats de la première étape, et les combine pour calculer la constante. Enfin, on trouve l'addition finale, qui était réalisée par un circuit d'addition utilisé à la fois pour l'ajustement décimal et l'addition binaire. La différence entre une addition normale et une opération d'ajustement décimal tient dans le fait que les deux premières étapes sont désactivées dans une addition normale.
[[File:Additionneur BCD parallèle.png|centre|vignette|upright=1.5|Additionneur BCD parallèle]]
===L'additionneur biquinaire===
Les entiers BCD ne sont qu'un des encodages hybrides entre décimal et binaire. L'encodage biquinaire est l'un d'entre eux et nous allons faire un rappel rapide à ce sujet. Pour simplifier, un chiffre encodé en biquinaire est composé de deux parties : un bit, couplé à une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits (0, 1, 2, 3 et 4). Le bit indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9.
Additionner deux nombres de biquinaire demande donc d'additionner deux parties quinaires encodées en ''one-hot'' et d'additionner deux bits. Mais attention : il faut tenir compte de la retenue de l'addition des parties quinaires. Et idéalement, il faut aussi tenir compte d'une retenue entrante, provenant de l'addition de la colonne de chiffres précédente. Toute la difficulté vient de la création de l'additionneur ''one-hot''. Heureusement, vu qu'il n'y a que 4-5 bits à additionner, il est souvent fabriqué à partir de sa table de vérité.
[[File:Additionneur bi-quinaire.png|centre|vignette|upright=2|Additionneur bi-quinaire]]
Un avantage du biquinaire est que le calcul du complément à 9 est très simple. Il faut pour cela : inverser la partie binaire avec une porte NON, puis inverser l'ordre des bits de la partie quinaire. Concrètement, le bit de poids faible devient le bit de poids fort, et ainsi de suite. Par exemple, une partie quinaire 01000 devient 00010, 10000 devient 00001, 00100 ne change pas, etc. Le tout peut se calculer avec une porte NON et 5 multiplexeurs.
===L'additionneur BCD avec calculs intermédiaires en biquinaire===
L'ordinateur IBM 1401, un ancien ''mainframe'' des années 60, utilisait un additionneur BCD un peu particulier. Les nombres étaient encodés en BCD dans la mémoire de l'ordinateur, mais les circuits de calcul utilisaient la représentation biquinaire. Lors d'un calcul, le processeur de l'ordinateur traduisait les chiffres BCD en représentation biquinaire, faisait une addition en biquinaire, avant de traduire le résultat en BCD normal.
Pour être précis, l'IBM 1401 utilisait une variante du biquinaire. L'encodage biquinaire de l'IBM 1401 est le suivant : la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre pair encodé par la partie quinaire. Si l'IBM 1401 utilisait cette variante du biquinaire, c'est car elle donnait des circuits de conversion BCD-biquinaire plus économes en portes logiques et plus rapides.
La partie binaire est le bit de poids faible du chiffre BCD, la partie biquinaire est calculée par un simple décodeur qui prend en entrée le chiffre BCD, amputé de son bit de poids faible. La traduction inverse demande d'utiliser un encodeur, à la place du décodeur. Par contre, le circuit d'addition biquinaire était plus compliqué du fait de la gestion des retenues. L'addition des parties binaires et quinaires se faisait en parallèle, dans deux additionneurs séparés. Cependant, l'addition des parties binaire fournit une retenue, qu'il faut prendre en compte. Pour cela, l'IBM 1401 disposait d'un troisième additionneur qui fournissait le résultat final, encodé en biquinaire.
[[File:Additionneur bi-quinaire de l'IBM 1401.png|centre|vignette|upright=2|Additionneur biquinaire de l'IBM 1401]]
Une implémentation moderne demanderait d'utiliser des portes ET combinées à des portes OU, le circuit pouvant être construit simplement à partir de sa table de vérité. Sur l'IBM 1401, le circuit était cependant différent, en raison de l'utilisation de OU câblés, des croisements de fils qui fonctionnent comme des portes OU, que nous n'avons pas encore vu pour le moment, mais qui seront détaillés dans quelques chapitres. Les OU câblés étaient utilisés pour simplifier le design du circuit, mais demandaient des portes logiques spécifiques, ce qui collait avec le fait que ce ''mainframe'' utilisait des transistors en Germanium. L'implémentation exacte est décrite dans cet article de blog, mais je ne recommande sa lecture qu'à ceux qui savent ce qu'est un OU câblé :
* [https://www.righto.com/2015/10/qui-binary-arithmetic-how-1960s-ibm.html Qui-binary arithmetic: how a 1960s IBM mainframe does math].
==Les débordements d'entier lors d'une addition/soustraction==
Les instructions arithmétiques manipulent des entiers codés sur un nombre fixe de bits, qui ne peuvent prendre leurs valeurs que dans un intervalle. Pour les nombres positifs, un ordinateur qui code ses entiers sur n bits pourra coder tous les entiers allant de 0 à <math>2^n -1</math>. Pour les nombres négatifs, l'intervalle est différent et dépend de la représentation utilisée. Dans le cas général, l'ordinateur peut coder les valeurs comprises de <math>N_\text{min}</math> à <math>N_\text{max}</math>. Si le résultat d'un calcul sort de cet intervalle, il ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math> , alors que la valeur basse vaut <math>N_\text{min} - 1</math> (avec <math>N_\text{max}</math> et <math>N_\text{min}</math> respectivement la plus grande et la plus petite valeur codable par l'ordinateur).
===La correction des débordements d'entier : l'arithmétique saturée===
Quand un débordement d'entier survient, tous les circuits de calcul ne procèdent pas de la même manière. Dans les grandes lignes, il y a deux réactions possibles : soit on corrige automatiquement le résultat du débordement, soit on ne fait rien et on se contente de détecter le débordement.
Si le débordement n'est pas corrigé automatiquement, le circuit ne conserve que les bits de poids faibles du résultat. Les bits en trop sont simplement ignorés. On dit qu'on utilise l''''arithmétique modulaire'''. Le problème avec ce genre d'arithmétique, c'est qu'une opération entre deux grands nombres peut donner un résultat très petit. Par exemple, si je dispose de registres 4 bits et que je souhaite faire l'addition 1111 + 0010 (ce qui donne 15 + 2), le résultat est censé être 10001 (17), ce qui est un résultat plus grand que la taille d'un registre. En conservant les 4 bits de poids faible, j’obtiens 0001 (1). En clair, un résultat très grand est transformé en un résultat très petit. Cela peut poser problèmes si on travaille uniquement avec des nombres positifs, mais c'est aussi utilisé pour coder des nombres en complément à deux.
D'autres circuits utilisent ce qu'on appelle l''''arithmétique saturée''' : si un calcul génère un débordement, on arrondi le résultat au plus grand entier supporté par le circuit. Les circuits capables de calculer en arithmétique saturée sont un peu plus complexes, vu qu'il faut rajouter des circuits pour corriger le résultat en cas de débordement. Il suffit généralement de rajouter un ''circuit de saturation'', qui prend en entrée le résultat et le corrige en cas de débordement. Ce circuit de saturation met la valeur maximale en sortie si un débordement survient, mais se contente de recopier le résultat du calcul sur sa sortie s'il n'y a pas de débordement. Typiquement, il est composé d'une couche de multiplexeurs, qui sélectionnent quelle valeur mettre sur la sortie : soit le résultat du calcul, soit le plus grand nombre entier géré par le processeur, soit le plus petit (pour les nombres négatifs/soustractions).
L'arithmétique saturée est utilisée pour les additions et soustractions, mais c'est plus rare pour les multiplications/divisions. Une des raisons est que le résultat d'une addition/soustraction prend un bit de plus que le résultat, là où les multiplications doublent le nombre de bits. Quand une addition déborde, le résultat réel est proche de la valeur maximale codable. mais quand une multiplication déborde, le résultat peut parfois valoir 200 à 60000 fois plus que la valeur maximale codable. Les calculs avec une valeur saturée/corrigée sont donc crédibles pour une suite d'additions, mais pas pour une suite de multiplications.
===La détection des débordements entiers===
Quand un débordement d'entier a eu lieu, il vaut mieux que l'additionneur prévienne ! Pour cela, l'additionneur a une '''sortie de débordement''', parfois nommée ''Overflow'', dont la valeur indique si l'addition a généré un débordement d'entier ou non. Reste que détecter un débordement ne se fait pas de la même manière selon que l'on parle d'un additionneur non-signé ou signé.
Pour les additionneur non-signés, l'additionneur calcule un bit de plus que ce qui est supporté par l'ordinateur. Par exemple, un additionneur 32 bits fournit un résultat sur 33 bits, un débordement d'entier a lieu quand le 33ème bit est à 1. Précisément, la sortie de débordement n'est autre que la retenue finale, celle fournie par le dernier additionneur complet. Le seul type de débordement possible est un débordement par le haut, où le résultat dépasse la valeur maximale. Avec l'arithmétique saturée, le circuit de saturation consiste en une seule couche de multiplexeurs, voire en un circuit de mise à la valeur maximale tel que vu dans le chapitre sur les opérations bits à bits.
[[File:Gestion des débordements d'entiers.png|centre|vignette|upright=2.5|Gestion des débordements d'entiers lors d'une addition non-signée.]]
Pour les additionneurs non-signés, la gestion des débordements d'entiers dépend fortement de la représentation signée. Nous allons étudier le cas du complément à deux. Si vous vous rappelez le chapitre 1, les calculs sur des nombres en complètement à deux utilisent les règles de l'arithmétique modulaire, c'est une condition nécessaire. À priori, on peut penser que dans ces conditions, les débordements d'entiers sont une chose parfaitement normale, qui nous permet d'avoir des résultats corrects. Néanmoins, certains débordements d'entiers peuvent survenir malgré tout et produire des bugs assez ennuyeux.
Si l'on tient en compte les règles du complément à deux, on sait que le bit de poids fort (le plus à gauche) permet de déterminer si le nombre est positif ou négatif : il indique le signe du nombre. Tout se passe comme si les entiers en complément à deux étaient codés sur un bit de moins, et avaient leur longueur amputé du bit de poids fort. Si le résultat d'un calcul écrase le bit de poids fort, il y a un débordement d'entiers. Il existe une règle simple qui permet de détecter ces débordements d'entiers. L'addition de deux nombres positifs ne peut pas être un nombre négatif. Si on additionne deux nombres dont le bit de signe est à 0 et que le bit de signe du résultat est à 1, on est en face d'un débordement d'entiers. Même chose pour deux nombres négatifs : le résultat de l'addition ne peut pas être positif. On peut résumer cela en une phrase : si deux nombres de même signe sont ajoutés, un débordement a lieu quand le bit du signe du résultat a le signe opposé.
Modifier les circuits d'au-dessus pour qu'ils détectent les débordements en complément à deux est simple comme bonjour : il suffit créer un petit circuit combinatoire qui prenne en entrée les bits de signe des opérandes et du résultat, et qui fasse le calcul de l'indicateur de débordements. Si l'on rédige sa table de vérité, on doit se retrouver avec la table suivante :
{|class="wikitable"
|-
!Entrées
!Sortie
|-
|000||0
|-
|001||1
|-
|010||0
|-
|011||0
|-
|100||0
|-
|101||0
|-
|110||1
|-
|111||0
|}
L'équation de ce circuit est la suivante, avec <math>S_a</math> et <math>S_b</math> les signes des deux opérandes, et <math>C_i</math> la retenue de la colonne précédente :
: <math> ( S_a . S_b . \overline{C_i} ) + ( \overline{S_a} . \overline{S_b} . C_i )</math>
En simplifiant, on obtient alors :
: <math>( S_a . S_b ) \oplus C_i</math>
Or, il se trouve que <math>S_a . S_b</math> est tout simplement la retenue en sortie du dernier additionneur, que nous noterons <math>C_o</math>. On trouve donc :
: <math>C_o \oplus C_i</math>
Il suffit donc de faire un XOR entre la dernière retenue et la précédente pour obtenir le bit de débordement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de décalage et de rotation
| prevText=Les circuits de décalage et de rotation
| next=Les circuits de comparaison
| nextText=Les circuits de comparaison
}}
</noinclude>
o6q67noxm55px1i3vtqfcw962emgfcw
768675
768674
2026-06-25T20:11:14Z
Mewtow
31375
/* Les additionneurs à calcul parallèle de préfixe */
768675
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir les circuits capables de faire une addition ou une soustraction, ainsi que quelques circuits spécialisés. Précisons cependant que les fabricants de processeurs travaillent d'arrache-pied pour trouver des moyens de rendre ces circuits de calcul plus rapides et plus économes en énergie. Autant vous dire que les circuits que vous allez voir sont vraiment des circuits qui font pâle figure comparé à ce que l'on peut trouver dans un vrai processeur commercial !
==Les circuits pour additionner 2 ou 3 bits==
L'addition se fait en binaire de la même manière qu'en décimal. On additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. La soustraction fonctionne sur le même principe, sur le même modèle qu'en décimal.
[[File:Binary Addition Demonstration.svg|centre|vignette|Exemple d'addition en binaire.]]
En clair, additionner deux nombres demande d'additionner 2 bits et une retenue sur chaque colonne, et de propager les retenues d'une colonne à l'autre. La propagation des retenues est quelque chose de simple en apparence, mais qui est sujet à des optimisations extraordinairement nombreuses. Aussi, pour simplifier l'exposition, nous allons voir comment gérer une colonne avant de voir comment sont propagées les retenues.
[[File:Full Adder Block.svg|vignette|upright=1|class=transparent|Additionneur complet.]]
Si on effectue une addition en colonne, on doit additionner les deux bits sur la colonne, mais aussi additionner une éventuelle retenue. Il faut donc créer un circuit qui additionne trois bits : deux bits de données, plus une retenue. Ce circuit qui additionne trois bits est appelé un '''additionneur complet'''. Il fournit en sortie deux bits : un bit de somme et une retenue sortante. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|-
||0||0||0|| ||0||0
|-
||0||0||1|| ||0||1
|-
||0||1||0|| ||0||1
|-
||0||1||1|| ||1||0
|-
||1||0||0|| ||0||1
|-
||1||0||1|| ||1||0
|-
||1||1||0|| ||1||0
|-
||1||1||1|| ||1||1
|}
Il est possible d'utiliser un tableau de Karnaugh pour traduire la table de vérité, mais elle donne un résultat assez compliqué. La retenue et le bit de somme sont calculés à part. La retenue est calculée avec trois portes ET et une porte OU. Le bit de somme est calculé lui avec 4 portes ET, une porte OU, et trois portes NON.
[[File:Full Adler with 3 entries.png|centre|vignette|upright=2|Additionneur complet, fabriqué à partir de la table de vérité.]]
D'autres méthodes donnent des résultats plus compréhensibles. Nous allons les voir dans la suite de cette section.
===L'additionneur complet basé sur des demi-additionneurs===
[[File:1-bit half-adder.svg|class=transparent|vignette|Demi-addtionneur.]]
Nous avons déjà vu comment additionner deux bits dans le chapitre sur les incrémenteurs, mais quelques rappels ne seront pas de trop. Le '''demi-additionneur''' est un circuit qui additionne deux bits. Il implémente la table d'addition, qui est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Mais un demi-additionneur n'additionne que deux bits, il manque de quoi additionner la retenue. Une solution pour cela est d'enchaîner deux demi-additionneurs : un qui additionne les deux bits de données, et un second qui additionne la retenue au résultat du précédent. La retenue finale se calcule en combinant les sorties de retenue des deux demi-additionneurs, avec une porte OU. Le résultat est un additionneur complet fabriqué avec deux demi-additionneurs. Pour vous en convaincre, établissez la table de vérité de ce circuit, vous verrez que ça marche.
[[File:Full Adder Blocks.svg|centre|vignette|upright=2|Additionneur complet fabriqué avec deux demi-additionneurs.]]
[[File:Half-adder.svg|vignette|upright=1|Circuit d'un demi-additionneur naïf.]]
L'implémentation naïve d'un demi-additionneur utilise une porte XOR et une porte ET, comme illustré ci-contre. Le circuit s'obtient en faisant uen traduction littérale de la table de vérité du circuit. Si je dis qu'elle est naïve, c'est car il est possible de l'optimiser, de manière à éliminer des redondances cachées. Mais nous avions déjà vu cela dans le chapitre sur les incrémenteurs, aussi laissons cela pour plus tard.
En combinant deux demi-additionneurs de ce type, on obtient l'additionneur complet suivant. Les deux sorties de retenue des demi-additionneurs sont combinées avec une porte OU, pour calculer la retenue finale.
{| class="flexible"
|[[File:Full Adder Modules.svg|class=transparent|Composition d'un additionneur complet. On voit bien que celui-ci est composé de deux demi-additionneurs, en rouge et en bleu, auxquels on a ajouté une porte OU pour calculer la retenue finale.]]
|[[File:Full-adder.svg|300px|class=transparent|Circuit d'un additionneur complet.]]
|}
Le fait de combiner les deux retenues avec une porte OU n'est pas ce qu'il y a de plus intuitif. La table de vérité nous dit que ça fonctionne, mais on comprend mal pourquoi. Mais tout devient plus clair quand on sait que les deux retenues ne peuvent pas être à 1 en même temps. Même dans l'addition 1 + 1 + 1, seule une retenue est à 1. Soit l'addition des deux bits d'opérande donne naissance à une retenue, soit c'est l'addition ''résultat + retenue entrante''. La retenue sortant est donc à 1 quand une des deux addition donne une retenue. D'où l'usage d'une porte OU. Remarquez qu'une porte XOR donne le même résultat, vu que le cas où les deux retenues sont à 1 n'est jamais rencontré.
===La propagation et la génération des retenues===
L'additionneur complet que nous allons voir dans cette section sert à introduire les concepts de propagation, génération et absorption de retenue. Il s'agit de concepts très importants quand on étudie les additionneurs. l'idée part d'un principe très simple : la retenue sortante dépend de la retenue d'entrée. La relation entre les deux se résume à trois cas, qui dépendent de la valeur des deux bits additionnés, nommés A et B.
* Dans le premier cas, la retenue entrante est égale à la retenue sortante. On dit que la retenue entrante est propagée par l'additionneur.
* Dans le second cas, la retenue est forcée à 0 : la retenue sortante vaut 0, peu importe la valeur de la retenue entrante.
* Dans le troisième cas, la retenue est forcée à 1 : la retenue entrante vaut 1, peu importe la valeur de la retenue entrante.
[[File:Signaux P et G fournis par un demi-additionneur.png|vignette|Signaux P et G fournis par un demi-additionneur]]
Maintenant, créons un circuit qui nous dise si une retenue est propagée ou générée. Il a deux bits de sortie, nommés P et G : P pour ''Propagate'', G pour ''Generate''. Le bit P est à 1 si la retenue entrante est propagée, il est à 0 sinon. Le bit G est à 1 si une retenue est générée, à 0 sinon. Une retenue est considérée comme absorbée si elle n'est pas ni propagée ni générée, pas besoin d'un troisième bit pour gérer ce cas.
Il se trouve que ce circuit n'est autre qu'un demi-additionneur ! Pour vous en rendre compte, regardez la table de vérité d'un additionneur complet, illustrée ci-dessous. Lorsque les deux bits d'opérande sont à 0, la retenue sortante vaut toujours 0. Si ils sont tous deux à 1, alors la retenue sortante vaut 1. S'ils sont différents, alors retenues sortante et entrante sont égales. Le bit P est donc généré par une simple porte XOR. Quant au bit G, il est à 1 si les deux bits d'opérandes sont à 1, ce qui correspond à une porte ET. Il se trouve que ces deux portes forment un demi-additionneur !
{|class="wikitable"
|-
! Opérande 1 !! Opérande 2 !! Retenue entrante !! !! Retenue sortante
|- class="f_rouge"
||0||0||0|| ||0
|- class="f_rouge"
||0||0||1|| ||0
|- class="f_bleu"
||0||1||0|| ||0
|- class="f_bleu"
||0||1||1|| ||1
|- class="f_bleu"
||1||0||0|| ||0
|- class="f_bleu"
||1||0||1|| ||1
|- class="f_vert"
||1||1||0|| ||1
|- class="f_vert"
||1||1||1|| ||1
|}
Ensuite, créons un circuit qui prend ces deux sorties P et G, et calcule la retenue sortante en fonction. Le circuit en question a trois entrées : la retenue entrante, les deux bits P et G. La retenue sortante vaut 1 soit si une retenue est générée, soit si la retenue entrante est propagée et qu'elle vaut 1. En notant <math>{C_{in}}</math> et <math>{C_{out}}</math> les retenues entrantes et sortante, on a :
: <math>{C_{out}} = G + \left( P . C_{in} \right)</math>
Le circuit est donc composé d'une porte OU et d'une porte ET. En combinant un demi-additionneur avec le circuit de calcul de retenue sortante vu plus haut, on a :
[[File:Additionneur complet avec propagation et génération de retenue.png|class=transparent|centre|vignette|upright=2|Additionneur complet avec propagation et génération de retenue.]]
Certes, on retombe sur le circuit vu plus haut. Mais il est possible de remplacer le circuit en orange par un autre. Par exemple, il est possible de le remplacer par multiplexeur, qui choisit entre la retenue générée et la retenue entrante (propagée). Le choix se fait selon la valeur du bit P, qui chosiit entre propager la retenue et la générer.
[[File:Additionneur crée avec un multiplexeur.png|centre|vignette|upright=1.5|Additionneur crée avec un multiplexeur]]
Le circuit exact est illustré ci-dessous. Il semble utiliser plus de portes logiques que l'additionneur complet précédent. Cependant, nous verrons dans quelques chapitres qu'il est possible d'implémenter un multiplexeur avec seulement 6 transistors, voire moins ! L'implémentation utilise des portes à transmission, mais nous en reparlerons dans le chapitre sur les transistors, quand nous verrons les additionneurs à ''Manchester Carry Chain''. Au passage, une variante de ce circuit a été utilisée dans le processeur processeur 8086 d'Intel, comme on le verra dans le chapitre suivant.
[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
[[File:FulladderPG.png|vignette|upright=1|Additionneur complet avec deux sorties P et G.]]
Pour finir, sachez qu'il existe des additionneurs qui fournissent : le bit de résultat, deux sorties P et G qui indiquent si l'addition propage ou génère une retenue. Il ne s'agit pas d'additionneurs complets, car il manque de quoi calculer la retenue sortante. De tels additionneurs seront appelés des '''additionneurs partiels'''. De tels additionneurs sont utilisés dans certains additionneurs pour gagner en performance.
Enfin, il existe aussi ce que j'ai décidé d'appeler des '''additionneurs P/G''', qui sont des additionneurs complets auxquels on a ajouté deux sorties P et G, en plus de la sortie de retenue. Leur circuit est le même que celui d'un additionneur complet, auquel on a ajouté deux fils. Vu que ces deux sorties sont fournies par le premier demi-additionneur, ajouter ces deux sorties demande d'ajouter des fils, pas de portes logiques. De tels additionneurs sont aussi utilisés dans certains additionneurs pour gagner en performance.
===L'additionneur complet basé sur une porte à majorité===
Maintenant, voyons une dernière implémentation possible de l'additionneur complet. Mais avant de voir comment l'additionneur est implémenté, nous allons devoir faire un petit focus sur la retenue sortante. Reprenons la table de vérité, et regardons ce qu'il en est pour la retenue sortante uniquement.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante
|-
||0||0||0|| ||0
|-
||0||0||1|| ||0
|-
||0||1||0|| ||0
|-
||0||1||1|| ||1
|-
||1||0||0|| ||0
|-
||1||0||1|| ||1
|-
||1||1||0|| ||1
|-
||1||1||1|| ||1
|}
Vous remarquerez que la retenue sortante est égale au bit majoritaire, parmi les trois bits d'opérande. Si deux ou trois bits sont à 1, la retenue sortante vaut 1. Et réciproquement avec 0. Le circuit de calcul de la retenue peut donc être remplacé par une '''porte à majorité'''.
[[File:Additionneur crée avec une porte à majorité.png|centre|vignette|upright=2|Additionneur crée avec une porte à majorité]]
Mais une porte à majorité est assez gourmande en circuit, ce qui fait qu'un additionneur pareil ne serait pas pratique, ni utile. Du moins, ce serait le cas s'il n'y avait pas une possibilité d'optimisation extrêmement intéressante. Il est possible de calculer le 'bit de somme' à partir de la retenue sortante ! En effet, le bit de somme est l'inverse de la retenue sortante, sauf dans deux cas : les trois bits d'entrée sont à 0, où ils sont tous à 1. Le bit de somme vaut 0 dans le premier cas, 1 dans le second cas.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|- class="f_bleu"
||0||0||0|| ||0||0
|- class="f_vert"
||0||0||1|| ||0||1
|- class="f_vert"
||0||1||0|| ||0||1
|- class="f_vert"
||0||1||1|| ||1||0
|- class="f_vert"
||1||0||0|| ||0||1
|- class="f_vert"
||1||0||1|| ||1||0
|- class="f_vert"
||1||1||0|| ||1||0
|- class="f_rouge"
||1||1||1|| ||1||1
|}
En clair, 6 lignes de la table de vérité sur 8 peuvent se calculer avec une porte à majorité, le reste demandant quelques portes logiques pour faire la correction. L'implémentation en circuit demande donc trois choses :
* de calculer la retenue sortante et de quoi l'inverser ;
* un circuit qui vérifie si tous les bits opérande valent 0 : une porte NOR ou OU fait l'affaire ;
* un circuit qui vérifie s'ils valent tous 1, à savoir une porte ET.
Ensuite, on combine le résultat des trois circuits précédents pour obtenir le résultat final. La combinaison est le fait du circuit en jaune dans le schéma ci-dessous.
[[File:Full adder basé sur une modification de la retenue.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue]]
Le circuit en jaune est assez simple. Quand la porte ET sort un 1, la sortie doit être forcée à 1. Par contre, quand la porte OU sort un zéro, la sortie doit être forcée à 0. Il suffit donc d'enchainer un circuit de mise à 1 et un circuit de mise à 0. Nous avons vu dans le chapitre sur les opérations de masquage qu'il s'agit respectivement d'une porte OU et d'une porte ET.
[[File:Full adder basé sur une modification de la retenue interne, au complet.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue interne, au complet]]
Divers processeurs utilisaient des additionneurs complets de ce type : le fameux Z80, l'Intel 4004 et de l'Intel 8008 sont les exemples les plus notables. Pourtant, on pourrait s'étonner qu'un tel circuit ait existé. Il utilise beaucoup de portes logiques, a une profondeur logique supérieure : il n'a rien d'avantageux. Sauf qu'il était utilisé sur d'anciens processeurs, qui utilisaient la technologie dite TTL, différente de la technologie CMOS des transistors modernes. Et avec la technologie TTL, il est possible de fusionner plusieurs portes logiques ET et NOR en une seule porte logique ET/OU/NON ! Un additionneur complet construit ainsi ne prenait que deux portes logiques : une pour le calcul de la retenue sortante, une autre pour le reste du circuit.
==L'addition non signée==
Voyons maintenant un circuit capable d'additionner deux nombres entiers: l''''additionneur'''. Dans la version qu'on va voir, ce circuit manipulera des nombres strictement positifs. L'addition des nombres codés en complètement à deux sera vu dans une section ultérieure.
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
L'interface d'un additionneur est illustrée ci-contre. Un additionneur prend deux opérandes sur deux entrées séparées, et fournit le résultat sur une sortie dédiée. De plus, il a une entrée sur laquelle envoyer une retenue entrante, et a une sortie pour la retenue sortante. La sortie de retenue indique que le résultat de l'addition a débordé (au sens d'un débordement d'entier). Pour l'entrée de retenue, son utilité deviendra plus claire dans ce qui suit, mais souvenez-vous qu'elle existe.
L'entrée et la sortie de retenue sont parfois utilisées pour combiner plusieurs petits additionneurs, pour former un additionneur plus grand. Nous verrons de nombreux exemples dans ce qui suit. Il est assez commun de créer des additionneurs pour des opérandes de 16/32/64 bits, avec des additionneurs de 4/5 bits.
===L'additionneur série===
Il est possible d'additionner deux nombres bit par bit, grâce à un additionneur complet associé à plusieurs registres à décalages. Les opérandes sont placées chacune dans un registre à décalage, et l'additionneur complet utilise les bits sortants de ces registres à décalage. Le bit du résultat est envoyé au registre à décalage pour le résultat. La retenue de l'addition est stockée dans une bascule de 1 bit, en attente du prochain cycle d'horloge. Un tel additionneur est appelé un '''additionneur série'''. Il a été utilisé sur d'anciens ordinateurs dans les années 50-60, aussi bien des prototypes que des ordinateurs commerciaux.
[[File:Additionneur série.jpg|centre|vignette|upright=2.5|Additionneur série.]]
===L'additionneur à propagation de retenue===
L''''additionneur à propagation de retenue''' est aussi appelé l'additionneur ''ripple carry'', c'est son nom anglais. J'utiliserais occasionnellement le nom anglais dans ce qui suit, car celui-ci est plus court. Ilutilise un additionneur complet pour chaque colonne de l'addition. Pour gérer les retenues, il suffit ainsi de câbler des additionneurs complets les uns à la suite des autres, chacun envoyant sa retenue à la colonne suivante.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Notons la présence de la retenue sortante, qui est utilisée pour détecter les débordements d'entier, ainsi que pour d'autres opérations. Le bit de retenue final est souvent stocké dans un registre spécial du processeur (généralement appelé ''carry flag'').
Notez aussi, à droite du schéma précédent, l'entrée de retenue <math>\text{R}_\text{Entrée}</math>. Elle est très utile pour l'implémentation de certaines opérations comme l'inversion de signe, la soustraction, l'incrémentation, etc. Certains processeurs sont capables de faire une opération appelée ADC, ADDC ou autre nom signifiant ''Addition with Carry'', qui permet de faire le calcul A + B + Retenue (la retenue en question est la retenue sortante de l'addition précédente, stockée dans le registre ''carry flag''). Son utilité principale est de permettre des additions d'entiers plus grands que ceux supportés par le processeur. Par exemple, cela permet de faire des additions d'entiers 32 bits sur un processeur 16 bits.
[[File:Adder Network 3 Bit Sum Delay 003.svg|vignette|upright=0.5|Propagation de retenue dans l'additionneur.]]
Cet additionneur utilise très peu de portes logiques, ce qui fait qu'il était utilisé sur certains processeurs 8 et 16 bits, dont le budget en portes logiques était limité. Bien que très simple, cet additionneur est cependant peu performant. Son temps de calcul est proportionnel à la taille des opérandes. Par exemple, additionner deux opérandes de 32 bits prendra deux fois plus de temps que pour des opérandes de 16 bits. La raison est que les retenues doivent se propager à travers le circuit, du premier additionneur jusqu'au dernier.
L'addition étant une opération fréquente, d'autres additionneurs ont été inventés. Ils utilisent des optimisations qui utilisent plus de circuits pour gagner en rapidité. Mais avant de voir ces optimisations, nous allons voir s'il est possible d'optimiser les additionneurs à propagation de retenue.
===L'additionneur à saut de retenue===
L'additionneur à propagation de retenue peut être optimisé de deux manières. La première accélère la propagation de la retenue, en agissant au niveau des additionneurs complets. La ''Manchester carry chain'' est une optimisation de ce type, mais on ne peut pas encore expliquer à ce stade du cours, car elle optimisait les additionneurs complets au niveau des transistors eux-mêmes. Elle a été utilisée sur de nombreux processeurs connus, comme le 8086 d'Intel ou le Z80.
La seconde manière rend l'addition plus rapide dans certains cas. Le résultat est un additionneur dont le temps de calcul est variable. Le calcul prendra quelques cycles d'horloges avec certains opérandes, mais d'autres opérandes ne verront aucune amélioration. Il n'améliore pas le pire des cas, dans lequel la retenue doit être propagée du début à la fin, du bit de poids faible au bit de poids fort. Mais dans d'autres cas, il permet d'avoir le résultat en avance. L'optimisation en question donne l''''additionneur à saut de retenue''' (''carry-skip adder'').
Il est composé en enchainant plusieurs additionneurs plus petits, qui additionnent 4/5 bits, rarement plus. De tels additionneurs de petite taille sont appelés des '''blocs''', dans ce qui suit. L'idée est de détecter précocement si la retenue entrante est propagée à travers tout le bloc. Si la retenue est propagée dans tous le bloc, on peut directement l'envoyer sur la sortie de retenue, la retenue saute le bloc entier. Dans le cas contraire, on doit calculer la retenue normalement. Le choix entre les deux est le fait d'un multiplexeur. Toute la difficulté est de savoir comment commander le multiplexeur.
[[File:Carry skip adder, principe de base.png|centre|vignette|upright=2.5|Carry skip adder : principe de base]]
Pour savoir si une retenue est propagée dans un bloc entier, il faut utiliser des additionneurs complets, qui ont en plus une sortie P indiquant que l'additionneur propage la retenue. Le bloc propage une retenue si chaque additionneur complet propage la retenue, donc s'ils ont tous leur sortie P à 1. Un simple ET logique suffit à déterminer si c'est le cas. Le signal de commande du multiplexeur est donc un vulgaire ET entre toutes les sorties P des additionneurs complets du bloc.
[[File:Additionneur à saut de retenue.png|centre|vignette|upright=2.5|Calcul de la commande du MUX.]]
Voici ce que ça donne pour un bloc de 4 bits :
[[File:CSAdder4Bit.svg|centre|vignette|upright=1.5|Additionneur ''carry skip'' : bloc de 4 bits.]]
L'additionneur à saut de retenue est construit en assemblant plusieurs blocs de ce type.
[[File:BCSAdder16Bit.svg|centre|vignette|upright=3|Additionneur à saut de retenue.]]
Les blocs sont tous identiques dans le cas le plus simple, mais il est possible d'utiliser des blocs de taille variable. Par exemple, le premier bloc peut avoir des opérandes de 6 bits, le second des opérandes de 7 bits, etc. Faire ainsi permet de gagner un petit peu en performances, si la taille de chaque bloc est bien choisie. La raison est une question de temps de propagation des retenues. La retenue met plus de temps à se propager à travers 8 blocs qu'à travers 4, ce qui prend plus de temps qu'à travers 2 blocs, etc. En tenir compte fait que la taille des blocs tend à augmenter ou diminuer quand on se rapproche des bits de poids fort.
===L'additionneur à sélection de retenue===
L''''additionneur à sélection de retenue''' utilise aussi des blocs, comme les additionneurs précédents. L'addition se fait en deux versions : une avec la retenue du bloc précédent valant zéro, et une autre version avec la retenue du bloc précédent valant 1. Il suffira alors de choisir le bon résultat avec un multiplexeur, une fois cette retenue connue. On gagne ainsi du temps en calculant à l'avance les valeurs de certains bits du résultat, sans connaître la valeur de la retenue. Petit détail : sur certains additionneurs à sélection de retenue, les blocs de base n'ont pas la même taille. Cela permet de tenir compte des temps de propagation des retenues entre les blocs.
[[File:Additionneur à sélection de retenue.png|centre|vignette|upright=1.5|Additionneur à sélection de retenue avec seulement deux blocs.]]
Dans les exemples du dessus, chaque sous-additionneur étaient des additionneurs à propagation de retenue. Mais ce n'est pas une obligation, et tout autre type d’additionneur peut être utilisé. Par exemple, on peut faire en sorte que les sous-additionneurs soient eux-mêmes des additionneurs à sélection de retenue, et poursuivre ainsi de suite, récursivement. On obtient alors un '''additionneur à somme conditionnelle''', plus rapide que l'additionneur à sélection de retenue, mais qui utilise beaucoup plus de portes logiques.
===Les additionneurs à anticipation de retenue===
Les '''additionneurs à anticipation de retenue''' calculent chaque retenue sans avoir à propager les retenues précédentes. Au lieu de calculer les retenues une par une, ils calculent toutes les retenues en parallèle, à partir des bits des opérandes. Une fois les retenues pré-calculées, il suffit de les additionner avec les deux bits adéquats, pour obtenir le résultat. Ces additionneurs sont composés de deux parties :
* un circuit qui pré-calcule les retenues, sans les propager, directement à partir des opérandes ;
* d'une couche d'additionneurs complets, qui additionnent chacun deux bits d'opérande et la retenue pré-calculée.
[[File:Additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Additionneur à anticipation de retenue.]]
Le calcul des retenues se fait en deux étapes. La première détermine si les retenues sont propagées ou générées, pour chaque colonne. Elle détermine, pour chaque colonne, les bits P et G qui indiquent respectivement : qu'une retenue a été générée sur cette colonne, que la colonne propage la retenue précédente. La seconde étape utilise ces signaux P et G pour déterminer la retenue sur chaque colonne. Le circuit de calcul des retenues est donc composé de deux parties : une couche de demi-additionneurs pour générer les signaux P et G, un circuit de calcul des retenues proprement dit.
[[File:Circuit complet d'un additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Circuit complet d'un additionneur à anticipation de retenue.]]
Pour rappel, la retenue de la colonne i, notée <math>C_i</math> est égale à :
: <math>G_i + (P_i . C_{i-1})</math>, avec G le signal de génération de retenue, P le signal de propagation de retenue.
Si on utilisait cette formule sans trop réfléchir, on retomberait sur un additionneur à propagation de retenue inutilement compliqué. L'astuce des additionneurs à anticipation de retenue consiste à remplacer le terme Ci−1 par sa valeur calculée avant. Par exemple, je prends un additionneur 4 bits. Je dispose de deux nombres A et B, contenant chacun 4 bits : A3, A2, A1, et A0 pour le nombre A, et B3, B2, B1, et B0 pour le nombre B. Si j'effectue les remplacements, j'obtiens les formules suivantes :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Ces formules nous permettent de déduire la valeur d'une retenue directement : il reste alors à créer un circuit qui implémente ces formules, et le tour est joué.
[[File:Four bit adder with carry lookahead.svg|centre|vignette|upright=1.5|Additionneur à anticipation de retenue de 4 bits.]]
Plus haut, j'ai dit que le circuit était composé de trois couches : une couche de demi-additionneur pour les signaux P et G ; un circuit d'anticipation de retenue, puis l'addition finale. Mais les deux bits d'opérandes sont déjà additionnés dans la couche de demi-additionneur, autant réutiliser le résultat pour l'additionner à la retenue. Pour éliminer toute redondance de ce type, il est possible d'utiliser des additionneurs partiels (des additionneurs où la sortie de retenue a été remplacée par deux sorties P et G). L'additionneur final est alors composé de plusieurs additionneurs complets de ce type, couplé à un circuit d'anticipation de retenue.
[[File:Cla4bits.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue de 4 bits.]]
Les additionneurs à anticipation de retenue sont plus rapides que les additionneurs à propagation de retenue. La raison est que les retenues sont calculées rapidement, dans un temps plus petit qu'en propageant la retenue. Pour rentrer dans les détails mathématiques, le temps de calcul n'est pas proportionnel au nombre de bits, mais proportionnel au logarithme de ce nombre de bits. Par contre, le gain en temps de calcul s'accompagne d'un cout en portes logiques conséquent. Le circuit qui calcule les retenues anticipées utilise beaucoup de portes logiques.
===Les additionneurs à calcul parallèle de préfixe===
Du fait du cout en portes logiques, utiliser un additionneur à anticipation de retenue est une solution pratique pour additionner 4 ou 8 bits. Mais pour des opérandes plus grands, de 16 ou 32 bits, cela utiliserait trop de portes logiques. Une partie de ce cout en portes logiques est cependant lié à des redondances. Si on analyse deux retenues différentes, à des colonnes différentes. Les circuits qui calculent ces deux retenues seront différents, mais ils contiendront des sous-circuits identiques. Pour vous en rendre compte, reprenons les quatre équations obtenues précédemment et essayez de trouver des redondances :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Vous devriez trouver :
* le terme ''P0 · C0'' dans toutes les lignes ;
* les termes ''P1 · P0 · C0'' et ''P1 · P0'' dans les trois dernières lignes ;
* le terme ''P2 · P1 · P0 · C0'' ; ''P2 · P1 · G0'' et ''P2 · G1'' dans les deux dernières lignes.
Il existe plusieurs manières d'exploiter de telles redondances, exploitées dans les '''additionneurs à calcul parallèle de préfixe'''. L'optimisation apportée est de générer des signaux ''propagate'' et ''generate'' pour des groupes de 2, 3, 4, ..., N colonnes. Par exemple, il est possible de générer un signal ''P 0 vers 3'', qui indique si la retenue de la première colonne est propagée jusqu'à la 4ème colonne. Un autre exemple est un signal qui indique qu'une retenue a été générée entre les colonnes 4 à 7 génèrent une retenue ou non. En clair, les signaux P et G ont maintenant un intervalle, qui précise de quelle colonne vers quelle colonne se fait la propagation, ou entre quelles colonnes se fait la génération.
Il est possible de combiner les signaux P et G de deux groupes de colonne, s'ils sont contiguës (ils peuvent aussi se recouvrir). Par exemple, il est possible de calculer les bits P et G pour les colonnes 0 à 10, à partir des deux signaux P/G des colonnes 0-4 et 5-10. Pour cela, les équations sont assez simples. Si je prends deux groupes nommés A et B, avec A pour les colonnes de poids fort et B celles de poids faible, on a :
: <math>P_{sortie} = P_A . P_B</math>
: <math>G_{sortie} = G_A . P_A . G_B</math>
Pour les bits des colonnes 2, 4, 8, 16, 32, 64, et autres, le calcul est simple. L'idée est de grouper les colonnes par groupes de 2. Puis, on calcule chaque groupe de 4 bits à partir de deux groupes de 2. Puis les groupes de 8 bits sont calculés à partir de deux groupes de 4 bits, et ainsi de suite pour toute puissance de deux.
[[File:Squelette d'un additionneur à calcul parallèle de préfixe.jpg|centre|vignette|upright=2|Squelette d'un additionneur à calcul parallèle de préfixe]]
Les seules difficultés surviennent pour les colonnes dont le nombre n'est pas une puissance de deux. Il y a plusieurs manières pour subdiviser ces colonnes en intervalles plus petits et combiner le tout. Et elles donnent chacune des additionneurs différents. Ils portent les noms d''''additionneur de Ladner-Fisher'', d''''additionneur de Brent-Kung'', d''''additionneur de Kogge-Stone'', etc. Dans le cas général, l'additionneur de Kogge-Stone est un des plus rapide, mais aussi un des plus gourmand en circuits, alors que c'est l'inverse pour l'additionneur de Brent-Kung. Les deux additionneurs sont illustrés ci-dessous, pour des opérandes de 8 bits. Dans ceux-ci, chaque losange calculent les bits P et G, à partir des signaux P et G de deux groupes précédents.
{|
|[[File:Brent-kung-8-bit.png|vignette|Brent-kung-8-bit]]
|[[File:Kogge-stone-8-bit.png|vignette|upright=1.2|Kogge-stone-8-bit]]
|}
===Le calcul parallèle de la retenue===
L'anticipation de retenue est très rapide, mais utilise beaucoup de circuits, même en utilisant le calcul parallèle de préfixe. Les additionneurs à propagation de retenue font eux le compromis inverse. Mais il existe des intermédiaires, qui visent à obtenir un compromis entre performance et cout en portes logiques.
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Un de ces compromis découpe un additionneur de 16/32/64 bits en additionneurs de 4/5 bits, qui sont enchainés en utilisant la propagation de retenue. Par contre, ces petits additionneurs de 4/5 bits utilisent l'anticipation de retenue. Ces additionneurs simples seront nommés ''blocs'' dans ce qui suit, et l'un d'entre eux est illustré ci-contre. Par exemple, on peut utiliser 4 additionneurs à anticipation de retenue de 4 bits, et propager les retenues entre eux. Le premier bloc calcule une retenue, qui est propagée au second bloc. Le second bloc calcule lui aussi une retenue, qui est propagée au troisième bloc, etc.
[[File:4008 Functional Diagram.svg|vignette|upright=1|4008 Functional Diagram]]
Le gain en performance est significatif, mais il est possible de faire presque aussi rapide, tout en économisant beaucoup de circuits. L'idée est de simplifier les additionneurs 4 bits, afin de ne pas utiliser une anticipation de retenue complète. L'anticipation de retenue n'est utilisée que pour la retenue sortante, alors que les bits de somme sont calculées avec propagation de retenue. La retenue sortante est donc calculée en parallèle de l'addition. Chaque bloc contient, à côté d'un additionneur proprement dit, un circuit qui calcule la retenue sortante. Il existe de nombreuses manières de calculer la retenue sortante.
Le gain en portes logiques est assez significatif. En effet, on se passe de circuits d'anticipation de retenue pour les 4 bits de somme, on ne garde que celui pour la retenue sortante. Le fait d'utiliser la propagation de retenue pour calculer les 4 bits de somme est censé avoir un léger cout en performance, mais il se trouve que ce cout est très faible. En effet, le résultat n'est connu qu'une fois les retenues propagées jusqu'au dernier bloc. Et malgré l'anticipation des retenues, la propagation est assez lente. les additionneurs à propagation de retenue auront terminé leur travail bien avant.
Un exemple est celui de l'additionneur CMOS 4008, un additionneur de 4 bit. Il est composé en trois sections. Une première couche de demi-additionneurs calcule les signaux P et G utilisés à la fois pour l'anticipation de la retenue sortante, que pour calculer les bits de somme. Le circuit de calcul de la retenue sortante utilise l'anticipation de retenue. Le reste du circuit fait l'addition en propageant les retenues.
[[File:CMOS 4008, circuit découpé en sections.png|centre|vignette|upright=1.5|CMOS 4008, circuit découpé en sections]]
===Les additionneurs à anticipation de retenue multi-niveau===
Les additionneurs précédents mixent anticipation et propagation de retenue. Ceux que nous allons voir utilisent des blocs, mais utilisent l'anticipation de retenue entre les blocs. Les blocs utilisés peuvent être un additionneur à propagation de retenue, ou à anticipation de retenue, peu importe. Ce qui compte est que la retenue entrante de chaque bloc est calculée par anticipation de retenue.
Par exemple, il est possible de créer un additionneur 16 bits en utilisant 4 additionneurs/blocs de 4 bits, couplé à un circuit d’anticipation de retenue par bloc. Pour cela, les additionneurs de 4 bits doivent fournir deux sorties P et G. Les deux sorties indiquent que le bloc soit a généré une retenue, soit a propagé la retenue entrante. Par propagé, on veut dire que la retenue entrante du bloc s'est retrouvée sur la sortie de retenue du bloc. C'est le cas si les 4 colonnes propagent la retenue, ce qui signifie que :
: <math>P_\text{4 bits} = P_0 \cdot P_1 \cdot P_2 \cdot P_3</math>
Pour la génération, cela veut dire qu'une colonne a générée une retenue, qui a été propagée jusqu'à la sortie. En clair, pour chaque colonne, il faut que le G = 1, et que les signaux P des colonnes soient à 1. Pour les 4 colonnes, cela donne :
: <math>G_\text{4 bits} = G_3 + G_2 \cdot P_3 + G_1 \cdot P_3 \cdot P_2 + G_0 \cdot P_3 \cdot P_2 \cdot P_1</math>
[[File:Cla4bitsPG.png|centre|vignette|upright=2|Additionneur à anticipation de retenue de 4 bits.]]
Pour combiner 4 blocs de 4 bits, on devra donc combiner 4 bits P et G, avec un circuit d'anticipation de retenue.
[[File:16-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 16 bits.]]
Il est même possible d'aller plus loin et de combiner 4 additionneurs précédents pour obtenir un additionneur 64 bits.
[[File:64-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 64 bits.]]
Le résultat est un additionneur à plusieurs niveaux d’anticipation de retenue. Une anticipation de retenue dans un blocs de 4 bits (facultative), entre blocs de 4 bits, entre blocs de 16 bits. La même logique peut être utilisée avec des blocs de taille différente de 4, 16 et 64 bits.
==L'addition signée et la soustraction==
Après avoir vu l'addition, il est logique de passer à la soustraction, les deux opérations étant très proches. Si on sait câbler une addition entre entiers positifs, câbler une soustraction n'est pas très compliqué. De plus, la soustraction permet de faire des additions de nombres signés.
===Le soustracteur pour opérandes entiers===
Pour soustraire deux nombres entiers, on peut adapter l'algorithme de soustraction utilisé en décimal, celui que vous avez appris à l'école. Celui-ci ressemble fortement à l'algorithme d'addition : on soustrait les bits de même poids, et on propage éventuellement une retenue sur la colonne suivante. À la différence de l'addition, la retenue est soustraite, et non ajoutée. La table de soustraction nous dit quel est le résultat de la soustraction de deux bits. La voici :
* 0 - 0 = 0 ;
* 0 - 1 = 1 et une retenue ;
* 1 - 0 = 1 ;
* 1 - 1 = 0.
[[File:Algebra1 05 fig014.svg|centre|vignette|Soustraction en binaire, avec les retenues en rouge.]]
La table de soustraction peut servir de table de vérité pour construire un circuit qui soustrait deux bits. Celui-ci est appelé un '''demi-soustracteur'''. Il ressemble beaucoup à un demi-additionneur, les différences se résumant à une porte NON ajoutée pour le calcul de la retenue.
[[File:Half Subtractor Vektor.svg|centre|vignette|Demi-soustracteur.]]
Comme pour l'additionneur, deux demi-soustracteurs peuvent être combinés pour donner un '''soustracteur complet'''. Le calcul de la retenue se fait en combinant les deux retenues des demi-soustracteurs avec une porte OU. Les soustracteurs complets sont utilisés pour créer des soustracteurs à propagation de retenue ou tout autre circuit soustracteur, sur le même modèle que les additionneurs.
[[File:FullSubtractor.svg|centre|vignette|upright=2|Soustracteur complet.]]
Vous l'aurez sans doute remarqué, mais le demi-soustracteur ressemble beaucoup au demi-additionneur. La seule différence est une porte NON ajoutée au bon endroit dans le demi-soustracteur.
{|
|[[File:Half-adder.svg|vignette|Demi-additionneur]]
|[[File:Half Subtractor Vektor.svg|vignette|upright=1.3|Demi-soustracteur]]
|}
La conséquence est qu'il est possible de créer un circuit capable de faire à la fois des additions et des soustractions. L'idée est de remplacer la porte NON du demi-soustracteur par un inverseur commandable. Si on envoie un zéro sur l'entrée de commande, l'inverseur commandable se comportera comme une porte OUI. Le circuit sera alors identique à un demi-additionneur. Si on envoie un 1 sur l'entrée de commande, l'inverseur commandable se comportera comme une porte NON. le circuit sera alors un demi-soustracteur. Il faut faire cela pour chaque demi-soustracteur, ce qui donne ceci pour un soustracteur complet. le résultat est alors appelé un '''additionneur-soustracteur complet'''.
[[File:Additionneur-soustracteur complet.png|centre|vignette|upright=2|Additionneur-soustracteur complet]]
===L'additionneur-soustracteur pour opérandes codées en complément à deux===
Étudions le cas de la soustraction en complément à deux, dans l'objectif de créer un circuit soustracteur. Vous savez sûrement que a−b et a+(−b) sont deux expressions équivalentes. Et en complément à deux, − b = not(b) + 1. Dit autrement, a − b = a + not(b) + 1. On pourrait se dire qu'il faut deux additionneurs pour faire le calcul, mais la majorité des additionneurs possède une entrée de retenue pour incrémenter le résultat de l'addition. Un soustracteur en complément à deux est donc simplement composé d'un additionneur et d'un inverseur.
[[File:Additionneur soustracteur.png|centre|vignette|upright=2|Soustracteur en complément à deux.]]
Il est possible de créer un circuit capable d'effectuer soit une addition, soit une soustraction : il suffit de remplacer l'inverseur par un inverseur commandable, qui peut être désactivé. On a vu comment créer un tel inverseur commandable dans le chapitre sur les circuits combinatoires. On peut remarquer que l'entrée de retenue et l'entrée de commande de l'inverseur sont activées en même temps : on peut fusionner les deux signaux en un seul.
[[File:Additionneur-soustracteur en complément à deux.png|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux.]]
Une implémentation alternative est la suivante. Elle remplace l'inverseur commandable par un multiplexeur.
[[File:4-bit ripple carry adder-subtracter.svg|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux, version alternative.]]
===L'additionneur-soustracteur pour opérandes codées en signe-magnitude===
Passons maintenant aux nombres codés en signe-valeur absolue, les deux opérandes étant notées A et B. Suivant les signes des deux opérandes, on a quatre cas possibles : A + B, A − B (B négatif), −A + B (A négatif) et −A − B (A et B négatifs). Une astuce est que le circuit n'a besoin que de calculer A + B et A − B : il peut les inverser pour obtenir − A − B ou B − A. A + B et A − B peuvent se calculer avec un additionneur-soustracteur, reste à corriger le résultat. Il suffit de lui ajouter un inverseur commandable pour obtenir le circuit d'addition finale.
[[File:Additionneur en signe-valeur absolue.png|centre|vignette|upright=2|Additionneur en signe-valeur absolue.]]
Toute la difficulté tient dans le calcul du bit de signe du résultat, quand interviennent des soustractions. Autant l'addition de deux nombres de même signe (A + B et −A − B) ne pose aucun problème, autant les soustractions posent problème (A − B et −A + B). Suivant que <math>A<B</math> ou que <math>A>B</math>, le signe du résultat ne sera pas le même. Déterminer le signe du résultat se fait en regardant les bits de débordement d'entier, comme on le verra plus bas.
===L'additionneur-soustracteur pour opérandes codées en représentation par excès===
Passons maintenant aux nombres codés en représentation par excès. On pourrait croire que ces nombres s'additionnent comme des nombres non-signés, mais ce serait oublier la présence du biais, qui pose problème. Dans les cas de nombres signés gérés avec un biais, voyons ce que donne l'addition de deux nombres :
: <math>( n_1 + biais ) + ( n_2 + biais ) = n_1 + n_2 + 2 \times biais</math>
Or, le résultat correct serait :
: <math>n_1 + n_2 + biais</math>
En effectuant l'addition telle quelle, le biais est compté deux fois. On doit donc le soustraire après l'addition pour obtenir le résultat correct.
Même chose pour la soustraction qui donne ceci :
: <math>(n_1 + biais) - (n_2 + biais) = n_1 - n_2</math>
Or, le résultat correct serait :
: <math> ( n_1 - n_2 ) + biais </math>
Il faut rajouter le biais pour obtenir l'exposant correct.
On a donc besoin de deux additionneurs/soustracteurs : un pour additionner/soustraire les représentations binaires des opérandes, et un autre pour ajouter/retirer le biais en trop/manquant.
==L'additionneur BCD==
Maintenant, voyons un additionneur qui additionne deux entiers au format BCD. Pour cela, nous allons devoir passer par deux étapes. La première est de créer un circuit capable d'additionneur deux chiffres BCD. Ensuite, nous allons voir comment enchaîner ces circuits pour créer un additionneur BCD complet.
===L'additionneur BCD qui fait l'opération chiffre par chiffre===
Nous allons commencer par voir un additionneur qui additionne deux chiffres en BCD, une sorte d'équivalent BCD de l'additionneur complet. Il fournit un résultat sur 4 bits et une retenue qui est mise à 1 si le résultat dépasse 10 (la limite d'un chiffre BCD). Les deux opérandes sont des chiffres BCD codés sur 4 bits et sont additionnés en binaire par un additionneur des plus normaux, similaire à ceux vus plus haut. Le résultat est alors un entier codé en binaire, sur 5 bits, qu'on corrige/convertit pour obtenir un chiffre BCD et une retenue sortante.
Pour corriger le résultat, une idée intuitive serait de prendre le résultat et de faire une division par 10. Le quotient donne la retenue, alors que le reste est le résultat, le chiffre BCD. Mais un circuit diviseur par 10 utilise beaucoup de portes logiques, ce qui ne vaut pas le coup. Une autre méthode détecte si le résultat est égal ou supérieur à 10, ce qui correspond à un "débordement" (on dépasse les limites d'un chiffre BCD). Si le résultat est plus petit que 10, il n'y a rien à faire : le résultat est bon et la retenue est de zéro. Par contre, si le résultat vaut 10 ou plus, il faut corriger le résultat et générer une retenue à 1.
Il faut donc ajouter un circuit qui détecte si le résultat est supérieur à 9, qui calcule directement la retenue. Ce circuit peut se fabriquer simplement à partir de sa table de vérité, ou en utilisant les techniques que nous verrons dans un chapitre ultérieur sur les comparateurs. La solution la plus simple est clairement d'utiliser la table de vérité, ce qui est très simple, assez pour être laissé en exercice au lecteur. Pour comprendre comment corriger le résultat, établissons une table de vérité qui associe le résultat et le résultat corrigé. L'entrée vaut au minimum 10 et au maximum 9 + 9 = 18. On considère la sortie comme un tout, la retenue étant un 5ème bit, le bit de poids fort.
{|class="wikitable"
|-
! colspan="5" | Entrée
!
! rowspan="10" |
! Retenue
! Résultat corrigé (sans retenue)
! interprétation de la sortie en binaire (retenue inclue)
|-
| 0 || 1 || 0 || 1 || 0
| (10)
| 1 || 0000
| (16)
|-
| 0 || 1 || 0 || 1 || 1
| (11)
| 1 || 0001
| (17)
|-
| 0 || 1 || 1 || 0 || 0
| (12)
| 1 || 0010
| (18)
|-
| 0 || 1 || 1 || 0 || 1
| (13)
| 1 || 0011
| (19)
|-
| 0 || 1 || 1 || 1 || 0
| (14)
| 1 || 0100
| (20)
|-
| 0 || 1 || 1 || 1 || 1
| (15)
| 1 || 0101
| (21)
|-
| 1 || 0 || 0 || 0 || 0
| (16)
| 1 || 0110
| (22)
|-
| 1 || 0 || 0 || 0 || 1
| (17)
| 1 || 0111
| (23)
|-
| 1 || 0 || 0 || 1 || 0
| (18)
| 1 || 1000
| (24)
|}
En analysant le tableau, on voit que pour corriger le résultat, il suffit d'ajouter 6. La raison est que le résultat déborde d'un nibble à 16 en binaire, mais à 10 en décimal : il suffit d'ajouter la différence entre les deux, à savoir 6, et le débordement binaire fait son travail. Donc, la correction après une addition est très simple : si le résultat dépasse 9, on ajoute 6.
On peut maintenant implémenter l'additionneur BCD, en combinant le comparateur avec 10, le circuit de correction, et l'additionneur. La première solution calcule deux versions du résultat : la version corrigée, la version normale. Le choix entre les deux est réalisée par un multiplexeur, commandé par le comparateur.
[[File:Additionneur BCD.png|centre|vignette|upright=1.5|Additionneur BCD]]
L'autre solution utilise un circuit commandable qui soit additionne 6, soit ne fait rien. Le choix entre les deux est commandé par le bit calculé par le comparateur.
[[File:Additionneur BCD, seconde version.png|centre|vignette|upright=1.5|Additionneur BCD, seconde version.]]
Une version alternative du circuit précédent est la suivante. Il contient deux additionneurs : un pour additionner les deux chiffres BCD, un autre pour additionner 6 si besoin. Le résultat du comparateur est directement utilisé pour générer l'opérande du second additionneur : 0 ou 6. Le circuit est simple à concevoir, mais gaspille beaucoup de circuit. Idéalement, il vaudrait mieux utiliser un circuit combinatoire d'addition avec une constante.
[[File:Adder 4 Bit BCD.svg|centre|vignette|upright=2|Additionneur BCD, circuit complet.]]
Pour obtenir un additionneur BCD complet, il suffit d’enchaîner les additionneurs précédents, comme on le ferait avec les additionneurs complets dans un additionneur à propagation de retenue. Au final, l'additionneur BCD est beaucoup plus compliqué qu'un additionneur normal, car il rajoute un comparateur ">9", un petit additionneur pour ajouter 6 et éventuellement d'autres circuits. De plus, il est difficile d'appliquer les optimisations disponibles sur les additionneurs non-BCD. Notamment, les circuits d'anticipation de retenue sont totalement à refaire et le résultat est relativement compliqué. C'est ce qui explique pourquoi le BCD a progressivement été abandonné au profit du binaire simple.
La soustraction en BCD se fait comme en binaire : le nombre à soustraire est remplacé par son complément, le circuit additionne le complément et l'autre opérande, le débordement d'entier fait que le résultat marche. Sauf qu'ici, le complément est un complément à 9. Il se calcule chiffre par chiffre : chaque chiffre est remplacé par (9 - le chiffre en question).
===L'additionneur BCD par ajustement décimal===
L'additionneur BCD précédent effectuait son travail chiffre BCD par chiffre BCD, mais il existe des additionneurs BCD qui font autrement. Sur les premiers processeurs x86, il n'y avait pas d'opération d'addition BCD proprement dit, seulement une addition binaire normale de 8, 16 ou 32 bits. Par contre, elle était secondée par une opération dite d''''ajustement décimal''' qui transformait un nombre binaire en nombre codé en BCD. L'opération d'ajustement décimal prenait un opérande de 8 bits codé en binaire et fournissait un résultat de la même taille, c'est à dire deux chiffres BCD. Effectuer une addition BCD demandait donc de faire deux opérations à la suite : une addition binaire simple, suivie par l'opération d'ajustement décimal. Cela permettait de gérer des nombres entiers en binaire usuel et des entiers BCD sans avoir deux instructions d'addition séparées pour les deux, sans compter que cela simplifiait aussi les circuits d'addition.
L'ajustement décimal s'effectue en ajoutant une constante bien précise à l'opérande à convertir en BCD. L'idée est que la constante est découpée en morceaux de 4 bits, correspondant chacun à un chiffre BCD de l'opérande, chaque morceau contenant soit un 0, soit 6. Cela permet d'ajouter soit 0, soit 6, à chaque chiffre BCD, et donc de le corriger. La propagation des retenues d'un chiffre à l'autre est effectuée automatiquement par l'addition binaire de la constante. L'opération d'ajustement décimal calcule automatiquement la constante. Elle découpe l'opérande en ''nibbles'', vérifie si chaque ''nibble'' est supérieur ou égal à 10, puis détermine la valeur de chaque ''nibble'' de la constante finale. Par exemple, si je prends l'opérande 1001 1110, le ''nibble'' de poids faible déborde, alors que celui de poids fort non. La constante sera donc 0000 0110 : 0x06. Inversement, si le ''nibble'' de poids fort déborde et pas celui de poids faible, la constante sera alors 0x60. Et la constante est de 0x66 si les deux ''nibbles'' débordent, de 0x00 si aucun ne déborde.
Le circuit d’ajustement décimal est donc composé de trois étapes : deux étapes pour calculer la constante, et un circuit d'addition pour additionner cette constante au nombre de départ. La première étape découpe l'opérande en morceaux de 4 bits, en chiffres BCD, et vérifie si chacun d'entre eux vaut 10 ou plus. La seconde étape prend les résultats de la première étape, et les combine pour calculer la constante. Enfin, on trouve l'addition finale, qui était réalisée par un circuit d'addition utilisé à la fois pour l'ajustement décimal et l'addition binaire. La différence entre une addition normale et une opération d'ajustement décimal tient dans le fait que les deux premières étapes sont désactivées dans une addition normale.
[[File:Additionneur BCD parallèle.png|centre|vignette|upright=1.5|Additionneur BCD parallèle]]
===L'additionneur biquinaire===
Les entiers BCD ne sont qu'un des encodages hybrides entre décimal et binaire. L'encodage biquinaire est l'un d'entre eux et nous allons faire un rappel rapide à ce sujet. Pour simplifier, un chiffre encodé en biquinaire est composé de deux parties : un bit, couplé à une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits (0, 1, 2, 3 et 4). Le bit indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9.
Additionner deux nombres de biquinaire demande donc d'additionner deux parties quinaires encodées en ''one-hot'' et d'additionner deux bits. Mais attention : il faut tenir compte de la retenue de l'addition des parties quinaires. Et idéalement, il faut aussi tenir compte d'une retenue entrante, provenant de l'addition de la colonne de chiffres précédente. Toute la difficulté vient de la création de l'additionneur ''one-hot''. Heureusement, vu qu'il n'y a que 4-5 bits à additionner, il est souvent fabriqué à partir de sa table de vérité.
[[File:Additionneur bi-quinaire.png|centre|vignette|upright=2|Additionneur bi-quinaire]]
Un avantage du biquinaire est que le calcul du complément à 9 est très simple. Il faut pour cela : inverser la partie binaire avec une porte NON, puis inverser l'ordre des bits de la partie quinaire. Concrètement, le bit de poids faible devient le bit de poids fort, et ainsi de suite. Par exemple, une partie quinaire 01000 devient 00010, 10000 devient 00001, 00100 ne change pas, etc. Le tout peut se calculer avec une porte NON et 5 multiplexeurs.
===L'additionneur BCD avec calculs intermédiaires en biquinaire===
L'ordinateur IBM 1401, un ancien ''mainframe'' des années 60, utilisait un additionneur BCD un peu particulier. Les nombres étaient encodés en BCD dans la mémoire de l'ordinateur, mais les circuits de calcul utilisaient la représentation biquinaire. Lors d'un calcul, le processeur de l'ordinateur traduisait les chiffres BCD en représentation biquinaire, faisait une addition en biquinaire, avant de traduire le résultat en BCD normal.
Pour être précis, l'IBM 1401 utilisait une variante du biquinaire. L'encodage biquinaire de l'IBM 1401 est le suivant : la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre pair encodé par la partie quinaire. Si l'IBM 1401 utilisait cette variante du biquinaire, c'est car elle donnait des circuits de conversion BCD-biquinaire plus économes en portes logiques et plus rapides.
La partie binaire est le bit de poids faible du chiffre BCD, la partie biquinaire est calculée par un simple décodeur qui prend en entrée le chiffre BCD, amputé de son bit de poids faible. La traduction inverse demande d'utiliser un encodeur, à la place du décodeur. Par contre, le circuit d'addition biquinaire était plus compliqué du fait de la gestion des retenues. L'addition des parties binaires et quinaires se faisait en parallèle, dans deux additionneurs séparés. Cependant, l'addition des parties binaire fournit une retenue, qu'il faut prendre en compte. Pour cela, l'IBM 1401 disposait d'un troisième additionneur qui fournissait le résultat final, encodé en biquinaire.
[[File:Additionneur bi-quinaire de l'IBM 1401.png|centre|vignette|upright=2|Additionneur biquinaire de l'IBM 1401]]
Une implémentation moderne demanderait d'utiliser des portes ET combinées à des portes OU, le circuit pouvant être construit simplement à partir de sa table de vérité. Sur l'IBM 1401, le circuit était cependant différent, en raison de l'utilisation de OU câblés, des croisements de fils qui fonctionnent comme des portes OU, que nous n'avons pas encore vu pour le moment, mais qui seront détaillés dans quelques chapitres. Les OU câblés étaient utilisés pour simplifier le design du circuit, mais demandaient des portes logiques spécifiques, ce qui collait avec le fait que ce ''mainframe'' utilisait des transistors en Germanium. L'implémentation exacte est décrite dans cet article de blog, mais je ne recommande sa lecture qu'à ceux qui savent ce qu'est un OU câblé :
* [https://www.righto.com/2015/10/qui-binary-arithmetic-how-1960s-ibm.html Qui-binary arithmetic: how a 1960s IBM mainframe does math].
==Les débordements d'entier lors d'une addition/soustraction==
Les instructions arithmétiques manipulent des entiers codés sur un nombre fixe de bits, qui ne peuvent prendre leurs valeurs que dans un intervalle. Pour les nombres positifs, un ordinateur qui code ses entiers sur n bits pourra coder tous les entiers allant de 0 à <math>2^n -1</math>. Pour les nombres négatifs, l'intervalle est différent et dépend de la représentation utilisée. Dans le cas général, l'ordinateur peut coder les valeurs comprises de <math>N_\text{min}</math> à <math>N_\text{max}</math>. Si le résultat d'un calcul sort de cet intervalle, il ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math> , alors que la valeur basse vaut <math>N_\text{min} - 1</math> (avec <math>N_\text{max}</math> et <math>N_\text{min}</math> respectivement la plus grande et la plus petite valeur codable par l'ordinateur).
===La correction des débordements d'entier : l'arithmétique saturée===
Quand un débordement d'entier survient, tous les circuits de calcul ne procèdent pas de la même manière. Dans les grandes lignes, il y a deux réactions possibles : soit on corrige automatiquement le résultat du débordement, soit on ne fait rien et on se contente de détecter le débordement.
Si le débordement n'est pas corrigé automatiquement, le circuit ne conserve que les bits de poids faibles du résultat. Les bits en trop sont simplement ignorés. On dit qu'on utilise l''''arithmétique modulaire'''. Le problème avec ce genre d'arithmétique, c'est qu'une opération entre deux grands nombres peut donner un résultat très petit. Par exemple, si je dispose de registres 4 bits et que je souhaite faire l'addition 1111 + 0010 (ce qui donne 15 + 2), le résultat est censé être 10001 (17), ce qui est un résultat plus grand que la taille d'un registre. En conservant les 4 bits de poids faible, j’obtiens 0001 (1). En clair, un résultat très grand est transformé en un résultat très petit. Cela peut poser problèmes si on travaille uniquement avec des nombres positifs, mais c'est aussi utilisé pour coder des nombres en complément à deux.
D'autres circuits utilisent ce qu'on appelle l''''arithmétique saturée''' : si un calcul génère un débordement, on arrondi le résultat au plus grand entier supporté par le circuit. Les circuits capables de calculer en arithmétique saturée sont un peu plus complexes, vu qu'il faut rajouter des circuits pour corriger le résultat en cas de débordement. Il suffit généralement de rajouter un ''circuit de saturation'', qui prend en entrée le résultat et le corrige en cas de débordement. Ce circuit de saturation met la valeur maximale en sortie si un débordement survient, mais se contente de recopier le résultat du calcul sur sa sortie s'il n'y a pas de débordement. Typiquement, il est composé d'une couche de multiplexeurs, qui sélectionnent quelle valeur mettre sur la sortie : soit le résultat du calcul, soit le plus grand nombre entier géré par le processeur, soit le plus petit (pour les nombres négatifs/soustractions).
L'arithmétique saturée est utilisée pour les additions et soustractions, mais c'est plus rare pour les multiplications/divisions. Une des raisons est que le résultat d'une addition/soustraction prend un bit de plus que le résultat, là où les multiplications doublent le nombre de bits. Quand une addition déborde, le résultat réel est proche de la valeur maximale codable. mais quand une multiplication déborde, le résultat peut parfois valoir 200 à 60000 fois plus que la valeur maximale codable. Les calculs avec une valeur saturée/corrigée sont donc crédibles pour une suite d'additions, mais pas pour une suite de multiplications.
===La détection des débordements entiers===
Quand un débordement d'entier a eu lieu, il vaut mieux que l'additionneur prévienne ! Pour cela, l'additionneur a une '''sortie de débordement''', parfois nommée ''Overflow'', dont la valeur indique si l'addition a généré un débordement d'entier ou non. Reste que détecter un débordement ne se fait pas de la même manière selon que l'on parle d'un additionneur non-signé ou signé.
Pour les additionneur non-signés, l'additionneur calcule un bit de plus que ce qui est supporté par l'ordinateur. Par exemple, un additionneur 32 bits fournit un résultat sur 33 bits, un débordement d'entier a lieu quand le 33ème bit est à 1. Précisément, la sortie de débordement n'est autre que la retenue finale, celle fournie par le dernier additionneur complet. Le seul type de débordement possible est un débordement par le haut, où le résultat dépasse la valeur maximale. Avec l'arithmétique saturée, le circuit de saturation consiste en une seule couche de multiplexeurs, voire en un circuit de mise à la valeur maximale tel que vu dans le chapitre sur les opérations bits à bits.
[[File:Gestion des débordements d'entiers.png|centre|vignette|upright=2.5|Gestion des débordements d'entiers lors d'une addition non-signée.]]
Pour les additionneurs non-signés, la gestion des débordements d'entiers dépend fortement de la représentation signée. Nous allons étudier le cas du complément à deux. Si vous vous rappelez le chapitre 1, les calculs sur des nombres en complètement à deux utilisent les règles de l'arithmétique modulaire, c'est une condition nécessaire. À priori, on peut penser que dans ces conditions, les débordements d'entiers sont une chose parfaitement normale, qui nous permet d'avoir des résultats corrects. Néanmoins, certains débordements d'entiers peuvent survenir malgré tout et produire des bugs assez ennuyeux.
Si l'on tient en compte les règles du complément à deux, on sait que le bit de poids fort (le plus à gauche) permet de déterminer si le nombre est positif ou négatif : il indique le signe du nombre. Tout se passe comme si les entiers en complément à deux étaient codés sur un bit de moins, et avaient leur longueur amputé du bit de poids fort. Si le résultat d'un calcul écrase le bit de poids fort, il y a un débordement d'entiers. Il existe une règle simple qui permet de détecter ces débordements d'entiers. L'addition de deux nombres positifs ne peut pas être un nombre négatif. Si on additionne deux nombres dont le bit de signe est à 0 et que le bit de signe du résultat est à 1, on est en face d'un débordement d'entiers. Même chose pour deux nombres négatifs : le résultat de l'addition ne peut pas être positif. On peut résumer cela en une phrase : si deux nombres de même signe sont ajoutés, un débordement a lieu quand le bit du signe du résultat a le signe opposé.
Modifier les circuits d'au-dessus pour qu'ils détectent les débordements en complément à deux est simple comme bonjour : il suffit créer un petit circuit combinatoire qui prenne en entrée les bits de signe des opérandes et du résultat, et qui fasse le calcul de l'indicateur de débordements. Si l'on rédige sa table de vérité, on doit se retrouver avec la table suivante :
{|class="wikitable"
|-
!Entrées
!Sortie
|-
|000||0
|-
|001||1
|-
|010||0
|-
|011||0
|-
|100||0
|-
|101||0
|-
|110||1
|-
|111||0
|}
L'équation de ce circuit est la suivante, avec <math>S_a</math> et <math>S_b</math> les signes des deux opérandes, et <math>C_i</math> la retenue de la colonne précédente :
: <math> ( S_a . S_b . \overline{C_i} ) + ( \overline{S_a} . \overline{S_b} . C_i )</math>
En simplifiant, on obtient alors :
: <math>( S_a . S_b ) \oplus C_i</math>
Or, il se trouve que <math>S_a . S_b</math> est tout simplement la retenue en sortie du dernier additionneur, que nous noterons <math>C_o</math>. On trouve donc :
: <math>C_o \oplus C_i</math>
Il suffit donc de faire un XOR entre la dernière retenue et la précédente pour obtenir le bit de débordement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de décalage et de rotation
| prevText=Les circuits de décalage et de rotation
| next=Les circuits de comparaison
| nextText=Les circuits de comparaison
}}
</noinclude>
g5xajyzuoc6wrg2lxfv9fviusukt7vn
768676
768675
2026-06-25T20:11:40Z
Mewtow
31375
/* Les additionneurs à calcul parallèle de préfixe */
768676
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir les circuits capables de faire une addition ou une soustraction, ainsi que quelques circuits spécialisés. Précisons cependant que les fabricants de processeurs travaillent d'arrache-pied pour trouver des moyens de rendre ces circuits de calcul plus rapides et plus économes en énergie. Autant vous dire que les circuits que vous allez voir sont vraiment des circuits qui font pâle figure comparé à ce que l'on peut trouver dans un vrai processeur commercial !
==Les circuits pour additionner 2 ou 3 bits==
L'addition se fait en binaire de la même manière qu'en décimal. On additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. La soustraction fonctionne sur le même principe, sur le même modèle qu'en décimal.
[[File:Binary Addition Demonstration.svg|centre|vignette|Exemple d'addition en binaire.]]
En clair, additionner deux nombres demande d'additionner 2 bits et une retenue sur chaque colonne, et de propager les retenues d'une colonne à l'autre. La propagation des retenues est quelque chose de simple en apparence, mais qui est sujet à des optimisations extraordinairement nombreuses. Aussi, pour simplifier l'exposition, nous allons voir comment gérer une colonne avant de voir comment sont propagées les retenues.
[[File:Full Adder Block.svg|vignette|upright=1|class=transparent|Additionneur complet.]]
Si on effectue une addition en colonne, on doit additionner les deux bits sur la colonne, mais aussi additionner une éventuelle retenue. Il faut donc créer un circuit qui additionne trois bits : deux bits de données, plus une retenue. Ce circuit qui additionne trois bits est appelé un '''additionneur complet'''. Il fournit en sortie deux bits : un bit de somme et une retenue sortante. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|-
||0||0||0|| ||0||0
|-
||0||0||1|| ||0||1
|-
||0||1||0|| ||0||1
|-
||0||1||1|| ||1||0
|-
||1||0||0|| ||0||1
|-
||1||0||1|| ||1||0
|-
||1||1||0|| ||1||0
|-
||1||1||1|| ||1||1
|}
Il est possible d'utiliser un tableau de Karnaugh pour traduire la table de vérité, mais elle donne un résultat assez compliqué. La retenue et le bit de somme sont calculés à part. La retenue est calculée avec trois portes ET et une porte OU. Le bit de somme est calculé lui avec 4 portes ET, une porte OU, et trois portes NON.
[[File:Full Adler with 3 entries.png|centre|vignette|upright=2|Additionneur complet, fabriqué à partir de la table de vérité.]]
D'autres méthodes donnent des résultats plus compréhensibles. Nous allons les voir dans la suite de cette section.
===L'additionneur complet basé sur des demi-additionneurs===
[[File:1-bit half-adder.svg|class=transparent|vignette|Demi-addtionneur.]]
Nous avons déjà vu comment additionner deux bits dans le chapitre sur les incrémenteurs, mais quelques rappels ne seront pas de trop. Le '''demi-additionneur''' est un circuit qui additionne deux bits. Il implémente la table d'addition, qui est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Mais un demi-additionneur n'additionne que deux bits, il manque de quoi additionner la retenue. Une solution pour cela est d'enchaîner deux demi-additionneurs : un qui additionne les deux bits de données, et un second qui additionne la retenue au résultat du précédent. La retenue finale se calcule en combinant les sorties de retenue des deux demi-additionneurs, avec une porte OU. Le résultat est un additionneur complet fabriqué avec deux demi-additionneurs. Pour vous en convaincre, établissez la table de vérité de ce circuit, vous verrez que ça marche.
[[File:Full Adder Blocks.svg|centre|vignette|upright=2|Additionneur complet fabriqué avec deux demi-additionneurs.]]
[[File:Half-adder.svg|vignette|upright=1|Circuit d'un demi-additionneur naïf.]]
L'implémentation naïve d'un demi-additionneur utilise une porte XOR et une porte ET, comme illustré ci-contre. Le circuit s'obtient en faisant uen traduction littérale de la table de vérité du circuit. Si je dis qu'elle est naïve, c'est car il est possible de l'optimiser, de manière à éliminer des redondances cachées. Mais nous avions déjà vu cela dans le chapitre sur les incrémenteurs, aussi laissons cela pour plus tard.
En combinant deux demi-additionneurs de ce type, on obtient l'additionneur complet suivant. Les deux sorties de retenue des demi-additionneurs sont combinées avec une porte OU, pour calculer la retenue finale.
{| class="flexible"
|[[File:Full Adder Modules.svg|class=transparent|Composition d'un additionneur complet. On voit bien que celui-ci est composé de deux demi-additionneurs, en rouge et en bleu, auxquels on a ajouté une porte OU pour calculer la retenue finale.]]
|[[File:Full-adder.svg|300px|class=transparent|Circuit d'un additionneur complet.]]
|}
Le fait de combiner les deux retenues avec une porte OU n'est pas ce qu'il y a de plus intuitif. La table de vérité nous dit que ça fonctionne, mais on comprend mal pourquoi. Mais tout devient plus clair quand on sait que les deux retenues ne peuvent pas être à 1 en même temps. Même dans l'addition 1 + 1 + 1, seule une retenue est à 1. Soit l'addition des deux bits d'opérande donne naissance à une retenue, soit c'est l'addition ''résultat + retenue entrante''. La retenue sortant est donc à 1 quand une des deux addition donne une retenue. D'où l'usage d'une porte OU. Remarquez qu'une porte XOR donne le même résultat, vu que le cas où les deux retenues sont à 1 n'est jamais rencontré.
===La propagation et la génération des retenues===
L'additionneur complet que nous allons voir dans cette section sert à introduire les concepts de propagation, génération et absorption de retenue. Il s'agit de concepts très importants quand on étudie les additionneurs. l'idée part d'un principe très simple : la retenue sortante dépend de la retenue d'entrée. La relation entre les deux se résume à trois cas, qui dépendent de la valeur des deux bits additionnés, nommés A et B.
* Dans le premier cas, la retenue entrante est égale à la retenue sortante. On dit que la retenue entrante est propagée par l'additionneur.
* Dans le second cas, la retenue est forcée à 0 : la retenue sortante vaut 0, peu importe la valeur de la retenue entrante.
* Dans le troisième cas, la retenue est forcée à 1 : la retenue entrante vaut 1, peu importe la valeur de la retenue entrante.
[[File:Signaux P et G fournis par un demi-additionneur.png|vignette|Signaux P et G fournis par un demi-additionneur]]
Maintenant, créons un circuit qui nous dise si une retenue est propagée ou générée. Il a deux bits de sortie, nommés P et G : P pour ''Propagate'', G pour ''Generate''. Le bit P est à 1 si la retenue entrante est propagée, il est à 0 sinon. Le bit G est à 1 si une retenue est générée, à 0 sinon. Une retenue est considérée comme absorbée si elle n'est pas ni propagée ni générée, pas besoin d'un troisième bit pour gérer ce cas.
Il se trouve que ce circuit n'est autre qu'un demi-additionneur ! Pour vous en rendre compte, regardez la table de vérité d'un additionneur complet, illustrée ci-dessous. Lorsque les deux bits d'opérande sont à 0, la retenue sortante vaut toujours 0. Si ils sont tous deux à 1, alors la retenue sortante vaut 1. S'ils sont différents, alors retenues sortante et entrante sont égales. Le bit P est donc généré par une simple porte XOR. Quant au bit G, il est à 1 si les deux bits d'opérandes sont à 1, ce qui correspond à une porte ET. Il se trouve que ces deux portes forment un demi-additionneur !
{|class="wikitable"
|-
! Opérande 1 !! Opérande 2 !! Retenue entrante !! !! Retenue sortante
|- class="f_rouge"
||0||0||0|| ||0
|- class="f_rouge"
||0||0||1|| ||0
|- class="f_bleu"
||0||1||0|| ||0
|- class="f_bleu"
||0||1||1|| ||1
|- class="f_bleu"
||1||0||0|| ||0
|- class="f_bleu"
||1||0||1|| ||1
|- class="f_vert"
||1||1||0|| ||1
|- class="f_vert"
||1||1||1|| ||1
|}
Ensuite, créons un circuit qui prend ces deux sorties P et G, et calcule la retenue sortante en fonction. Le circuit en question a trois entrées : la retenue entrante, les deux bits P et G. La retenue sortante vaut 1 soit si une retenue est générée, soit si la retenue entrante est propagée et qu'elle vaut 1. En notant <math>{C_{in}}</math> et <math>{C_{out}}</math> les retenues entrantes et sortante, on a :
: <math>{C_{out}} = G + \left( P . C_{in} \right)</math>
Le circuit est donc composé d'une porte OU et d'une porte ET. En combinant un demi-additionneur avec le circuit de calcul de retenue sortante vu plus haut, on a :
[[File:Additionneur complet avec propagation et génération de retenue.png|class=transparent|centre|vignette|upright=2|Additionneur complet avec propagation et génération de retenue.]]
Certes, on retombe sur le circuit vu plus haut. Mais il est possible de remplacer le circuit en orange par un autre. Par exemple, il est possible de le remplacer par multiplexeur, qui choisit entre la retenue générée et la retenue entrante (propagée). Le choix se fait selon la valeur du bit P, qui chosiit entre propager la retenue et la générer.
[[File:Additionneur crée avec un multiplexeur.png|centre|vignette|upright=1.5|Additionneur crée avec un multiplexeur]]
Le circuit exact est illustré ci-dessous. Il semble utiliser plus de portes logiques que l'additionneur complet précédent. Cependant, nous verrons dans quelques chapitres qu'il est possible d'implémenter un multiplexeur avec seulement 6 transistors, voire moins ! L'implémentation utilise des portes à transmission, mais nous en reparlerons dans le chapitre sur les transistors, quand nous verrons les additionneurs à ''Manchester Carry Chain''. Au passage, une variante de ce circuit a été utilisée dans le processeur processeur 8086 d'Intel, comme on le verra dans le chapitre suivant.
[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
[[File:FulladderPG.png|vignette|upright=1|Additionneur complet avec deux sorties P et G.]]
Pour finir, sachez qu'il existe des additionneurs qui fournissent : le bit de résultat, deux sorties P et G qui indiquent si l'addition propage ou génère une retenue. Il ne s'agit pas d'additionneurs complets, car il manque de quoi calculer la retenue sortante. De tels additionneurs seront appelés des '''additionneurs partiels'''. De tels additionneurs sont utilisés dans certains additionneurs pour gagner en performance.
Enfin, il existe aussi ce que j'ai décidé d'appeler des '''additionneurs P/G''', qui sont des additionneurs complets auxquels on a ajouté deux sorties P et G, en plus de la sortie de retenue. Leur circuit est le même que celui d'un additionneur complet, auquel on a ajouté deux fils. Vu que ces deux sorties sont fournies par le premier demi-additionneur, ajouter ces deux sorties demande d'ajouter des fils, pas de portes logiques. De tels additionneurs sont aussi utilisés dans certains additionneurs pour gagner en performance.
===L'additionneur complet basé sur une porte à majorité===
Maintenant, voyons une dernière implémentation possible de l'additionneur complet. Mais avant de voir comment l'additionneur est implémenté, nous allons devoir faire un petit focus sur la retenue sortante. Reprenons la table de vérité, et regardons ce qu'il en est pour la retenue sortante uniquement.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante
|-
||0||0||0|| ||0
|-
||0||0||1|| ||0
|-
||0||1||0|| ||0
|-
||0||1||1|| ||1
|-
||1||0||0|| ||0
|-
||1||0||1|| ||1
|-
||1||1||0|| ||1
|-
||1||1||1|| ||1
|}
Vous remarquerez que la retenue sortante est égale au bit majoritaire, parmi les trois bits d'opérande. Si deux ou trois bits sont à 1, la retenue sortante vaut 1. Et réciproquement avec 0. Le circuit de calcul de la retenue peut donc être remplacé par une '''porte à majorité'''.
[[File:Additionneur crée avec une porte à majorité.png|centre|vignette|upright=2|Additionneur crée avec une porte à majorité]]
Mais une porte à majorité est assez gourmande en circuit, ce qui fait qu'un additionneur pareil ne serait pas pratique, ni utile. Du moins, ce serait le cas s'il n'y avait pas une possibilité d'optimisation extrêmement intéressante. Il est possible de calculer le 'bit de somme' à partir de la retenue sortante ! En effet, le bit de somme est l'inverse de la retenue sortante, sauf dans deux cas : les trois bits d'entrée sont à 0, où ils sont tous à 1. Le bit de somme vaut 0 dans le premier cas, 1 dans le second cas.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|- class="f_bleu"
||0||0||0|| ||0||0
|- class="f_vert"
||0||0||1|| ||0||1
|- class="f_vert"
||0||1||0|| ||0||1
|- class="f_vert"
||0||1||1|| ||1||0
|- class="f_vert"
||1||0||0|| ||0||1
|- class="f_vert"
||1||0||1|| ||1||0
|- class="f_vert"
||1||1||0|| ||1||0
|- class="f_rouge"
||1||1||1|| ||1||1
|}
En clair, 6 lignes de la table de vérité sur 8 peuvent se calculer avec une porte à majorité, le reste demandant quelques portes logiques pour faire la correction. L'implémentation en circuit demande donc trois choses :
* de calculer la retenue sortante et de quoi l'inverser ;
* un circuit qui vérifie si tous les bits opérande valent 0 : une porte NOR ou OU fait l'affaire ;
* un circuit qui vérifie s'ils valent tous 1, à savoir une porte ET.
Ensuite, on combine le résultat des trois circuits précédents pour obtenir le résultat final. La combinaison est le fait du circuit en jaune dans le schéma ci-dessous.
[[File:Full adder basé sur une modification de la retenue.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue]]
Le circuit en jaune est assez simple. Quand la porte ET sort un 1, la sortie doit être forcée à 1. Par contre, quand la porte OU sort un zéro, la sortie doit être forcée à 0. Il suffit donc d'enchainer un circuit de mise à 1 et un circuit de mise à 0. Nous avons vu dans le chapitre sur les opérations de masquage qu'il s'agit respectivement d'une porte OU et d'une porte ET.
[[File:Full adder basé sur une modification de la retenue interne, au complet.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue interne, au complet]]
Divers processeurs utilisaient des additionneurs complets de ce type : le fameux Z80, l'Intel 4004 et de l'Intel 8008 sont les exemples les plus notables. Pourtant, on pourrait s'étonner qu'un tel circuit ait existé. Il utilise beaucoup de portes logiques, a une profondeur logique supérieure : il n'a rien d'avantageux. Sauf qu'il était utilisé sur d'anciens processeurs, qui utilisaient la technologie dite TTL, différente de la technologie CMOS des transistors modernes. Et avec la technologie TTL, il est possible de fusionner plusieurs portes logiques ET et NOR en une seule porte logique ET/OU/NON ! Un additionneur complet construit ainsi ne prenait que deux portes logiques : une pour le calcul de la retenue sortante, une autre pour le reste du circuit.
==L'addition non signée==
Voyons maintenant un circuit capable d'additionner deux nombres entiers: l''''additionneur'''. Dans la version qu'on va voir, ce circuit manipulera des nombres strictement positifs. L'addition des nombres codés en complètement à deux sera vu dans une section ultérieure.
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
L'interface d'un additionneur est illustrée ci-contre. Un additionneur prend deux opérandes sur deux entrées séparées, et fournit le résultat sur une sortie dédiée. De plus, il a une entrée sur laquelle envoyer une retenue entrante, et a une sortie pour la retenue sortante. La sortie de retenue indique que le résultat de l'addition a débordé (au sens d'un débordement d'entier). Pour l'entrée de retenue, son utilité deviendra plus claire dans ce qui suit, mais souvenez-vous qu'elle existe.
L'entrée et la sortie de retenue sont parfois utilisées pour combiner plusieurs petits additionneurs, pour former un additionneur plus grand. Nous verrons de nombreux exemples dans ce qui suit. Il est assez commun de créer des additionneurs pour des opérandes de 16/32/64 bits, avec des additionneurs de 4/5 bits.
===L'additionneur série===
Il est possible d'additionner deux nombres bit par bit, grâce à un additionneur complet associé à plusieurs registres à décalages. Les opérandes sont placées chacune dans un registre à décalage, et l'additionneur complet utilise les bits sortants de ces registres à décalage. Le bit du résultat est envoyé au registre à décalage pour le résultat. La retenue de l'addition est stockée dans une bascule de 1 bit, en attente du prochain cycle d'horloge. Un tel additionneur est appelé un '''additionneur série'''. Il a été utilisé sur d'anciens ordinateurs dans les années 50-60, aussi bien des prototypes que des ordinateurs commerciaux.
[[File:Additionneur série.jpg|centre|vignette|upright=2.5|Additionneur série.]]
===L'additionneur à propagation de retenue===
L''''additionneur à propagation de retenue''' est aussi appelé l'additionneur ''ripple carry'', c'est son nom anglais. J'utiliserais occasionnellement le nom anglais dans ce qui suit, car celui-ci est plus court. Ilutilise un additionneur complet pour chaque colonne de l'addition. Pour gérer les retenues, il suffit ainsi de câbler des additionneurs complets les uns à la suite des autres, chacun envoyant sa retenue à la colonne suivante.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Notons la présence de la retenue sortante, qui est utilisée pour détecter les débordements d'entier, ainsi que pour d'autres opérations. Le bit de retenue final est souvent stocké dans un registre spécial du processeur (généralement appelé ''carry flag'').
Notez aussi, à droite du schéma précédent, l'entrée de retenue <math>\text{R}_\text{Entrée}</math>. Elle est très utile pour l'implémentation de certaines opérations comme l'inversion de signe, la soustraction, l'incrémentation, etc. Certains processeurs sont capables de faire une opération appelée ADC, ADDC ou autre nom signifiant ''Addition with Carry'', qui permet de faire le calcul A + B + Retenue (la retenue en question est la retenue sortante de l'addition précédente, stockée dans le registre ''carry flag''). Son utilité principale est de permettre des additions d'entiers plus grands que ceux supportés par le processeur. Par exemple, cela permet de faire des additions d'entiers 32 bits sur un processeur 16 bits.
[[File:Adder Network 3 Bit Sum Delay 003.svg|vignette|upright=0.5|Propagation de retenue dans l'additionneur.]]
Cet additionneur utilise très peu de portes logiques, ce qui fait qu'il était utilisé sur certains processeurs 8 et 16 bits, dont le budget en portes logiques était limité. Bien que très simple, cet additionneur est cependant peu performant. Son temps de calcul est proportionnel à la taille des opérandes. Par exemple, additionner deux opérandes de 32 bits prendra deux fois plus de temps que pour des opérandes de 16 bits. La raison est que les retenues doivent se propager à travers le circuit, du premier additionneur jusqu'au dernier.
L'addition étant une opération fréquente, d'autres additionneurs ont été inventés. Ils utilisent des optimisations qui utilisent plus de circuits pour gagner en rapidité. Mais avant de voir ces optimisations, nous allons voir s'il est possible d'optimiser les additionneurs à propagation de retenue.
===L'additionneur à saut de retenue===
L'additionneur à propagation de retenue peut être optimisé de deux manières. La première accélère la propagation de la retenue, en agissant au niveau des additionneurs complets. La ''Manchester carry chain'' est une optimisation de ce type, mais on ne peut pas encore expliquer à ce stade du cours, car elle optimisait les additionneurs complets au niveau des transistors eux-mêmes. Elle a été utilisée sur de nombreux processeurs connus, comme le 8086 d'Intel ou le Z80.
La seconde manière rend l'addition plus rapide dans certains cas. Le résultat est un additionneur dont le temps de calcul est variable. Le calcul prendra quelques cycles d'horloges avec certains opérandes, mais d'autres opérandes ne verront aucune amélioration. Il n'améliore pas le pire des cas, dans lequel la retenue doit être propagée du début à la fin, du bit de poids faible au bit de poids fort. Mais dans d'autres cas, il permet d'avoir le résultat en avance. L'optimisation en question donne l''''additionneur à saut de retenue''' (''carry-skip adder'').
Il est composé en enchainant plusieurs additionneurs plus petits, qui additionnent 4/5 bits, rarement plus. De tels additionneurs de petite taille sont appelés des '''blocs''', dans ce qui suit. L'idée est de détecter précocement si la retenue entrante est propagée à travers tout le bloc. Si la retenue est propagée dans tous le bloc, on peut directement l'envoyer sur la sortie de retenue, la retenue saute le bloc entier. Dans le cas contraire, on doit calculer la retenue normalement. Le choix entre les deux est le fait d'un multiplexeur. Toute la difficulté est de savoir comment commander le multiplexeur.
[[File:Carry skip adder, principe de base.png|centre|vignette|upright=2.5|Carry skip adder : principe de base]]
Pour savoir si une retenue est propagée dans un bloc entier, il faut utiliser des additionneurs complets, qui ont en plus une sortie P indiquant que l'additionneur propage la retenue. Le bloc propage une retenue si chaque additionneur complet propage la retenue, donc s'ils ont tous leur sortie P à 1. Un simple ET logique suffit à déterminer si c'est le cas. Le signal de commande du multiplexeur est donc un vulgaire ET entre toutes les sorties P des additionneurs complets du bloc.
[[File:Additionneur à saut de retenue.png|centre|vignette|upright=2.5|Calcul de la commande du MUX.]]
Voici ce que ça donne pour un bloc de 4 bits :
[[File:CSAdder4Bit.svg|centre|vignette|upright=1.5|Additionneur ''carry skip'' : bloc de 4 bits.]]
L'additionneur à saut de retenue est construit en assemblant plusieurs blocs de ce type.
[[File:BCSAdder16Bit.svg|centre|vignette|upright=3|Additionneur à saut de retenue.]]
Les blocs sont tous identiques dans le cas le plus simple, mais il est possible d'utiliser des blocs de taille variable. Par exemple, le premier bloc peut avoir des opérandes de 6 bits, le second des opérandes de 7 bits, etc. Faire ainsi permet de gagner un petit peu en performances, si la taille de chaque bloc est bien choisie. La raison est une question de temps de propagation des retenues. La retenue met plus de temps à se propager à travers 8 blocs qu'à travers 4, ce qui prend plus de temps qu'à travers 2 blocs, etc. En tenir compte fait que la taille des blocs tend à augmenter ou diminuer quand on se rapproche des bits de poids fort.
===L'additionneur à sélection de retenue===
L''''additionneur à sélection de retenue''' utilise aussi des blocs, comme les additionneurs précédents. L'addition se fait en deux versions : une avec la retenue du bloc précédent valant zéro, et une autre version avec la retenue du bloc précédent valant 1. Il suffira alors de choisir le bon résultat avec un multiplexeur, une fois cette retenue connue. On gagne ainsi du temps en calculant à l'avance les valeurs de certains bits du résultat, sans connaître la valeur de la retenue. Petit détail : sur certains additionneurs à sélection de retenue, les blocs de base n'ont pas la même taille. Cela permet de tenir compte des temps de propagation des retenues entre les blocs.
[[File:Additionneur à sélection de retenue.png|centre|vignette|upright=1.5|Additionneur à sélection de retenue avec seulement deux blocs.]]
Dans les exemples du dessus, chaque sous-additionneur étaient des additionneurs à propagation de retenue. Mais ce n'est pas une obligation, et tout autre type d’additionneur peut être utilisé. Par exemple, on peut faire en sorte que les sous-additionneurs soient eux-mêmes des additionneurs à sélection de retenue, et poursuivre ainsi de suite, récursivement. On obtient alors un '''additionneur à somme conditionnelle''', plus rapide que l'additionneur à sélection de retenue, mais qui utilise beaucoup plus de portes logiques.
===Les additionneurs à anticipation de retenue===
Les '''additionneurs à anticipation de retenue''' calculent chaque retenue sans avoir à propager les retenues précédentes. Au lieu de calculer les retenues une par une, ils calculent toutes les retenues en parallèle, à partir des bits des opérandes. Une fois les retenues pré-calculées, il suffit de les additionner avec les deux bits adéquats, pour obtenir le résultat. Ces additionneurs sont composés de deux parties :
* un circuit qui pré-calcule les retenues, sans les propager, directement à partir des opérandes ;
* d'une couche d'additionneurs complets, qui additionnent chacun deux bits d'opérande et la retenue pré-calculée.
[[File:Additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Additionneur à anticipation de retenue.]]
Le calcul des retenues se fait en deux étapes. La première détermine si les retenues sont propagées ou générées, pour chaque colonne. Elle détermine, pour chaque colonne, les bits P et G qui indiquent respectivement : qu'une retenue a été générée sur cette colonne, que la colonne propage la retenue précédente. La seconde étape utilise ces signaux P et G pour déterminer la retenue sur chaque colonne. Le circuit de calcul des retenues est donc composé de deux parties : une couche de demi-additionneurs pour générer les signaux P et G, un circuit de calcul des retenues proprement dit.
[[File:Circuit complet d'un additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Circuit complet d'un additionneur à anticipation de retenue.]]
Pour rappel, la retenue de la colonne i, notée <math>C_i</math> est égale à :
: <math>G_i + (P_i . C_{i-1})</math>, avec G le signal de génération de retenue, P le signal de propagation de retenue.
Si on utilisait cette formule sans trop réfléchir, on retomberait sur un additionneur à propagation de retenue inutilement compliqué. L'astuce des additionneurs à anticipation de retenue consiste à remplacer le terme Ci−1 par sa valeur calculée avant. Par exemple, je prends un additionneur 4 bits. Je dispose de deux nombres A et B, contenant chacun 4 bits : A3, A2, A1, et A0 pour le nombre A, et B3, B2, B1, et B0 pour le nombre B. Si j'effectue les remplacements, j'obtiens les formules suivantes :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Ces formules nous permettent de déduire la valeur d'une retenue directement : il reste alors à créer un circuit qui implémente ces formules, et le tour est joué.
[[File:Four bit adder with carry lookahead.svg|centre|vignette|upright=1.5|Additionneur à anticipation de retenue de 4 bits.]]
Plus haut, j'ai dit que le circuit était composé de trois couches : une couche de demi-additionneur pour les signaux P et G ; un circuit d'anticipation de retenue, puis l'addition finale. Mais les deux bits d'opérandes sont déjà additionnés dans la couche de demi-additionneur, autant réutiliser le résultat pour l'additionner à la retenue. Pour éliminer toute redondance de ce type, il est possible d'utiliser des additionneurs partiels (des additionneurs où la sortie de retenue a été remplacée par deux sorties P et G). L'additionneur final est alors composé de plusieurs additionneurs complets de ce type, couplé à un circuit d'anticipation de retenue.
[[File:Cla4bits.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue de 4 bits.]]
Les additionneurs à anticipation de retenue sont plus rapides que les additionneurs à propagation de retenue. La raison est que les retenues sont calculées rapidement, dans un temps plus petit qu'en propageant la retenue. Pour rentrer dans les détails mathématiques, le temps de calcul n'est pas proportionnel au nombre de bits, mais proportionnel au logarithme de ce nombre de bits. Par contre, le gain en temps de calcul s'accompagne d'un cout en portes logiques conséquent. Le circuit qui calcule les retenues anticipées utilise beaucoup de portes logiques.
===Les additionneurs à calcul parallèle de préfixe===
Du fait du cout en portes logiques, utiliser un additionneur à anticipation de retenue est une solution pratique pour additionner 4 ou 8 bits. Mais pour des opérandes plus grands, de 16 ou 32 bits, cela utiliserait trop de portes logiques. Une partie de ce cout en portes logiques est cependant lié à des redondances. Si on analyse deux retenues différentes, à des colonnes différentes. Les circuits qui calculent ces deux retenues seront différents, mais ils contiendront des sous-circuits identiques. Pour vous en rendre compte, reprenons les quatre équations obtenues précédemment et essayez de trouver des redondances :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Vous devriez trouver :
* le terme ''P0 · C0'' dans toutes les lignes ;
* les termes ''P1 · P0 · C0'' et ''P1 · P0'' dans les trois dernières lignes ;
* le terme ''P2 · P1 · P0 · C0'' ; ''P2 · P1 · G0'' et ''P2 · G1'' dans les deux dernières lignes.
Il existe plusieurs manières d'exploiter de telles redondances, exploitées dans les '''additionneurs à calcul parallèle de préfixe'''. L'optimisation apportée est de générer des signaux ''propagate'' et ''generate'' pour des groupes de 2, 3, 4, ..., N colonnes. Par exemple, il est possible de générer un signal ''P 0 vers 3'', qui indique si la retenue de la première colonne est propagée jusqu'à la 4ème colonne. Un autre exemple est un signal qui indique qu'une retenue a été générée entre les colonnes 4 à 7 génèrent une retenue ou non. En clair, les signaux P et G ont maintenant un intervalle, qui précise de quelle colonne vers quelle colonne se fait la propagation, ou entre quelles colonnes se fait la génération.
Il est possible de combiner les signaux P et G de deux groupes de colonne, s'ils sont contiguës (ils peuvent aussi se recouvrir). Par exemple, il est possible de calculer les bits P et G pour les colonnes 0 à 10, à partir des deux signaux P/G des colonnes 0-4 et 5-10. Pour cela, les équations sont assez simples. Si je prends deux groupes nommés A et B, avec A pour les colonnes de poids fort et B celles de poids faible, on a :
: <math>P_{sortie} = P_A . P_B</math>
: <math>G_{sortie} = G_A . P_A . G_B</math>
Pour les bits des colonnes 2, 4, 8, 16, 32, 64, et autres, le calcul est simple. L'idée est de grouper les colonnes par groupes de 2. Puis, on calcule chaque groupe de 4 bits à partir de deux groupes de 2. Puis les groupes de 8 bits sont calculés à partir de deux groupes de 4 bits, et ainsi de suite pour toute puissance de deux.
[[File:Squelette d'un additionneur à calcul parallèle de préfixe.jpg|centre|vignette|upright=2|Squelette d'un additionneur à calcul parallèle de préfixe]]
Les seules difficultés surviennent pour les colonnes dont le nombre n'est pas une puissance de deux. Il y a plusieurs manières pour subdiviser ces colonnes en intervalles plus petits et combiner le tout. Et elles donnent chacune des additionneurs différents. Ils portent les noms d''''additionneur de Ladner-Fisher''', d''''additionneur de Brent-Kung''', d''''additionneur de Kogge-Stone''', etc. Dans le cas général, l'additionneur de Kogge-Stone est un des plus rapide, mais aussi un des plus gourmand en circuits, alors que c'est l'inverse pour l'additionneur de Brent-Kung. Les deux additionneurs sont illustrés ci-dessous, pour des opérandes de 8 bits. Dans ceux-ci, chaque losange calculent les bits P et G, à partir des signaux P et G de deux groupes précédents.
{|
|[[File:Brent-kung-8-bit.png|vignette|Brent-kung-8-bit]]
|[[File:Kogge-stone-8-bit.png|vignette|upright=1.2|Kogge-stone-8-bit]]
|}
===Le calcul parallèle de la retenue===
L'anticipation de retenue est très rapide, mais utilise beaucoup de circuits, même en utilisant le calcul parallèle de préfixe. Les additionneurs à propagation de retenue font eux le compromis inverse. Mais il existe des intermédiaires, qui visent à obtenir un compromis entre performance et cout en portes logiques.
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
Un de ces compromis découpe un additionneur de 16/32/64 bits en additionneurs de 4/5 bits, qui sont enchainés en utilisant la propagation de retenue. Par contre, ces petits additionneurs de 4/5 bits utilisent l'anticipation de retenue. Ces additionneurs simples seront nommés ''blocs'' dans ce qui suit, et l'un d'entre eux est illustré ci-contre. Par exemple, on peut utiliser 4 additionneurs à anticipation de retenue de 4 bits, et propager les retenues entre eux. Le premier bloc calcule une retenue, qui est propagée au second bloc. Le second bloc calcule lui aussi une retenue, qui est propagée au troisième bloc, etc.
[[File:4008 Functional Diagram.svg|vignette|upright=1|4008 Functional Diagram]]
Le gain en performance est significatif, mais il est possible de faire presque aussi rapide, tout en économisant beaucoup de circuits. L'idée est de simplifier les additionneurs 4 bits, afin de ne pas utiliser une anticipation de retenue complète. L'anticipation de retenue n'est utilisée que pour la retenue sortante, alors que les bits de somme sont calculées avec propagation de retenue. La retenue sortante est donc calculée en parallèle de l'addition. Chaque bloc contient, à côté d'un additionneur proprement dit, un circuit qui calcule la retenue sortante. Il existe de nombreuses manières de calculer la retenue sortante.
Le gain en portes logiques est assez significatif. En effet, on se passe de circuits d'anticipation de retenue pour les 4 bits de somme, on ne garde que celui pour la retenue sortante. Le fait d'utiliser la propagation de retenue pour calculer les 4 bits de somme est censé avoir un léger cout en performance, mais il se trouve que ce cout est très faible. En effet, le résultat n'est connu qu'une fois les retenues propagées jusqu'au dernier bloc. Et malgré l'anticipation des retenues, la propagation est assez lente. les additionneurs à propagation de retenue auront terminé leur travail bien avant.
Un exemple est celui de l'additionneur CMOS 4008, un additionneur de 4 bit. Il est composé en trois sections. Une première couche de demi-additionneurs calcule les signaux P et G utilisés à la fois pour l'anticipation de la retenue sortante, que pour calculer les bits de somme. Le circuit de calcul de la retenue sortante utilise l'anticipation de retenue. Le reste du circuit fait l'addition en propageant les retenues.
[[File:CMOS 4008, circuit découpé en sections.png|centre|vignette|upright=1.5|CMOS 4008, circuit découpé en sections]]
===Les additionneurs à anticipation de retenue multi-niveau===
Les additionneurs précédents mixent anticipation et propagation de retenue. Ceux que nous allons voir utilisent des blocs, mais utilisent l'anticipation de retenue entre les blocs. Les blocs utilisés peuvent être un additionneur à propagation de retenue, ou à anticipation de retenue, peu importe. Ce qui compte est que la retenue entrante de chaque bloc est calculée par anticipation de retenue.
Par exemple, il est possible de créer un additionneur 16 bits en utilisant 4 additionneurs/blocs de 4 bits, couplé à un circuit d’anticipation de retenue par bloc. Pour cela, les additionneurs de 4 bits doivent fournir deux sorties P et G. Les deux sorties indiquent que le bloc soit a généré une retenue, soit a propagé la retenue entrante. Par propagé, on veut dire que la retenue entrante du bloc s'est retrouvée sur la sortie de retenue du bloc. C'est le cas si les 4 colonnes propagent la retenue, ce qui signifie que :
: <math>P_\text{4 bits} = P_0 \cdot P_1 \cdot P_2 \cdot P_3</math>
Pour la génération, cela veut dire qu'une colonne a générée une retenue, qui a été propagée jusqu'à la sortie. En clair, pour chaque colonne, il faut que le G = 1, et que les signaux P des colonnes soient à 1. Pour les 4 colonnes, cela donne :
: <math>G_\text{4 bits} = G_3 + G_2 \cdot P_3 + G_1 \cdot P_3 \cdot P_2 + G_0 \cdot P_3 \cdot P_2 \cdot P_1</math>
[[File:Cla4bitsPG.png|centre|vignette|upright=2|Additionneur à anticipation de retenue de 4 bits.]]
Pour combiner 4 blocs de 4 bits, on devra donc combiner 4 bits P et G, avec un circuit d'anticipation de retenue.
[[File:16-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 16 bits.]]
Il est même possible d'aller plus loin et de combiner 4 additionneurs précédents pour obtenir un additionneur 64 bits.
[[File:64-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 64 bits.]]
Le résultat est un additionneur à plusieurs niveaux d’anticipation de retenue. Une anticipation de retenue dans un blocs de 4 bits (facultative), entre blocs de 4 bits, entre blocs de 16 bits. La même logique peut être utilisée avec des blocs de taille différente de 4, 16 et 64 bits.
==L'addition signée et la soustraction==
Après avoir vu l'addition, il est logique de passer à la soustraction, les deux opérations étant très proches. Si on sait câbler une addition entre entiers positifs, câbler une soustraction n'est pas très compliqué. De plus, la soustraction permet de faire des additions de nombres signés.
===Le soustracteur pour opérandes entiers===
Pour soustraire deux nombres entiers, on peut adapter l'algorithme de soustraction utilisé en décimal, celui que vous avez appris à l'école. Celui-ci ressemble fortement à l'algorithme d'addition : on soustrait les bits de même poids, et on propage éventuellement une retenue sur la colonne suivante. À la différence de l'addition, la retenue est soustraite, et non ajoutée. La table de soustraction nous dit quel est le résultat de la soustraction de deux bits. La voici :
* 0 - 0 = 0 ;
* 0 - 1 = 1 et une retenue ;
* 1 - 0 = 1 ;
* 1 - 1 = 0.
[[File:Algebra1 05 fig014.svg|centre|vignette|Soustraction en binaire, avec les retenues en rouge.]]
La table de soustraction peut servir de table de vérité pour construire un circuit qui soustrait deux bits. Celui-ci est appelé un '''demi-soustracteur'''. Il ressemble beaucoup à un demi-additionneur, les différences se résumant à une porte NON ajoutée pour le calcul de la retenue.
[[File:Half Subtractor Vektor.svg|centre|vignette|Demi-soustracteur.]]
Comme pour l'additionneur, deux demi-soustracteurs peuvent être combinés pour donner un '''soustracteur complet'''. Le calcul de la retenue se fait en combinant les deux retenues des demi-soustracteurs avec une porte OU. Les soustracteurs complets sont utilisés pour créer des soustracteurs à propagation de retenue ou tout autre circuit soustracteur, sur le même modèle que les additionneurs.
[[File:FullSubtractor.svg|centre|vignette|upright=2|Soustracteur complet.]]
Vous l'aurez sans doute remarqué, mais le demi-soustracteur ressemble beaucoup au demi-additionneur. La seule différence est une porte NON ajoutée au bon endroit dans le demi-soustracteur.
{|
|[[File:Half-adder.svg|vignette|Demi-additionneur]]
|[[File:Half Subtractor Vektor.svg|vignette|upright=1.3|Demi-soustracteur]]
|}
La conséquence est qu'il est possible de créer un circuit capable de faire à la fois des additions et des soustractions. L'idée est de remplacer la porte NON du demi-soustracteur par un inverseur commandable. Si on envoie un zéro sur l'entrée de commande, l'inverseur commandable se comportera comme une porte OUI. Le circuit sera alors identique à un demi-additionneur. Si on envoie un 1 sur l'entrée de commande, l'inverseur commandable se comportera comme une porte NON. le circuit sera alors un demi-soustracteur. Il faut faire cela pour chaque demi-soustracteur, ce qui donne ceci pour un soustracteur complet. le résultat est alors appelé un '''additionneur-soustracteur complet'''.
[[File:Additionneur-soustracteur complet.png|centre|vignette|upright=2|Additionneur-soustracteur complet]]
===L'additionneur-soustracteur pour opérandes codées en complément à deux===
Étudions le cas de la soustraction en complément à deux, dans l'objectif de créer un circuit soustracteur. Vous savez sûrement que a−b et a+(−b) sont deux expressions équivalentes. Et en complément à deux, − b = not(b) + 1. Dit autrement, a − b = a + not(b) + 1. On pourrait se dire qu'il faut deux additionneurs pour faire le calcul, mais la majorité des additionneurs possède une entrée de retenue pour incrémenter le résultat de l'addition. Un soustracteur en complément à deux est donc simplement composé d'un additionneur et d'un inverseur.
[[File:Additionneur soustracteur.png|centre|vignette|upright=2|Soustracteur en complément à deux.]]
Il est possible de créer un circuit capable d'effectuer soit une addition, soit une soustraction : il suffit de remplacer l'inverseur par un inverseur commandable, qui peut être désactivé. On a vu comment créer un tel inverseur commandable dans le chapitre sur les circuits combinatoires. On peut remarquer que l'entrée de retenue et l'entrée de commande de l'inverseur sont activées en même temps : on peut fusionner les deux signaux en un seul.
[[File:Additionneur-soustracteur en complément à deux.png|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux.]]
Une implémentation alternative est la suivante. Elle remplace l'inverseur commandable par un multiplexeur.
[[File:4-bit ripple carry adder-subtracter.svg|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux, version alternative.]]
===L'additionneur-soustracteur pour opérandes codées en signe-magnitude===
Passons maintenant aux nombres codés en signe-valeur absolue, les deux opérandes étant notées A et B. Suivant les signes des deux opérandes, on a quatre cas possibles : A + B, A − B (B négatif), −A + B (A négatif) et −A − B (A et B négatifs). Une astuce est que le circuit n'a besoin que de calculer A + B et A − B : il peut les inverser pour obtenir − A − B ou B − A. A + B et A − B peuvent se calculer avec un additionneur-soustracteur, reste à corriger le résultat. Il suffit de lui ajouter un inverseur commandable pour obtenir le circuit d'addition finale.
[[File:Additionneur en signe-valeur absolue.png|centre|vignette|upright=2|Additionneur en signe-valeur absolue.]]
Toute la difficulté tient dans le calcul du bit de signe du résultat, quand interviennent des soustractions. Autant l'addition de deux nombres de même signe (A + B et −A − B) ne pose aucun problème, autant les soustractions posent problème (A − B et −A + B). Suivant que <math>A<B</math> ou que <math>A>B</math>, le signe du résultat ne sera pas le même. Déterminer le signe du résultat se fait en regardant les bits de débordement d'entier, comme on le verra plus bas.
===L'additionneur-soustracteur pour opérandes codées en représentation par excès===
Passons maintenant aux nombres codés en représentation par excès. On pourrait croire que ces nombres s'additionnent comme des nombres non-signés, mais ce serait oublier la présence du biais, qui pose problème. Dans les cas de nombres signés gérés avec un biais, voyons ce que donne l'addition de deux nombres :
: <math>( n_1 + biais ) + ( n_2 + biais ) = n_1 + n_2 + 2 \times biais</math>
Or, le résultat correct serait :
: <math>n_1 + n_2 + biais</math>
En effectuant l'addition telle quelle, le biais est compté deux fois. On doit donc le soustraire après l'addition pour obtenir le résultat correct.
Même chose pour la soustraction qui donne ceci :
: <math>(n_1 + biais) - (n_2 + biais) = n_1 - n_2</math>
Or, le résultat correct serait :
: <math> ( n_1 - n_2 ) + biais </math>
Il faut rajouter le biais pour obtenir l'exposant correct.
On a donc besoin de deux additionneurs/soustracteurs : un pour additionner/soustraire les représentations binaires des opérandes, et un autre pour ajouter/retirer le biais en trop/manquant.
==L'additionneur BCD==
Maintenant, voyons un additionneur qui additionne deux entiers au format BCD. Pour cela, nous allons devoir passer par deux étapes. La première est de créer un circuit capable d'additionneur deux chiffres BCD. Ensuite, nous allons voir comment enchaîner ces circuits pour créer un additionneur BCD complet.
===L'additionneur BCD qui fait l'opération chiffre par chiffre===
Nous allons commencer par voir un additionneur qui additionne deux chiffres en BCD, une sorte d'équivalent BCD de l'additionneur complet. Il fournit un résultat sur 4 bits et une retenue qui est mise à 1 si le résultat dépasse 10 (la limite d'un chiffre BCD). Les deux opérandes sont des chiffres BCD codés sur 4 bits et sont additionnés en binaire par un additionneur des plus normaux, similaire à ceux vus plus haut. Le résultat est alors un entier codé en binaire, sur 5 bits, qu'on corrige/convertit pour obtenir un chiffre BCD et une retenue sortante.
Pour corriger le résultat, une idée intuitive serait de prendre le résultat et de faire une division par 10. Le quotient donne la retenue, alors que le reste est le résultat, le chiffre BCD. Mais un circuit diviseur par 10 utilise beaucoup de portes logiques, ce qui ne vaut pas le coup. Une autre méthode détecte si le résultat est égal ou supérieur à 10, ce qui correspond à un "débordement" (on dépasse les limites d'un chiffre BCD). Si le résultat est plus petit que 10, il n'y a rien à faire : le résultat est bon et la retenue est de zéro. Par contre, si le résultat vaut 10 ou plus, il faut corriger le résultat et générer une retenue à 1.
Il faut donc ajouter un circuit qui détecte si le résultat est supérieur à 9, qui calcule directement la retenue. Ce circuit peut se fabriquer simplement à partir de sa table de vérité, ou en utilisant les techniques que nous verrons dans un chapitre ultérieur sur les comparateurs. La solution la plus simple est clairement d'utiliser la table de vérité, ce qui est très simple, assez pour être laissé en exercice au lecteur. Pour comprendre comment corriger le résultat, établissons une table de vérité qui associe le résultat et le résultat corrigé. L'entrée vaut au minimum 10 et au maximum 9 + 9 = 18. On considère la sortie comme un tout, la retenue étant un 5ème bit, le bit de poids fort.
{|class="wikitable"
|-
! colspan="5" | Entrée
!
! rowspan="10" |
! Retenue
! Résultat corrigé (sans retenue)
! interprétation de la sortie en binaire (retenue inclue)
|-
| 0 || 1 || 0 || 1 || 0
| (10)
| 1 || 0000
| (16)
|-
| 0 || 1 || 0 || 1 || 1
| (11)
| 1 || 0001
| (17)
|-
| 0 || 1 || 1 || 0 || 0
| (12)
| 1 || 0010
| (18)
|-
| 0 || 1 || 1 || 0 || 1
| (13)
| 1 || 0011
| (19)
|-
| 0 || 1 || 1 || 1 || 0
| (14)
| 1 || 0100
| (20)
|-
| 0 || 1 || 1 || 1 || 1
| (15)
| 1 || 0101
| (21)
|-
| 1 || 0 || 0 || 0 || 0
| (16)
| 1 || 0110
| (22)
|-
| 1 || 0 || 0 || 0 || 1
| (17)
| 1 || 0111
| (23)
|-
| 1 || 0 || 0 || 1 || 0
| (18)
| 1 || 1000
| (24)
|}
En analysant le tableau, on voit que pour corriger le résultat, il suffit d'ajouter 6. La raison est que le résultat déborde d'un nibble à 16 en binaire, mais à 10 en décimal : il suffit d'ajouter la différence entre les deux, à savoir 6, et le débordement binaire fait son travail. Donc, la correction après une addition est très simple : si le résultat dépasse 9, on ajoute 6.
On peut maintenant implémenter l'additionneur BCD, en combinant le comparateur avec 10, le circuit de correction, et l'additionneur. La première solution calcule deux versions du résultat : la version corrigée, la version normale. Le choix entre les deux est réalisée par un multiplexeur, commandé par le comparateur.
[[File:Additionneur BCD.png|centre|vignette|upright=1.5|Additionneur BCD]]
L'autre solution utilise un circuit commandable qui soit additionne 6, soit ne fait rien. Le choix entre les deux est commandé par le bit calculé par le comparateur.
[[File:Additionneur BCD, seconde version.png|centre|vignette|upright=1.5|Additionneur BCD, seconde version.]]
Une version alternative du circuit précédent est la suivante. Il contient deux additionneurs : un pour additionner les deux chiffres BCD, un autre pour additionner 6 si besoin. Le résultat du comparateur est directement utilisé pour générer l'opérande du second additionneur : 0 ou 6. Le circuit est simple à concevoir, mais gaspille beaucoup de circuit. Idéalement, il vaudrait mieux utiliser un circuit combinatoire d'addition avec une constante.
[[File:Adder 4 Bit BCD.svg|centre|vignette|upright=2|Additionneur BCD, circuit complet.]]
Pour obtenir un additionneur BCD complet, il suffit d’enchaîner les additionneurs précédents, comme on le ferait avec les additionneurs complets dans un additionneur à propagation de retenue. Au final, l'additionneur BCD est beaucoup plus compliqué qu'un additionneur normal, car il rajoute un comparateur ">9", un petit additionneur pour ajouter 6 et éventuellement d'autres circuits. De plus, il est difficile d'appliquer les optimisations disponibles sur les additionneurs non-BCD. Notamment, les circuits d'anticipation de retenue sont totalement à refaire et le résultat est relativement compliqué. C'est ce qui explique pourquoi le BCD a progressivement été abandonné au profit du binaire simple.
La soustraction en BCD se fait comme en binaire : le nombre à soustraire est remplacé par son complément, le circuit additionne le complément et l'autre opérande, le débordement d'entier fait que le résultat marche. Sauf qu'ici, le complément est un complément à 9. Il se calcule chiffre par chiffre : chaque chiffre est remplacé par (9 - le chiffre en question).
===L'additionneur BCD par ajustement décimal===
L'additionneur BCD précédent effectuait son travail chiffre BCD par chiffre BCD, mais il existe des additionneurs BCD qui font autrement. Sur les premiers processeurs x86, il n'y avait pas d'opération d'addition BCD proprement dit, seulement une addition binaire normale de 8, 16 ou 32 bits. Par contre, elle était secondée par une opération dite d''''ajustement décimal''' qui transformait un nombre binaire en nombre codé en BCD. L'opération d'ajustement décimal prenait un opérande de 8 bits codé en binaire et fournissait un résultat de la même taille, c'est à dire deux chiffres BCD. Effectuer une addition BCD demandait donc de faire deux opérations à la suite : une addition binaire simple, suivie par l'opération d'ajustement décimal. Cela permettait de gérer des nombres entiers en binaire usuel et des entiers BCD sans avoir deux instructions d'addition séparées pour les deux, sans compter que cela simplifiait aussi les circuits d'addition.
L'ajustement décimal s'effectue en ajoutant une constante bien précise à l'opérande à convertir en BCD. L'idée est que la constante est découpée en morceaux de 4 bits, correspondant chacun à un chiffre BCD de l'opérande, chaque morceau contenant soit un 0, soit 6. Cela permet d'ajouter soit 0, soit 6, à chaque chiffre BCD, et donc de le corriger. La propagation des retenues d'un chiffre à l'autre est effectuée automatiquement par l'addition binaire de la constante. L'opération d'ajustement décimal calcule automatiquement la constante. Elle découpe l'opérande en ''nibbles'', vérifie si chaque ''nibble'' est supérieur ou égal à 10, puis détermine la valeur de chaque ''nibble'' de la constante finale. Par exemple, si je prends l'opérande 1001 1110, le ''nibble'' de poids faible déborde, alors que celui de poids fort non. La constante sera donc 0000 0110 : 0x06. Inversement, si le ''nibble'' de poids fort déborde et pas celui de poids faible, la constante sera alors 0x60. Et la constante est de 0x66 si les deux ''nibbles'' débordent, de 0x00 si aucun ne déborde.
Le circuit d’ajustement décimal est donc composé de trois étapes : deux étapes pour calculer la constante, et un circuit d'addition pour additionner cette constante au nombre de départ. La première étape découpe l'opérande en morceaux de 4 bits, en chiffres BCD, et vérifie si chacun d'entre eux vaut 10 ou plus. La seconde étape prend les résultats de la première étape, et les combine pour calculer la constante. Enfin, on trouve l'addition finale, qui était réalisée par un circuit d'addition utilisé à la fois pour l'ajustement décimal et l'addition binaire. La différence entre une addition normale et une opération d'ajustement décimal tient dans le fait que les deux premières étapes sont désactivées dans une addition normale.
[[File:Additionneur BCD parallèle.png|centre|vignette|upright=1.5|Additionneur BCD parallèle]]
===L'additionneur biquinaire===
Les entiers BCD ne sont qu'un des encodages hybrides entre décimal et binaire. L'encodage biquinaire est l'un d'entre eux et nous allons faire un rappel rapide à ce sujet. Pour simplifier, un chiffre encodé en biquinaire est composé de deux parties : un bit, couplé à une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits (0, 1, 2, 3 et 4). Le bit indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9.
Additionner deux nombres de biquinaire demande donc d'additionner deux parties quinaires encodées en ''one-hot'' et d'additionner deux bits. Mais attention : il faut tenir compte de la retenue de l'addition des parties quinaires. Et idéalement, il faut aussi tenir compte d'une retenue entrante, provenant de l'addition de la colonne de chiffres précédente. Toute la difficulté vient de la création de l'additionneur ''one-hot''. Heureusement, vu qu'il n'y a que 4-5 bits à additionner, il est souvent fabriqué à partir de sa table de vérité.
[[File:Additionneur bi-quinaire.png|centre|vignette|upright=2|Additionneur bi-quinaire]]
Un avantage du biquinaire est que le calcul du complément à 9 est très simple. Il faut pour cela : inverser la partie binaire avec une porte NON, puis inverser l'ordre des bits de la partie quinaire. Concrètement, le bit de poids faible devient le bit de poids fort, et ainsi de suite. Par exemple, une partie quinaire 01000 devient 00010, 10000 devient 00001, 00100 ne change pas, etc. Le tout peut se calculer avec une porte NON et 5 multiplexeurs.
===L'additionneur BCD avec calculs intermédiaires en biquinaire===
L'ordinateur IBM 1401, un ancien ''mainframe'' des années 60, utilisait un additionneur BCD un peu particulier. Les nombres étaient encodés en BCD dans la mémoire de l'ordinateur, mais les circuits de calcul utilisaient la représentation biquinaire. Lors d'un calcul, le processeur de l'ordinateur traduisait les chiffres BCD en représentation biquinaire, faisait une addition en biquinaire, avant de traduire le résultat en BCD normal.
Pour être précis, l'IBM 1401 utilisait une variante du biquinaire. L'encodage biquinaire de l'IBM 1401 est le suivant : la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre pair encodé par la partie quinaire. Si l'IBM 1401 utilisait cette variante du biquinaire, c'est car elle donnait des circuits de conversion BCD-biquinaire plus économes en portes logiques et plus rapides.
La partie binaire est le bit de poids faible du chiffre BCD, la partie biquinaire est calculée par un simple décodeur qui prend en entrée le chiffre BCD, amputé de son bit de poids faible. La traduction inverse demande d'utiliser un encodeur, à la place du décodeur. Par contre, le circuit d'addition biquinaire était plus compliqué du fait de la gestion des retenues. L'addition des parties binaires et quinaires se faisait en parallèle, dans deux additionneurs séparés. Cependant, l'addition des parties binaire fournit une retenue, qu'il faut prendre en compte. Pour cela, l'IBM 1401 disposait d'un troisième additionneur qui fournissait le résultat final, encodé en biquinaire.
[[File:Additionneur bi-quinaire de l'IBM 1401.png|centre|vignette|upright=2|Additionneur biquinaire de l'IBM 1401]]
Une implémentation moderne demanderait d'utiliser des portes ET combinées à des portes OU, le circuit pouvant être construit simplement à partir de sa table de vérité. Sur l'IBM 1401, le circuit était cependant différent, en raison de l'utilisation de OU câblés, des croisements de fils qui fonctionnent comme des portes OU, que nous n'avons pas encore vu pour le moment, mais qui seront détaillés dans quelques chapitres. Les OU câblés étaient utilisés pour simplifier le design du circuit, mais demandaient des portes logiques spécifiques, ce qui collait avec le fait que ce ''mainframe'' utilisait des transistors en Germanium. L'implémentation exacte est décrite dans cet article de blog, mais je ne recommande sa lecture qu'à ceux qui savent ce qu'est un OU câblé :
* [https://www.righto.com/2015/10/qui-binary-arithmetic-how-1960s-ibm.html Qui-binary arithmetic: how a 1960s IBM mainframe does math].
==Les débordements d'entier lors d'une addition/soustraction==
Les instructions arithmétiques manipulent des entiers codés sur un nombre fixe de bits, qui ne peuvent prendre leurs valeurs que dans un intervalle. Pour les nombres positifs, un ordinateur qui code ses entiers sur n bits pourra coder tous les entiers allant de 0 à <math>2^n -1</math>. Pour les nombres négatifs, l'intervalle est différent et dépend de la représentation utilisée. Dans le cas général, l'ordinateur peut coder les valeurs comprises de <math>N_\text{min}</math> à <math>N_\text{max}</math>. Si le résultat d'un calcul sort de cet intervalle, il ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math> , alors que la valeur basse vaut <math>N_\text{min} - 1</math> (avec <math>N_\text{max}</math> et <math>N_\text{min}</math> respectivement la plus grande et la plus petite valeur codable par l'ordinateur).
===La correction des débordements d'entier : l'arithmétique saturée===
Quand un débordement d'entier survient, tous les circuits de calcul ne procèdent pas de la même manière. Dans les grandes lignes, il y a deux réactions possibles : soit on corrige automatiquement le résultat du débordement, soit on ne fait rien et on se contente de détecter le débordement.
Si le débordement n'est pas corrigé automatiquement, le circuit ne conserve que les bits de poids faibles du résultat. Les bits en trop sont simplement ignorés. On dit qu'on utilise l''''arithmétique modulaire'''. Le problème avec ce genre d'arithmétique, c'est qu'une opération entre deux grands nombres peut donner un résultat très petit. Par exemple, si je dispose de registres 4 bits et que je souhaite faire l'addition 1111 + 0010 (ce qui donne 15 + 2), le résultat est censé être 10001 (17), ce qui est un résultat plus grand que la taille d'un registre. En conservant les 4 bits de poids faible, j’obtiens 0001 (1). En clair, un résultat très grand est transformé en un résultat très petit. Cela peut poser problèmes si on travaille uniquement avec des nombres positifs, mais c'est aussi utilisé pour coder des nombres en complément à deux.
D'autres circuits utilisent ce qu'on appelle l''''arithmétique saturée''' : si un calcul génère un débordement, on arrondi le résultat au plus grand entier supporté par le circuit. Les circuits capables de calculer en arithmétique saturée sont un peu plus complexes, vu qu'il faut rajouter des circuits pour corriger le résultat en cas de débordement. Il suffit généralement de rajouter un ''circuit de saturation'', qui prend en entrée le résultat et le corrige en cas de débordement. Ce circuit de saturation met la valeur maximale en sortie si un débordement survient, mais se contente de recopier le résultat du calcul sur sa sortie s'il n'y a pas de débordement. Typiquement, il est composé d'une couche de multiplexeurs, qui sélectionnent quelle valeur mettre sur la sortie : soit le résultat du calcul, soit le plus grand nombre entier géré par le processeur, soit le plus petit (pour les nombres négatifs/soustractions).
L'arithmétique saturée est utilisée pour les additions et soustractions, mais c'est plus rare pour les multiplications/divisions. Une des raisons est que le résultat d'une addition/soustraction prend un bit de plus que le résultat, là où les multiplications doublent le nombre de bits. Quand une addition déborde, le résultat réel est proche de la valeur maximale codable. mais quand une multiplication déborde, le résultat peut parfois valoir 200 à 60000 fois plus que la valeur maximale codable. Les calculs avec une valeur saturée/corrigée sont donc crédibles pour une suite d'additions, mais pas pour une suite de multiplications.
===La détection des débordements entiers===
Quand un débordement d'entier a eu lieu, il vaut mieux que l'additionneur prévienne ! Pour cela, l'additionneur a une '''sortie de débordement''', parfois nommée ''Overflow'', dont la valeur indique si l'addition a généré un débordement d'entier ou non. Reste que détecter un débordement ne se fait pas de la même manière selon que l'on parle d'un additionneur non-signé ou signé.
Pour les additionneur non-signés, l'additionneur calcule un bit de plus que ce qui est supporté par l'ordinateur. Par exemple, un additionneur 32 bits fournit un résultat sur 33 bits, un débordement d'entier a lieu quand le 33ème bit est à 1. Précisément, la sortie de débordement n'est autre que la retenue finale, celle fournie par le dernier additionneur complet. Le seul type de débordement possible est un débordement par le haut, où le résultat dépasse la valeur maximale. Avec l'arithmétique saturée, le circuit de saturation consiste en une seule couche de multiplexeurs, voire en un circuit de mise à la valeur maximale tel que vu dans le chapitre sur les opérations bits à bits.
[[File:Gestion des débordements d'entiers.png|centre|vignette|upright=2.5|Gestion des débordements d'entiers lors d'une addition non-signée.]]
Pour les additionneurs non-signés, la gestion des débordements d'entiers dépend fortement de la représentation signée. Nous allons étudier le cas du complément à deux. Si vous vous rappelez le chapitre 1, les calculs sur des nombres en complètement à deux utilisent les règles de l'arithmétique modulaire, c'est une condition nécessaire. À priori, on peut penser que dans ces conditions, les débordements d'entiers sont une chose parfaitement normale, qui nous permet d'avoir des résultats corrects. Néanmoins, certains débordements d'entiers peuvent survenir malgré tout et produire des bugs assez ennuyeux.
Si l'on tient en compte les règles du complément à deux, on sait que le bit de poids fort (le plus à gauche) permet de déterminer si le nombre est positif ou négatif : il indique le signe du nombre. Tout se passe comme si les entiers en complément à deux étaient codés sur un bit de moins, et avaient leur longueur amputé du bit de poids fort. Si le résultat d'un calcul écrase le bit de poids fort, il y a un débordement d'entiers. Il existe une règle simple qui permet de détecter ces débordements d'entiers. L'addition de deux nombres positifs ne peut pas être un nombre négatif. Si on additionne deux nombres dont le bit de signe est à 0 et que le bit de signe du résultat est à 1, on est en face d'un débordement d'entiers. Même chose pour deux nombres négatifs : le résultat de l'addition ne peut pas être positif. On peut résumer cela en une phrase : si deux nombres de même signe sont ajoutés, un débordement a lieu quand le bit du signe du résultat a le signe opposé.
Modifier les circuits d'au-dessus pour qu'ils détectent les débordements en complément à deux est simple comme bonjour : il suffit créer un petit circuit combinatoire qui prenne en entrée les bits de signe des opérandes et du résultat, et qui fasse le calcul de l'indicateur de débordements. Si l'on rédige sa table de vérité, on doit se retrouver avec la table suivante :
{|class="wikitable"
|-
!Entrées
!Sortie
|-
|000||0
|-
|001||1
|-
|010||0
|-
|011||0
|-
|100||0
|-
|101||0
|-
|110||1
|-
|111||0
|}
L'équation de ce circuit est la suivante, avec <math>S_a</math> et <math>S_b</math> les signes des deux opérandes, et <math>C_i</math> la retenue de la colonne précédente :
: <math> ( S_a . S_b . \overline{C_i} ) + ( \overline{S_a} . \overline{S_b} . C_i )</math>
En simplifiant, on obtient alors :
: <math>( S_a . S_b ) \oplus C_i</math>
Or, il se trouve que <math>S_a . S_b</math> est tout simplement la retenue en sortie du dernier additionneur, que nous noterons <math>C_o</math>. On trouve donc :
: <math>C_o \oplus C_i</math>
Il suffit donc de faire un XOR entre la dernière retenue et la précédente pour obtenir le bit de débordement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de décalage et de rotation
| prevText=Les circuits de décalage et de rotation
| next=Les circuits de comparaison
| nextText=Les circuits de comparaison
}}
</noinclude>
3w7w8ufoqvw42qarh8a5aj1196okypn
768677
768676
2026-06-25T20:11:57Z
Mewtow
31375
/* Le calcul parallèle de la retenue */
768677
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir les circuits capables de faire une addition ou une soustraction, ainsi que quelques circuits spécialisés. Précisons cependant que les fabricants de processeurs travaillent d'arrache-pied pour trouver des moyens de rendre ces circuits de calcul plus rapides et plus économes en énergie. Autant vous dire que les circuits que vous allez voir sont vraiment des circuits qui font pâle figure comparé à ce que l'on peut trouver dans un vrai processeur commercial !
==Les circuits pour additionner 2 ou 3 bits==
L'addition se fait en binaire de la même manière qu'en décimal. On additionne les chiffres/bits colonne par colonne, une éventuelle retenue est propagée à la colonne d'à côté. La soustraction fonctionne sur le même principe, sur le même modèle qu'en décimal.
[[File:Binary Addition Demonstration.svg|centre|vignette|Exemple d'addition en binaire.]]
En clair, additionner deux nombres demande d'additionner 2 bits et une retenue sur chaque colonne, et de propager les retenues d'une colonne à l'autre. La propagation des retenues est quelque chose de simple en apparence, mais qui est sujet à des optimisations extraordinairement nombreuses. Aussi, pour simplifier l'exposition, nous allons voir comment gérer une colonne avant de voir comment sont propagées les retenues.
[[File:Full Adder Block.svg|vignette|upright=1|class=transparent|Additionneur complet.]]
Si on effectue une addition en colonne, on doit additionner les deux bits sur la colonne, mais aussi additionner une éventuelle retenue. Il faut donc créer un circuit qui additionne trois bits : deux bits de données, plus une retenue. Ce circuit qui additionne trois bits est appelé un '''additionneur complet'''. Il fournit en sortie deux bits : un bit de somme et une retenue sortante. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|-
||0||0||0|| ||0||0
|-
||0||0||1|| ||0||1
|-
||0||1||0|| ||0||1
|-
||0||1||1|| ||1||0
|-
||1||0||0|| ||0||1
|-
||1||0||1|| ||1||0
|-
||1||1||0|| ||1||0
|-
||1||1||1|| ||1||1
|}
Il est possible d'utiliser un tableau de Karnaugh pour traduire la table de vérité, mais elle donne un résultat assez compliqué. La retenue et le bit de somme sont calculés à part. La retenue est calculée avec trois portes ET et une porte OU. Le bit de somme est calculé lui avec 4 portes ET, une porte OU, et trois portes NON.
[[File:Full Adler with 3 entries.png|centre|vignette|upright=2|Additionneur complet, fabriqué à partir de la table de vérité.]]
D'autres méthodes donnent des résultats plus compréhensibles. Nous allons les voir dans la suite de cette section.
===L'additionneur complet basé sur des demi-additionneurs===
[[File:1-bit half-adder.svg|class=transparent|vignette|Demi-addtionneur.]]
Nous avons déjà vu comment additionner deux bits dans le chapitre sur les incrémenteurs, mais quelques rappels ne seront pas de trop. Le '''demi-additionneur''' est un circuit qui additionne deux bits. Il implémente la table d'addition, qui est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Mais un demi-additionneur n'additionne que deux bits, il manque de quoi additionner la retenue. Une solution pour cela est d'enchaîner deux demi-additionneurs : un qui additionne les deux bits de données, et un second qui additionne la retenue au résultat du précédent. La retenue finale se calcule en combinant les sorties de retenue des deux demi-additionneurs, avec une porte OU. Le résultat est un additionneur complet fabriqué avec deux demi-additionneurs. Pour vous en convaincre, établissez la table de vérité de ce circuit, vous verrez que ça marche.
[[File:Full Adder Blocks.svg|centre|vignette|upright=2|Additionneur complet fabriqué avec deux demi-additionneurs.]]
[[File:Half-adder.svg|vignette|upright=1|Circuit d'un demi-additionneur naïf.]]
L'implémentation naïve d'un demi-additionneur utilise une porte XOR et une porte ET, comme illustré ci-contre. Le circuit s'obtient en faisant uen traduction littérale de la table de vérité du circuit. Si je dis qu'elle est naïve, c'est car il est possible de l'optimiser, de manière à éliminer des redondances cachées. Mais nous avions déjà vu cela dans le chapitre sur les incrémenteurs, aussi laissons cela pour plus tard.
En combinant deux demi-additionneurs de ce type, on obtient l'additionneur complet suivant. Les deux sorties de retenue des demi-additionneurs sont combinées avec une porte OU, pour calculer la retenue finale.
{| class="flexible"
|[[File:Full Adder Modules.svg|class=transparent|Composition d'un additionneur complet. On voit bien que celui-ci est composé de deux demi-additionneurs, en rouge et en bleu, auxquels on a ajouté une porte OU pour calculer la retenue finale.]]
|[[File:Full-adder.svg|300px|class=transparent|Circuit d'un additionneur complet.]]
|}
Le fait de combiner les deux retenues avec une porte OU n'est pas ce qu'il y a de plus intuitif. La table de vérité nous dit que ça fonctionne, mais on comprend mal pourquoi. Mais tout devient plus clair quand on sait que les deux retenues ne peuvent pas être à 1 en même temps. Même dans l'addition 1 + 1 + 1, seule une retenue est à 1. Soit l'addition des deux bits d'opérande donne naissance à une retenue, soit c'est l'addition ''résultat + retenue entrante''. La retenue sortant est donc à 1 quand une des deux addition donne une retenue. D'où l'usage d'une porte OU. Remarquez qu'une porte XOR donne le même résultat, vu que le cas où les deux retenues sont à 1 n'est jamais rencontré.
===La propagation et la génération des retenues===
L'additionneur complet que nous allons voir dans cette section sert à introduire les concepts de propagation, génération et absorption de retenue. Il s'agit de concepts très importants quand on étudie les additionneurs. l'idée part d'un principe très simple : la retenue sortante dépend de la retenue d'entrée. La relation entre les deux se résume à trois cas, qui dépendent de la valeur des deux bits additionnés, nommés A et B.
* Dans le premier cas, la retenue entrante est égale à la retenue sortante. On dit que la retenue entrante est propagée par l'additionneur.
* Dans le second cas, la retenue est forcée à 0 : la retenue sortante vaut 0, peu importe la valeur de la retenue entrante.
* Dans le troisième cas, la retenue est forcée à 1 : la retenue entrante vaut 1, peu importe la valeur de la retenue entrante.
[[File:Signaux P et G fournis par un demi-additionneur.png|vignette|Signaux P et G fournis par un demi-additionneur]]
Maintenant, créons un circuit qui nous dise si une retenue est propagée ou générée. Il a deux bits de sortie, nommés P et G : P pour ''Propagate'', G pour ''Generate''. Le bit P est à 1 si la retenue entrante est propagée, il est à 0 sinon. Le bit G est à 1 si une retenue est générée, à 0 sinon. Une retenue est considérée comme absorbée si elle n'est pas ni propagée ni générée, pas besoin d'un troisième bit pour gérer ce cas.
Il se trouve que ce circuit n'est autre qu'un demi-additionneur ! Pour vous en rendre compte, regardez la table de vérité d'un additionneur complet, illustrée ci-dessous. Lorsque les deux bits d'opérande sont à 0, la retenue sortante vaut toujours 0. Si ils sont tous deux à 1, alors la retenue sortante vaut 1. S'ils sont différents, alors retenues sortante et entrante sont égales. Le bit P est donc généré par une simple porte XOR. Quant au bit G, il est à 1 si les deux bits d'opérandes sont à 1, ce qui correspond à une porte ET. Il se trouve que ces deux portes forment un demi-additionneur !
{|class="wikitable"
|-
! Opérande 1 !! Opérande 2 !! Retenue entrante !! !! Retenue sortante
|- class="f_rouge"
||0||0||0|| ||0
|- class="f_rouge"
||0||0||1|| ||0
|- class="f_bleu"
||0||1||0|| ||0
|- class="f_bleu"
||0||1||1|| ||1
|- class="f_bleu"
||1||0||0|| ||0
|- class="f_bleu"
||1||0||1|| ||1
|- class="f_vert"
||1||1||0|| ||1
|- class="f_vert"
||1||1||1|| ||1
|}
Ensuite, créons un circuit qui prend ces deux sorties P et G, et calcule la retenue sortante en fonction. Le circuit en question a trois entrées : la retenue entrante, les deux bits P et G. La retenue sortante vaut 1 soit si une retenue est générée, soit si la retenue entrante est propagée et qu'elle vaut 1. En notant <math>{C_{in}}</math> et <math>{C_{out}}</math> les retenues entrantes et sortante, on a :
: <math>{C_{out}} = G + \left( P . C_{in} \right)</math>
Le circuit est donc composé d'une porte OU et d'une porte ET. En combinant un demi-additionneur avec le circuit de calcul de retenue sortante vu plus haut, on a :
[[File:Additionneur complet avec propagation et génération de retenue.png|class=transparent|centre|vignette|upright=2|Additionneur complet avec propagation et génération de retenue.]]
Certes, on retombe sur le circuit vu plus haut. Mais il est possible de remplacer le circuit en orange par un autre. Par exemple, il est possible de le remplacer par multiplexeur, qui choisit entre la retenue générée et la retenue entrante (propagée). Le choix se fait selon la valeur du bit P, qui chosiit entre propager la retenue et la générer.
[[File:Additionneur crée avec un multiplexeur.png|centre|vignette|upright=1.5|Additionneur crée avec un multiplexeur]]
Le circuit exact est illustré ci-dessous. Il semble utiliser plus de portes logiques que l'additionneur complet précédent. Cependant, nous verrons dans quelques chapitres qu'il est possible d'implémenter un multiplexeur avec seulement 6 transistors, voire moins ! L'implémentation utilise des portes à transmission, mais nous en reparlerons dans le chapitre sur les transistors, quand nous verrons les additionneurs à ''Manchester Carry Chain''. Au passage, une variante de ce circuit a été utilisée dans le processeur processeur 8086 d'Intel, comme on le verra dans le chapitre suivant.
[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
[[File:FulladderPG.png|vignette|upright=1|Additionneur complet avec deux sorties P et G.]]
Pour finir, sachez qu'il existe des additionneurs qui fournissent : le bit de résultat, deux sorties P et G qui indiquent si l'addition propage ou génère une retenue. Il ne s'agit pas d'additionneurs complets, car il manque de quoi calculer la retenue sortante. De tels additionneurs seront appelés des '''additionneurs partiels'''. De tels additionneurs sont utilisés dans certains additionneurs pour gagner en performance.
Enfin, il existe aussi ce que j'ai décidé d'appeler des '''additionneurs P/G''', qui sont des additionneurs complets auxquels on a ajouté deux sorties P et G, en plus de la sortie de retenue. Leur circuit est le même que celui d'un additionneur complet, auquel on a ajouté deux fils. Vu que ces deux sorties sont fournies par le premier demi-additionneur, ajouter ces deux sorties demande d'ajouter des fils, pas de portes logiques. De tels additionneurs sont aussi utilisés dans certains additionneurs pour gagner en performance.
===L'additionneur complet basé sur une porte à majorité===
Maintenant, voyons une dernière implémentation possible de l'additionneur complet. Mais avant de voir comment l'additionneur est implémenté, nous allons devoir faire un petit focus sur la retenue sortante. Reprenons la table de vérité, et regardons ce qu'il en est pour la retenue sortante uniquement.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante
|-
||0||0||0|| ||0
|-
||0||0||1|| ||0
|-
||0||1||0|| ||0
|-
||0||1||1|| ||1
|-
||1||0||0|| ||0
|-
||1||0||1|| ||1
|-
||1||1||0|| ||1
|-
||1||1||1|| ||1
|}
Vous remarquerez que la retenue sortante est égale au bit majoritaire, parmi les trois bits d'opérande. Si deux ou trois bits sont à 1, la retenue sortante vaut 1. Et réciproquement avec 0. Le circuit de calcul de la retenue peut donc être remplacé par une '''porte à majorité'''.
[[File:Additionneur crée avec une porte à majorité.png|centre|vignette|upright=2|Additionneur crée avec une porte à majorité]]
Mais une porte à majorité est assez gourmande en circuit, ce qui fait qu'un additionneur pareil ne serait pas pratique, ni utile. Du moins, ce serait le cas s'il n'y avait pas une possibilité d'optimisation extrêmement intéressante. Il est possible de calculer le 'bit de somme' à partir de la retenue sortante ! En effet, le bit de somme est l'inverse de la retenue sortante, sauf dans deux cas : les trois bits d'entrée sont à 0, où ils sont tous à 1. Le bit de somme vaut 0 dans le premier cas, 1 dans le second cas.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! Opérande 2 !! !! Retenue sortante !! Bit de somme
|- class="f_bleu"
||0||0||0|| ||0||0
|- class="f_vert"
||0||0||1|| ||0||1
|- class="f_vert"
||0||1||0|| ||0||1
|- class="f_vert"
||0||1||1|| ||1||0
|- class="f_vert"
||1||0||0|| ||0||1
|- class="f_vert"
||1||0||1|| ||1||0
|- class="f_vert"
||1||1||0|| ||1||0
|- class="f_rouge"
||1||1||1|| ||1||1
|}
En clair, 6 lignes de la table de vérité sur 8 peuvent se calculer avec une porte à majorité, le reste demandant quelques portes logiques pour faire la correction. L'implémentation en circuit demande donc trois choses :
* de calculer la retenue sortante et de quoi l'inverser ;
* un circuit qui vérifie si tous les bits opérande valent 0 : une porte NOR ou OU fait l'affaire ;
* un circuit qui vérifie s'ils valent tous 1, à savoir une porte ET.
Ensuite, on combine le résultat des trois circuits précédents pour obtenir le résultat final. La combinaison est le fait du circuit en jaune dans le schéma ci-dessous.
[[File:Full adder basé sur une modification de la retenue.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue]]
Le circuit en jaune est assez simple. Quand la porte ET sort un 1, la sortie doit être forcée à 1. Par contre, quand la porte OU sort un zéro, la sortie doit être forcée à 0. Il suffit donc d'enchainer un circuit de mise à 1 et un circuit de mise à 0. Nous avons vu dans le chapitre sur les opérations de masquage qu'il s'agit respectivement d'une porte OU et d'une porte ET.
[[File:Full adder basé sur une modification de la retenue interne, au complet.png|centre|vignette|upright=2|Full adder basé sur une modification de la retenue interne, au complet]]
Divers processeurs utilisaient des additionneurs complets de ce type : le fameux Z80, l'Intel 4004 et de l'Intel 8008 sont les exemples les plus notables. Pourtant, on pourrait s'étonner qu'un tel circuit ait existé. Il utilise beaucoup de portes logiques, a une profondeur logique supérieure : il n'a rien d'avantageux. Sauf qu'il était utilisé sur d'anciens processeurs, qui utilisaient la technologie dite TTL, différente de la technologie CMOS des transistors modernes. Et avec la technologie TTL, il est possible de fusionner plusieurs portes logiques ET et NOR en une seule porte logique ET/OU/NON ! Un additionneur complet construit ainsi ne prenait que deux portes logiques : une pour le calcul de la retenue sortante, une autre pour le reste du circuit.
==L'addition non signée==
Voyons maintenant un circuit capable d'additionner deux nombres entiers: l''''additionneur'''. Dans la version qu'on va voir, ce circuit manipulera des nombres strictement positifs. L'addition des nombres codés en complètement à deux sera vu dans une section ultérieure.
[[File:Full-adder-4bits-serial-CEI.svg|vignette|upright=0.5|Additionneur 4 bits, un bloc.]]
L'interface d'un additionneur est illustrée ci-contre. Un additionneur prend deux opérandes sur deux entrées séparées, et fournit le résultat sur une sortie dédiée. De plus, il a une entrée sur laquelle envoyer une retenue entrante, et a une sortie pour la retenue sortante. La sortie de retenue indique que le résultat de l'addition a débordé (au sens d'un débordement d'entier). Pour l'entrée de retenue, son utilité deviendra plus claire dans ce qui suit, mais souvenez-vous qu'elle existe.
L'entrée et la sortie de retenue sont parfois utilisées pour combiner plusieurs petits additionneurs, pour former un additionneur plus grand. Nous verrons de nombreux exemples dans ce qui suit. Il est assez commun de créer des additionneurs pour des opérandes de 16/32/64 bits, avec des additionneurs de 4/5 bits.
===L'additionneur série===
Il est possible d'additionner deux nombres bit par bit, grâce à un additionneur complet associé à plusieurs registres à décalages. Les opérandes sont placées chacune dans un registre à décalage, et l'additionneur complet utilise les bits sortants de ces registres à décalage. Le bit du résultat est envoyé au registre à décalage pour le résultat. La retenue de l'addition est stockée dans une bascule de 1 bit, en attente du prochain cycle d'horloge. Un tel additionneur est appelé un '''additionneur série'''. Il a été utilisé sur d'anciens ordinateurs dans les années 50-60, aussi bien des prototypes que des ordinateurs commerciaux.
[[File:Additionneur série.jpg|centre|vignette|upright=2.5|Additionneur série.]]
===L'additionneur à propagation de retenue===
L''''additionneur à propagation de retenue''' est aussi appelé l'additionneur ''ripple carry'', c'est son nom anglais. J'utiliserais occasionnellement le nom anglais dans ce qui suit, car celui-ci est plus court. Ilutilise un additionneur complet pour chaque colonne de l'addition. Pour gérer les retenues, il suffit ainsi de câbler des additionneurs complets les uns à la suite des autres, chacun envoyant sa retenue à la colonne suivante.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Notons la présence de la retenue sortante, qui est utilisée pour détecter les débordements d'entier, ainsi que pour d'autres opérations. Le bit de retenue final est souvent stocké dans un registre spécial du processeur (généralement appelé ''carry flag'').
Notez aussi, à droite du schéma précédent, l'entrée de retenue <math>\text{R}_\text{Entrée}</math>. Elle est très utile pour l'implémentation de certaines opérations comme l'inversion de signe, la soustraction, l'incrémentation, etc. Certains processeurs sont capables de faire une opération appelée ADC, ADDC ou autre nom signifiant ''Addition with Carry'', qui permet de faire le calcul A + B + Retenue (la retenue en question est la retenue sortante de l'addition précédente, stockée dans le registre ''carry flag''). Son utilité principale est de permettre des additions d'entiers plus grands que ceux supportés par le processeur. Par exemple, cela permet de faire des additions d'entiers 32 bits sur un processeur 16 bits.
[[File:Adder Network 3 Bit Sum Delay 003.svg|vignette|upright=0.5|Propagation de retenue dans l'additionneur.]]
Cet additionneur utilise très peu de portes logiques, ce qui fait qu'il était utilisé sur certains processeurs 8 et 16 bits, dont le budget en portes logiques était limité. Bien que très simple, cet additionneur est cependant peu performant. Son temps de calcul est proportionnel à la taille des opérandes. Par exemple, additionner deux opérandes de 32 bits prendra deux fois plus de temps que pour des opérandes de 16 bits. La raison est que les retenues doivent se propager à travers le circuit, du premier additionneur jusqu'au dernier.
L'addition étant une opération fréquente, d'autres additionneurs ont été inventés. Ils utilisent des optimisations qui utilisent plus de circuits pour gagner en rapidité. Mais avant de voir ces optimisations, nous allons voir s'il est possible d'optimiser les additionneurs à propagation de retenue.
===L'additionneur à saut de retenue===
L'additionneur à propagation de retenue peut être optimisé de deux manières. La première accélère la propagation de la retenue, en agissant au niveau des additionneurs complets. La ''Manchester carry chain'' est une optimisation de ce type, mais on ne peut pas encore expliquer à ce stade du cours, car elle optimisait les additionneurs complets au niveau des transistors eux-mêmes. Elle a été utilisée sur de nombreux processeurs connus, comme le 8086 d'Intel ou le Z80.
La seconde manière rend l'addition plus rapide dans certains cas. Le résultat est un additionneur dont le temps de calcul est variable. Le calcul prendra quelques cycles d'horloges avec certains opérandes, mais d'autres opérandes ne verront aucune amélioration. Il n'améliore pas le pire des cas, dans lequel la retenue doit être propagée du début à la fin, du bit de poids faible au bit de poids fort. Mais dans d'autres cas, il permet d'avoir le résultat en avance. L'optimisation en question donne l''''additionneur à saut de retenue''' (''carry-skip adder'').
Il est composé en enchainant plusieurs additionneurs plus petits, qui additionnent 4/5 bits, rarement plus. De tels additionneurs de petite taille sont appelés des '''blocs''', dans ce qui suit. L'idée est de détecter précocement si la retenue entrante est propagée à travers tout le bloc. Si la retenue est propagée dans tous le bloc, on peut directement l'envoyer sur la sortie de retenue, la retenue saute le bloc entier. Dans le cas contraire, on doit calculer la retenue normalement. Le choix entre les deux est le fait d'un multiplexeur. Toute la difficulté est de savoir comment commander le multiplexeur.
[[File:Carry skip adder, principe de base.png|centre|vignette|upright=2.5|Carry skip adder : principe de base]]
Pour savoir si une retenue est propagée dans un bloc entier, il faut utiliser des additionneurs complets, qui ont en plus une sortie P indiquant que l'additionneur propage la retenue. Le bloc propage une retenue si chaque additionneur complet propage la retenue, donc s'ils ont tous leur sortie P à 1. Un simple ET logique suffit à déterminer si c'est le cas. Le signal de commande du multiplexeur est donc un vulgaire ET entre toutes les sorties P des additionneurs complets du bloc.
[[File:Additionneur à saut de retenue.png|centre|vignette|upright=2.5|Calcul de la commande du MUX.]]
Voici ce que ça donne pour un bloc de 4 bits :
[[File:CSAdder4Bit.svg|centre|vignette|upright=1.5|Additionneur ''carry skip'' : bloc de 4 bits.]]
L'additionneur à saut de retenue est construit en assemblant plusieurs blocs de ce type.
[[File:BCSAdder16Bit.svg|centre|vignette|upright=3|Additionneur à saut de retenue.]]
Les blocs sont tous identiques dans le cas le plus simple, mais il est possible d'utiliser des blocs de taille variable. Par exemple, le premier bloc peut avoir des opérandes de 6 bits, le second des opérandes de 7 bits, etc. Faire ainsi permet de gagner un petit peu en performances, si la taille de chaque bloc est bien choisie. La raison est une question de temps de propagation des retenues. La retenue met plus de temps à se propager à travers 8 blocs qu'à travers 4, ce qui prend plus de temps qu'à travers 2 blocs, etc. En tenir compte fait que la taille des blocs tend à augmenter ou diminuer quand on se rapproche des bits de poids fort.
===L'additionneur à sélection de retenue===
L''''additionneur à sélection de retenue''' utilise aussi des blocs, comme les additionneurs précédents. L'addition se fait en deux versions : une avec la retenue du bloc précédent valant zéro, et une autre version avec la retenue du bloc précédent valant 1. Il suffira alors de choisir le bon résultat avec un multiplexeur, une fois cette retenue connue. On gagne ainsi du temps en calculant à l'avance les valeurs de certains bits du résultat, sans connaître la valeur de la retenue. Petit détail : sur certains additionneurs à sélection de retenue, les blocs de base n'ont pas la même taille. Cela permet de tenir compte des temps de propagation des retenues entre les blocs.
[[File:Additionneur à sélection de retenue.png|centre|vignette|upright=1.5|Additionneur à sélection de retenue avec seulement deux blocs.]]
Dans les exemples du dessus, chaque sous-additionneur étaient des additionneurs à propagation de retenue. Mais ce n'est pas une obligation, et tout autre type d’additionneur peut être utilisé. Par exemple, on peut faire en sorte que les sous-additionneurs soient eux-mêmes des additionneurs à sélection de retenue, et poursuivre ainsi de suite, récursivement. On obtient alors un '''additionneur à somme conditionnelle''', plus rapide que l'additionneur à sélection de retenue, mais qui utilise beaucoup plus de portes logiques.
===Les additionneurs à anticipation de retenue===
Les '''additionneurs à anticipation de retenue''' calculent chaque retenue sans avoir à propager les retenues précédentes. Au lieu de calculer les retenues une par une, ils calculent toutes les retenues en parallèle, à partir des bits des opérandes. Une fois les retenues pré-calculées, il suffit de les additionner avec les deux bits adéquats, pour obtenir le résultat. Ces additionneurs sont composés de deux parties :
* un circuit qui pré-calcule les retenues, sans les propager, directement à partir des opérandes ;
* d'une couche d'additionneurs complets, qui additionnent chacun deux bits d'opérande et la retenue pré-calculée.
[[File:Additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Additionneur à anticipation de retenue.]]
Le calcul des retenues se fait en deux étapes. La première détermine si les retenues sont propagées ou générées, pour chaque colonne. Elle détermine, pour chaque colonne, les bits P et G qui indiquent respectivement : qu'une retenue a été générée sur cette colonne, que la colonne propage la retenue précédente. La seconde étape utilise ces signaux P et G pour déterminer la retenue sur chaque colonne. Le circuit de calcul des retenues est donc composé de deux parties : une couche de demi-additionneurs pour générer les signaux P et G, un circuit de calcul des retenues proprement dit.
[[File:Circuit complet d'un additionneur à anticipation de retenue.png|centre|vignette|upright=1.5|Circuit complet d'un additionneur à anticipation de retenue.]]
Pour rappel, la retenue de la colonne i, notée <math>C_i</math> est égale à :
: <math>G_i + (P_i . C_{i-1})</math>, avec G le signal de génération de retenue, P le signal de propagation de retenue.
Si on utilisait cette formule sans trop réfléchir, on retomberait sur un additionneur à propagation de retenue inutilement compliqué. L'astuce des additionneurs à anticipation de retenue consiste à remplacer le terme Ci−1 par sa valeur calculée avant. Par exemple, je prends un additionneur 4 bits. Je dispose de deux nombres A et B, contenant chacun 4 bits : A3, A2, A1, et A0 pour le nombre A, et B3, B2, B1, et B0 pour le nombre B. Si j'effectue les remplacements, j'obtiens les formules suivantes :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Ces formules nous permettent de déduire la valeur d'une retenue directement : il reste alors à créer un circuit qui implémente ces formules, et le tour est joué.
[[File:Four bit adder with carry lookahead.svg|centre|vignette|upright=1.5|Additionneur à anticipation de retenue de 4 bits.]]
Plus haut, j'ai dit que le circuit était composé de trois couches : une couche de demi-additionneur pour les signaux P et G ; un circuit d'anticipation de retenue, puis l'addition finale. Mais les deux bits d'opérandes sont déjà additionnés dans la couche de demi-additionneur, autant réutiliser le résultat pour l'additionner à la retenue. Pour éliminer toute redondance de ce type, il est possible d'utiliser des additionneurs partiels (des additionneurs où la sortie de retenue a été remplacée par deux sorties P et G). L'additionneur final est alors composé de plusieurs additionneurs complets de ce type, couplé à un circuit d'anticipation de retenue.
[[File:Cla4bits.png|centre|vignette|upright=2.5|Additionneur à anticipation de retenue de 4 bits.]]
Les additionneurs à anticipation de retenue sont plus rapides que les additionneurs à propagation de retenue. La raison est que les retenues sont calculées rapidement, dans un temps plus petit qu'en propageant la retenue. Pour rentrer dans les détails mathématiques, le temps de calcul n'est pas proportionnel au nombre de bits, mais proportionnel au logarithme de ce nombre de bits. Par contre, le gain en temps de calcul s'accompagne d'un cout en portes logiques conséquent. Le circuit qui calcule les retenues anticipées utilise beaucoup de portes logiques.
===Les additionneurs à calcul parallèle de préfixe===
Du fait du cout en portes logiques, utiliser un additionneur à anticipation de retenue est une solution pratique pour additionner 4 ou 8 bits. Mais pour des opérandes plus grands, de 16 ou 32 bits, cela utiliserait trop de portes logiques. Une partie de ce cout en portes logiques est cependant lié à des redondances. Si on analyse deux retenues différentes, à des colonnes différentes. Les circuits qui calculent ces deux retenues seront différents, mais ils contiendront des sous-circuits identiques. Pour vous en rendre compte, reprenons les quatre équations obtenues précédemment et essayez de trouver des redondances :
* C1 = G0 + ( P0 · C0 ) ;
* C2 = G1 + ( P1 · G0 ) + ( P1 · P0 · C0 ) ;
* C3 = G2 + ( P2 · G1 ) + ( P2 · P1 · G0 ) + ( P2 · P1 · P0 · C0 ) ;
* C4 = G3 + ( P3 · G2 ) + ( P3 · P2 · G1 ) + ( P3 · P2 · P1 · G0 ) + ( P3 · P2 · P1 · P0 · C0 ).
Vous devriez trouver :
* le terme ''P0 · C0'' dans toutes les lignes ;
* les termes ''P1 · P0 · C0'' et ''P1 · P0'' dans les trois dernières lignes ;
* le terme ''P2 · P1 · P0 · C0'' ; ''P2 · P1 · G0'' et ''P2 · G1'' dans les deux dernières lignes.
Il existe plusieurs manières d'exploiter de telles redondances, exploitées dans les '''additionneurs à calcul parallèle de préfixe'''. L'optimisation apportée est de générer des signaux ''propagate'' et ''generate'' pour des groupes de 2, 3, 4, ..., N colonnes. Par exemple, il est possible de générer un signal ''P 0 vers 3'', qui indique si la retenue de la première colonne est propagée jusqu'à la 4ème colonne. Un autre exemple est un signal qui indique qu'une retenue a été générée entre les colonnes 4 à 7 génèrent une retenue ou non. En clair, les signaux P et G ont maintenant un intervalle, qui précise de quelle colonne vers quelle colonne se fait la propagation, ou entre quelles colonnes se fait la génération.
Il est possible de combiner les signaux P et G de deux groupes de colonne, s'ils sont contiguës (ils peuvent aussi se recouvrir). Par exemple, il est possible de calculer les bits P et G pour les colonnes 0 à 10, à partir des deux signaux P/G des colonnes 0-4 et 5-10. Pour cela, les équations sont assez simples. Si je prends deux groupes nommés A et B, avec A pour les colonnes de poids fort et B celles de poids faible, on a :
: <math>P_{sortie} = P_A . P_B</math>
: <math>G_{sortie} = G_A . P_A . G_B</math>
Pour les bits des colonnes 2, 4, 8, 16, 32, 64, et autres, le calcul est simple. L'idée est de grouper les colonnes par groupes de 2. Puis, on calcule chaque groupe de 4 bits à partir de deux groupes de 2. Puis les groupes de 8 bits sont calculés à partir de deux groupes de 4 bits, et ainsi de suite pour toute puissance de deux.
[[File:Squelette d'un additionneur à calcul parallèle de préfixe.jpg|centre|vignette|upright=2|Squelette d'un additionneur à calcul parallèle de préfixe]]
Les seules difficultés surviennent pour les colonnes dont le nombre n'est pas une puissance de deux. Il y a plusieurs manières pour subdiviser ces colonnes en intervalles plus petits et combiner le tout. Et elles donnent chacune des additionneurs différents. Ils portent les noms d''''additionneur de Ladner-Fisher''', d''''additionneur de Brent-Kung''', d''''additionneur de Kogge-Stone''', etc. Dans le cas général, l'additionneur de Kogge-Stone est un des plus rapide, mais aussi un des plus gourmand en circuits, alors que c'est l'inverse pour l'additionneur de Brent-Kung. Les deux additionneurs sont illustrés ci-dessous, pour des opérandes de 8 bits. Dans ceux-ci, chaque losange calculent les bits P et G, à partir des signaux P et G de deux groupes précédents.
{|
|[[File:Brent-kung-8-bit.png|vignette|Brent-kung-8-bit]]
|[[File:Kogge-stone-8-bit.png|vignette|upright=1.2|Kogge-stone-8-bit]]
|}
===Le calcul parallèle de la retenue===
L'anticipation de retenue est très rapide, mais utilise beaucoup de circuits, même en utilisant le calcul parallèle de préfixe. Les additionneurs à propagation de retenue font eux le compromis inverse. Mais il existe des intermédiaires, qui visent à obtenir un compromis entre performance et cout en portes logiques.
Un de ces compromis découpe un additionneur de 16/32/64 bits en additionneurs de 4/5 bits, qui sont enchainés en utilisant la propagation de retenue. Par contre, ces petits additionneurs de 4/5 bits utilisent l'anticipation de retenue. Ces additionneurs simples seront nommés ''blocs'' dans ce qui suit. Par exemple, on peut utiliser 4 additionneurs à anticipation de retenue de 4 bits, et propager les retenues entre eux. Le premier bloc calcule une retenue, qui est propagée au second bloc. Le second bloc calcule lui aussi une retenue, qui est propagée au troisième bloc, etc.
[[File:4008 Functional Diagram.svg|vignette|upright=1|4008 Functional Diagram]]
Le gain en performance est significatif, mais il est possible de faire presque aussi rapide, tout en économisant beaucoup de circuits. L'idée est de simplifier les additionneurs 4 bits, afin de ne pas utiliser une anticipation de retenue complète. L'anticipation de retenue n'est utilisée que pour la retenue sortante, alors que les bits de somme sont calculées avec propagation de retenue. La retenue sortante est donc calculée en parallèle de l'addition. Chaque bloc contient, à côté d'un additionneur proprement dit, un circuit qui calcule la retenue sortante. Il existe de nombreuses manières de calculer la retenue sortante.
Le gain en portes logiques est assez significatif. En effet, on se passe de circuits d'anticipation de retenue pour les 4 bits de somme, on ne garde que celui pour la retenue sortante. Le fait d'utiliser la propagation de retenue pour calculer les 4 bits de somme est censé avoir un léger cout en performance, mais il se trouve que ce cout est très faible. En effet, le résultat n'est connu qu'une fois les retenues propagées jusqu'au dernier bloc. Et malgré l'anticipation des retenues, la propagation est assez lente. les additionneurs à propagation de retenue auront terminé leur travail bien avant.
Un exemple est celui de l'additionneur CMOS 4008, un additionneur de 4 bit. Il est composé en trois sections. Une première couche de demi-additionneurs calcule les signaux P et G utilisés à la fois pour l'anticipation de la retenue sortante, que pour calculer les bits de somme. Le circuit de calcul de la retenue sortante utilise l'anticipation de retenue. Le reste du circuit fait l'addition en propageant les retenues.
[[File:CMOS 4008, circuit découpé en sections.png|centre|vignette|upright=1.5|CMOS 4008, circuit découpé en sections]]
===Les additionneurs à anticipation de retenue multi-niveau===
Les additionneurs précédents mixent anticipation et propagation de retenue. Ceux que nous allons voir utilisent des blocs, mais utilisent l'anticipation de retenue entre les blocs. Les blocs utilisés peuvent être un additionneur à propagation de retenue, ou à anticipation de retenue, peu importe. Ce qui compte est que la retenue entrante de chaque bloc est calculée par anticipation de retenue.
Par exemple, il est possible de créer un additionneur 16 bits en utilisant 4 additionneurs/blocs de 4 bits, couplé à un circuit d’anticipation de retenue par bloc. Pour cela, les additionneurs de 4 bits doivent fournir deux sorties P et G. Les deux sorties indiquent que le bloc soit a généré une retenue, soit a propagé la retenue entrante. Par propagé, on veut dire que la retenue entrante du bloc s'est retrouvée sur la sortie de retenue du bloc. C'est le cas si les 4 colonnes propagent la retenue, ce qui signifie que :
: <math>P_\text{4 bits} = P_0 \cdot P_1 \cdot P_2 \cdot P_3</math>
Pour la génération, cela veut dire qu'une colonne a générée une retenue, qui a été propagée jusqu'à la sortie. En clair, pour chaque colonne, il faut que le G = 1, et que les signaux P des colonnes soient à 1. Pour les 4 colonnes, cela donne :
: <math>G_\text{4 bits} = G_3 + G_2 \cdot P_3 + G_1 \cdot P_3 \cdot P_2 + G_0 \cdot P_3 \cdot P_2 \cdot P_1</math>
[[File:Cla4bitsPG.png|centre|vignette|upright=2|Additionneur à anticipation de retenue de 4 bits.]]
Pour combiner 4 blocs de 4 bits, on devra donc combiner 4 bits P et G, avec un circuit d'anticipation de retenue.
[[File:16-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 16 bits.]]
Il est même possible d'aller plus loin et de combiner 4 additionneurs précédents pour obtenir un additionneur 64 bits.
[[File:64-bit lookahead carry unit.svg|centre|vignette|upright=2|Additionneur à anticipation de retenue de 64 bits.]]
Le résultat est un additionneur à plusieurs niveaux d’anticipation de retenue. Une anticipation de retenue dans un blocs de 4 bits (facultative), entre blocs de 4 bits, entre blocs de 16 bits. La même logique peut être utilisée avec des blocs de taille différente de 4, 16 et 64 bits.
==L'addition signée et la soustraction==
Après avoir vu l'addition, il est logique de passer à la soustraction, les deux opérations étant très proches. Si on sait câbler une addition entre entiers positifs, câbler une soustraction n'est pas très compliqué. De plus, la soustraction permet de faire des additions de nombres signés.
===Le soustracteur pour opérandes entiers===
Pour soustraire deux nombres entiers, on peut adapter l'algorithme de soustraction utilisé en décimal, celui que vous avez appris à l'école. Celui-ci ressemble fortement à l'algorithme d'addition : on soustrait les bits de même poids, et on propage éventuellement une retenue sur la colonne suivante. À la différence de l'addition, la retenue est soustraite, et non ajoutée. La table de soustraction nous dit quel est le résultat de la soustraction de deux bits. La voici :
* 0 - 0 = 0 ;
* 0 - 1 = 1 et une retenue ;
* 1 - 0 = 1 ;
* 1 - 1 = 0.
[[File:Algebra1 05 fig014.svg|centre|vignette|Soustraction en binaire, avec les retenues en rouge.]]
La table de soustraction peut servir de table de vérité pour construire un circuit qui soustrait deux bits. Celui-ci est appelé un '''demi-soustracteur'''. Il ressemble beaucoup à un demi-additionneur, les différences se résumant à une porte NON ajoutée pour le calcul de la retenue.
[[File:Half Subtractor Vektor.svg|centre|vignette|Demi-soustracteur.]]
Comme pour l'additionneur, deux demi-soustracteurs peuvent être combinés pour donner un '''soustracteur complet'''. Le calcul de la retenue se fait en combinant les deux retenues des demi-soustracteurs avec une porte OU. Les soustracteurs complets sont utilisés pour créer des soustracteurs à propagation de retenue ou tout autre circuit soustracteur, sur le même modèle que les additionneurs.
[[File:FullSubtractor.svg|centre|vignette|upright=2|Soustracteur complet.]]
Vous l'aurez sans doute remarqué, mais le demi-soustracteur ressemble beaucoup au demi-additionneur. La seule différence est une porte NON ajoutée au bon endroit dans le demi-soustracteur.
{|
|[[File:Half-adder.svg|vignette|Demi-additionneur]]
|[[File:Half Subtractor Vektor.svg|vignette|upright=1.3|Demi-soustracteur]]
|}
La conséquence est qu'il est possible de créer un circuit capable de faire à la fois des additions et des soustractions. L'idée est de remplacer la porte NON du demi-soustracteur par un inverseur commandable. Si on envoie un zéro sur l'entrée de commande, l'inverseur commandable se comportera comme une porte OUI. Le circuit sera alors identique à un demi-additionneur. Si on envoie un 1 sur l'entrée de commande, l'inverseur commandable se comportera comme une porte NON. le circuit sera alors un demi-soustracteur. Il faut faire cela pour chaque demi-soustracteur, ce qui donne ceci pour un soustracteur complet. le résultat est alors appelé un '''additionneur-soustracteur complet'''.
[[File:Additionneur-soustracteur complet.png|centre|vignette|upright=2|Additionneur-soustracteur complet]]
===L'additionneur-soustracteur pour opérandes codées en complément à deux===
Étudions le cas de la soustraction en complément à deux, dans l'objectif de créer un circuit soustracteur. Vous savez sûrement que a−b et a+(−b) sont deux expressions équivalentes. Et en complément à deux, − b = not(b) + 1. Dit autrement, a − b = a + not(b) + 1. On pourrait se dire qu'il faut deux additionneurs pour faire le calcul, mais la majorité des additionneurs possède une entrée de retenue pour incrémenter le résultat de l'addition. Un soustracteur en complément à deux est donc simplement composé d'un additionneur et d'un inverseur.
[[File:Additionneur soustracteur.png|centre|vignette|upright=2|Soustracteur en complément à deux.]]
Il est possible de créer un circuit capable d'effectuer soit une addition, soit une soustraction : il suffit de remplacer l'inverseur par un inverseur commandable, qui peut être désactivé. On a vu comment créer un tel inverseur commandable dans le chapitre sur les circuits combinatoires. On peut remarquer que l'entrée de retenue et l'entrée de commande de l'inverseur sont activées en même temps : on peut fusionner les deux signaux en un seul.
[[File:Additionneur-soustracteur en complément à deux.png|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux.]]
Une implémentation alternative est la suivante. Elle remplace l'inverseur commandable par un multiplexeur.
[[File:4-bit ripple carry adder-subtracter.svg|centre|vignette|upright=2|Additionneur-soustracteur en complément à deux, version alternative.]]
===L'additionneur-soustracteur pour opérandes codées en signe-magnitude===
Passons maintenant aux nombres codés en signe-valeur absolue, les deux opérandes étant notées A et B. Suivant les signes des deux opérandes, on a quatre cas possibles : A + B, A − B (B négatif), −A + B (A négatif) et −A − B (A et B négatifs). Une astuce est que le circuit n'a besoin que de calculer A + B et A − B : il peut les inverser pour obtenir − A − B ou B − A. A + B et A − B peuvent se calculer avec un additionneur-soustracteur, reste à corriger le résultat. Il suffit de lui ajouter un inverseur commandable pour obtenir le circuit d'addition finale.
[[File:Additionneur en signe-valeur absolue.png|centre|vignette|upright=2|Additionneur en signe-valeur absolue.]]
Toute la difficulté tient dans le calcul du bit de signe du résultat, quand interviennent des soustractions. Autant l'addition de deux nombres de même signe (A + B et −A − B) ne pose aucun problème, autant les soustractions posent problème (A − B et −A + B). Suivant que <math>A<B</math> ou que <math>A>B</math>, le signe du résultat ne sera pas le même. Déterminer le signe du résultat se fait en regardant les bits de débordement d'entier, comme on le verra plus bas.
===L'additionneur-soustracteur pour opérandes codées en représentation par excès===
Passons maintenant aux nombres codés en représentation par excès. On pourrait croire que ces nombres s'additionnent comme des nombres non-signés, mais ce serait oublier la présence du biais, qui pose problème. Dans les cas de nombres signés gérés avec un biais, voyons ce que donne l'addition de deux nombres :
: <math>( n_1 + biais ) + ( n_2 + biais ) = n_1 + n_2 + 2 \times biais</math>
Or, le résultat correct serait :
: <math>n_1 + n_2 + biais</math>
En effectuant l'addition telle quelle, le biais est compté deux fois. On doit donc le soustraire après l'addition pour obtenir le résultat correct.
Même chose pour la soustraction qui donne ceci :
: <math>(n_1 + biais) - (n_2 + biais) = n_1 - n_2</math>
Or, le résultat correct serait :
: <math> ( n_1 - n_2 ) + biais </math>
Il faut rajouter le biais pour obtenir l'exposant correct.
On a donc besoin de deux additionneurs/soustracteurs : un pour additionner/soustraire les représentations binaires des opérandes, et un autre pour ajouter/retirer le biais en trop/manquant.
==L'additionneur BCD==
Maintenant, voyons un additionneur qui additionne deux entiers au format BCD. Pour cela, nous allons devoir passer par deux étapes. La première est de créer un circuit capable d'additionneur deux chiffres BCD. Ensuite, nous allons voir comment enchaîner ces circuits pour créer un additionneur BCD complet.
===L'additionneur BCD qui fait l'opération chiffre par chiffre===
Nous allons commencer par voir un additionneur qui additionne deux chiffres en BCD, une sorte d'équivalent BCD de l'additionneur complet. Il fournit un résultat sur 4 bits et une retenue qui est mise à 1 si le résultat dépasse 10 (la limite d'un chiffre BCD). Les deux opérandes sont des chiffres BCD codés sur 4 bits et sont additionnés en binaire par un additionneur des plus normaux, similaire à ceux vus plus haut. Le résultat est alors un entier codé en binaire, sur 5 bits, qu'on corrige/convertit pour obtenir un chiffre BCD et une retenue sortante.
Pour corriger le résultat, une idée intuitive serait de prendre le résultat et de faire une division par 10. Le quotient donne la retenue, alors que le reste est le résultat, le chiffre BCD. Mais un circuit diviseur par 10 utilise beaucoup de portes logiques, ce qui ne vaut pas le coup. Une autre méthode détecte si le résultat est égal ou supérieur à 10, ce qui correspond à un "débordement" (on dépasse les limites d'un chiffre BCD). Si le résultat est plus petit que 10, il n'y a rien à faire : le résultat est bon et la retenue est de zéro. Par contre, si le résultat vaut 10 ou plus, il faut corriger le résultat et générer une retenue à 1.
Il faut donc ajouter un circuit qui détecte si le résultat est supérieur à 9, qui calcule directement la retenue. Ce circuit peut se fabriquer simplement à partir de sa table de vérité, ou en utilisant les techniques que nous verrons dans un chapitre ultérieur sur les comparateurs. La solution la plus simple est clairement d'utiliser la table de vérité, ce qui est très simple, assez pour être laissé en exercice au lecteur. Pour comprendre comment corriger le résultat, établissons une table de vérité qui associe le résultat et le résultat corrigé. L'entrée vaut au minimum 10 et au maximum 9 + 9 = 18. On considère la sortie comme un tout, la retenue étant un 5ème bit, le bit de poids fort.
{|class="wikitable"
|-
! colspan="5" | Entrée
!
! rowspan="10" |
! Retenue
! Résultat corrigé (sans retenue)
! interprétation de la sortie en binaire (retenue inclue)
|-
| 0 || 1 || 0 || 1 || 0
| (10)
| 1 || 0000
| (16)
|-
| 0 || 1 || 0 || 1 || 1
| (11)
| 1 || 0001
| (17)
|-
| 0 || 1 || 1 || 0 || 0
| (12)
| 1 || 0010
| (18)
|-
| 0 || 1 || 1 || 0 || 1
| (13)
| 1 || 0011
| (19)
|-
| 0 || 1 || 1 || 1 || 0
| (14)
| 1 || 0100
| (20)
|-
| 0 || 1 || 1 || 1 || 1
| (15)
| 1 || 0101
| (21)
|-
| 1 || 0 || 0 || 0 || 0
| (16)
| 1 || 0110
| (22)
|-
| 1 || 0 || 0 || 0 || 1
| (17)
| 1 || 0111
| (23)
|-
| 1 || 0 || 0 || 1 || 0
| (18)
| 1 || 1000
| (24)
|}
En analysant le tableau, on voit que pour corriger le résultat, il suffit d'ajouter 6. La raison est que le résultat déborde d'un nibble à 16 en binaire, mais à 10 en décimal : il suffit d'ajouter la différence entre les deux, à savoir 6, et le débordement binaire fait son travail. Donc, la correction après une addition est très simple : si le résultat dépasse 9, on ajoute 6.
On peut maintenant implémenter l'additionneur BCD, en combinant le comparateur avec 10, le circuit de correction, et l'additionneur. La première solution calcule deux versions du résultat : la version corrigée, la version normale. Le choix entre les deux est réalisée par un multiplexeur, commandé par le comparateur.
[[File:Additionneur BCD.png|centre|vignette|upright=1.5|Additionneur BCD]]
L'autre solution utilise un circuit commandable qui soit additionne 6, soit ne fait rien. Le choix entre les deux est commandé par le bit calculé par le comparateur.
[[File:Additionneur BCD, seconde version.png|centre|vignette|upright=1.5|Additionneur BCD, seconde version.]]
Une version alternative du circuit précédent est la suivante. Il contient deux additionneurs : un pour additionner les deux chiffres BCD, un autre pour additionner 6 si besoin. Le résultat du comparateur est directement utilisé pour générer l'opérande du second additionneur : 0 ou 6. Le circuit est simple à concevoir, mais gaspille beaucoup de circuit. Idéalement, il vaudrait mieux utiliser un circuit combinatoire d'addition avec une constante.
[[File:Adder 4 Bit BCD.svg|centre|vignette|upright=2|Additionneur BCD, circuit complet.]]
Pour obtenir un additionneur BCD complet, il suffit d’enchaîner les additionneurs précédents, comme on le ferait avec les additionneurs complets dans un additionneur à propagation de retenue. Au final, l'additionneur BCD est beaucoup plus compliqué qu'un additionneur normal, car il rajoute un comparateur ">9", un petit additionneur pour ajouter 6 et éventuellement d'autres circuits. De plus, il est difficile d'appliquer les optimisations disponibles sur les additionneurs non-BCD. Notamment, les circuits d'anticipation de retenue sont totalement à refaire et le résultat est relativement compliqué. C'est ce qui explique pourquoi le BCD a progressivement été abandonné au profit du binaire simple.
La soustraction en BCD se fait comme en binaire : le nombre à soustraire est remplacé par son complément, le circuit additionne le complément et l'autre opérande, le débordement d'entier fait que le résultat marche. Sauf qu'ici, le complément est un complément à 9. Il se calcule chiffre par chiffre : chaque chiffre est remplacé par (9 - le chiffre en question).
===L'additionneur BCD par ajustement décimal===
L'additionneur BCD précédent effectuait son travail chiffre BCD par chiffre BCD, mais il existe des additionneurs BCD qui font autrement. Sur les premiers processeurs x86, il n'y avait pas d'opération d'addition BCD proprement dit, seulement une addition binaire normale de 8, 16 ou 32 bits. Par contre, elle était secondée par une opération dite d''''ajustement décimal''' qui transformait un nombre binaire en nombre codé en BCD. L'opération d'ajustement décimal prenait un opérande de 8 bits codé en binaire et fournissait un résultat de la même taille, c'est à dire deux chiffres BCD. Effectuer une addition BCD demandait donc de faire deux opérations à la suite : une addition binaire simple, suivie par l'opération d'ajustement décimal. Cela permettait de gérer des nombres entiers en binaire usuel et des entiers BCD sans avoir deux instructions d'addition séparées pour les deux, sans compter que cela simplifiait aussi les circuits d'addition.
L'ajustement décimal s'effectue en ajoutant une constante bien précise à l'opérande à convertir en BCD. L'idée est que la constante est découpée en morceaux de 4 bits, correspondant chacun à un chiffre BCD de l'opérande, chaque morceau contenant soit un 0, soit 6. Cela permet d'ajouter soit 0, soit 6, à chaque chiffre BCD, et donc de le corriger. La propagation des retenues d'un chiffre à l'autre est effectuée automatiquement par l'addition binaire de la constante. L'opération d'ajustement décimal calcule automatiquement la constante. Elle découpe l'opérande en ''nibbles'', vérifie si chaque ''nibble'' est supérieur ou égal à 10, puis détermine la valeur de chaque ''nibble'' de la constante finale. Par exemple, si je prends l'opérande 1001 1110, le ''nibble'' de poids faible déborde, alors que celui de poids fort non. La constante sera donc 0000 0110 : 0x06. Inversement, si le ''nibble'' de poids fort déborde et pas celui de poids faible, la constante sera alors 0x60. Et la constante est de 0x66 si les deux ''nibbles'' débordent, de 0x00 si aucun ne déborde.
Le circuit d’ajustement décimal est donc composé de trois étapes : deux étapes pour calculer la constante, et un circuit d'addition pour additionner cette constante au nombre de départ. La première étape découpe l'opérande en morceaux de 4 bits, en chiffres BCD, et vérifie si chacun d'entre eux vaut 10 ou plus. La seconde étape prend les résultats de la première étape, et les combine pour calculer la constante. Enfin, on trouve l'addition finale, qui était réalisée par un circuit d'addition utilisé à la fois pour l'ajustement décimal et l'addition binaire. La différence entre une addition normale et une opération d'ajustement décimal tient dans le fait que les deux premières étapes sont désactivées dans une addition normale.
[[File:Additionneur BCD parallèle.png|centre|vignette|upright=1.5|Additionneur BCD parallèle]]
===L'additionneur biquinaire===
Les entiers BCD ne sont qu'un des encodages hybrides entre décimal et binaire. L'encodage biquinaire est l'un d'entre eux et nous allons faire un rappel rapide à ce sujet. Pour simplifier, un chiffre encodé en biquinaire est composé de deux parties : un bit, couplé à une partie quinaire encodée en représentation ''one-hot''. La partie quinaire encode un nombre allant de 0 à 4, ce qui prend 5 bits (0, 1, 2, 3 et 4). Le bit indique s'il faut ou non ajouter 5 à la valeur encodée par la partie quinaire. Ainsi, on peut coder tous les nombres de 0 à 9.
Additionner deux nombres de biquinaire demande donc d'additionner deux parties quinaires encodées en ''one-hot'' et d'additionner deux bits. Mais attention : il faut tenir compte de la retenue de l'addition des parties quinaires. Et idéalement, il faut aussi tenir compte d'une retenue entrante, provenant de l'addition de la colonne de chiffres précédente. Toute la difficulté vient de la création de l'additionneur ''one-hot''. Heureusement, vu qu'il n'y a que 4-5 bits à additionner, il est souvent fabriqué à partir de sa table de vérité.
[[File:Additionneur bi-quinaire.png|centre|vignette|upright=2|Additionneur bi-quinaire]]
Un avantage du biquinaire est que le calcul du complément à 9 est très simple. Il faut pour cela : inverser la partie binaire avec une porte NON, puis inverser l'ordre des bits de la partie quinaire. Concrètement, le bit de poids faible devient le bit de poids fort, et ainsi de suite. Par exemple, une partie quinaire 01000 devient 00010, 10000 devient 00001, 00100 ne change pas, etc. Le tout peut se calculer avec une porte NON et 5 multiplexeurs.
===L'additionneur BCD avec calculs intermédiaires en biquinaire===
L'ordinateur IBM 1401, un ancien ''mainframe'' des années 60, utilisait un additionneur BCD un peu particulier. Les nombres étaient encodés en BCD dans la mémoire de l'ordinateur, mais les circuits de calcul utilisaient la représentation biquinaire. Lors d'un calcul, le processeur de l'ordinateur traduisait les chiffres BCD en représentation biquinaire, faisait une addition en biquinaire, avant de traduire le résultat en BCD normal.
Pour être précis, l'IBM 1401 utilisait une variante du biquinaire. L'encodage biquinaire de l'IBM 1401 est le suivant : la partie binaire disait si le chiffre était pair ou non, la partie quinaire encodait les valeurs 0, 2, 4, 6 et 8. Le chiffre se calculait en additionnant la partie binaire (0 ou 1) au nombre pair encodé par la partie quinaire. Si l'IBM 1401 utilisait cette variante du biquinaire, c'est car elle donnait des circuits de conversion BCD-biquinaire plus économes en portes logiques et plus rapides.
La partie binaire est le bit de poids faible du chiffre BCD, la partie biquinaire est calculée par un simple décodeur qui prend en entrée le chiffre BCD, amputé de son bit de poids faible. La traduction inverse demande d'utiliser un encodeur, à la place du décodeur. Par contre, le circuit d'addition biquinaire était plus compliqué du fait de la gestion des retenues. L'addition des parties binaires et quinaires se faisait en parallèle, dans deux additionneurs séparés. Cependant, l'addition des parties binaire fournit une retenue, qu'il faut prendre en compte. Pour cela, l'IBM 1401 disposait d'un troisième additionneur qui fournissait le résultat final, encodé en biquinaire.
[[File:Additionneur bi-quinaire de l'IBM 1401.png|centre|vignette|upright=2|Additionneur biquinaire de l'IBM 1401]]
Une implémentation moderne demanderait d'utiliser des portes ET combinées à des portes OU, le circuit pouvant être construit simplement à partir de sa table de vérité. Sur l'IBM 1401, le circuit était cependant différent, en raison de l'utilisation de OU câblés, des croisements de fils qui fonctionnent comme des portes OU, que nous n'avons pas encore vu pour le moment, mais qui seront détaillés dans quelques chapitres. Les OU câblés étaient utilisés pour simplifier le design du circuit, mais demandaient des portes logiques spécifiques, ce qui collait avec le fait que ce ''mainframe'' utilisait des transistors en Germanium. L'implémentation exacte est décrite dans cet article de blog, mais je ne recommande sa lecture qu'à ceux qui savent ce qu'est un OU câblé :
* [https://www.righto.com/2015/10/qui-binary-arithmetic-how-1960s-ibm.html Qui-binary arithmetic: how a 1960s IBM mainframe does math].
==Les débordements d'entier lors d'une addition/soustraction==
Les instructions arithmétiques manipulent des entiers codés sur un nombre fixe de bits, qui ne peuvent prendre leurs valeurs que dans un intervalle. Pour les nombres positifs, un ordinateur qui code ses entiers sur n bits pourra coder tous les entiers allant de 0 à <math>2^n -1</math>. Pour les nombres négatifs, l'intervalle est différent et dépend de la représentation utilisée. Dans le cas général, l'ordinateur peut coder les valeurs comprises de <math>N_\text{min}</math> à <math>N_\text{max}</math>. Si le résultat d'un calcul sort de cet intervalle, il ne peut pas être représenté par l'ordinateur et il se produit ce qu'on appelle un '''débordement d'entier'''.
La '''valeur haute de débordement''' désigne la première valeur qui est trop grande pour être représentée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 0 et 7, la valeur haute de débordement est égale à 8. On peut aussi définir la '''valeur basse de débordement''', qui est la première valeur trop petite pour être codée par l'ordinateur. Par exemple, pour un ordinateur qui peut coder tous les nombres entre 8 et 250, la valeur basse de débordement est égale à 7. Pour les nombres entiers, la valeur haute de débordement vaut <math>N_\text{max} + 1</math> , alors que la valeur basse vaut <math>N_\text{min} - 1</math> (avec <math>N_\text{max}</math> et <math>N_\text{min}</math> respectivement la plus grande et la plus petite valeur codable par l'ordinateur).
===La correction des débordements d'entier : l'arithmétique saturée===
Quand un débordement d'entier survient, tous les circuits de calcul ne procèdent pas de la même manière. Dans les grandes lignes, il y a deux réactions possibles : soit on corrige automatiquement le résultat du débordement, soit on ne fait rien et on se contente de détecter le débordement.
Si le débordement n'est pas corrigé automatiquement, le circuit ne conserve que les bits de poids faibles du résultat. Les bits en trop sont simplement ignorés. On dit qu'on utilise l''''arithmétique modulaire'''. Le problème avec ce genre d'arithmétique, c'est qu'une opération entre deux grands nombres peut donner un résultat très petit. Par exemple, si je dispose de registres 4 bits et que je souhaite faire l'addition 1111 + 0010 (ce qui donne 15 + 2), le résultat est censé être 10001 (17), ce qui est un résultat plus grand que la taille d'un registre. En conservant les 4 bits de poids faible, j’obtiens 0001 (1). En clair, un résultat très grand est transformé en un résultat très petit. Cela peut poser problèmes si on travaille uniquement avec des nombres positifs, mais c'est aussi utilisé pour coder des nombres en complément à deux.
D'autres circuits utilisent ce qu'on appelle l''''arithmétique saturée''' : si un calcul génère un débordement, on arrondi le résultat au plus grand entier supporté par le circuit. Les circuits capables de calculer en arithmétique saturée sont un peu plus complexes, vu qu'il faut rajouter des circuits pour corriger le résultat en cas de débordement. Il suffit généralement de rajouter un ''circuit de saturation'', qui prend en entrée le résultat et le corrige en cas de débordement. Ce circuit de saturation met la valeur maximale en sortie si un débordement survient, mais se contente de recopier le résultat du calcul sur sa sortie s'il n'y a pas de débordement. Typiquement, il est composé d'une couche de multiplexeurs, qui sélectionnent quelle valeur mettre sur la sortie : soit le résultat du calcul, soit le plus grand nombre entier géré par le processeur, soit le plus petit (pour les nombres négatifs/soustractions).
L'arithmétique saturée est utilisée pour les additions et soustractions, mais c'est plus rare pour les multiplications/divisions. Une des raisons est que le résultat d'une addition/soustraction prend un bit de plus que le résultat, là où les multiplications doublent le nombre de bits. Quand une addition déborde, le résultat réel est proche de la valeur maximale codable. mais quand une multiplication déborde, le résultat peut parfois valoir 200 à 60000 fois plus que la valeur maximale codable. Les calculs avec une valeur saturée/corrigée sont donc crédibles pour une suite d'additions, mais pas pour une suite de multiplications.
===La détection des débordements entiers===
Quand un débordement d'entier a eu lieu, il vaut mieux que l'additionneur prévienne ! Pour cela, l'additionneur a une '''sortie de débordement''', parfois nommée ''Overflow'', dont la valeur indique si l'addition a généré un débordement d'entier ou non. Reste que détecter un débordement ne se fait pas de la même manière selon que l'on parle d'un additionneur non-signé ou signé.
Pour les additionneur non-signés, l'additionneur calcule un bit de plus que ce qui est supporté par l'ordinateur. Par exemple, un additionneur 32 bits fournit un résultat sur 33 bits, un débordement d'entier a lieu quand le 33ème bit est à 1. Précisément, la sortie de débordement n'est autre que la retenue finale, celle fournie par le dernier additionneur complet. Le seul type de débordement possible est un débordement par le haut, où le résultat dépasse la valeur maximale. Avec l'arithmétique saturée, le circuit de saturation consiste en une seule couche de multiplexeurs, voire en un circuit de mise à la valeur maximale tel que vu dans le chapitre sur les opérations bits à bits.
[[File:Gestion des débordements d'entiers.png|centre|vignette|upright=2.5|Gestion des débordements d'entiers lors d'une addition non-signée.]]
Pour les additionneurs non-signés, la gestion des débordements d'entiers dépend fortement de la représentation signée. Nous allons étudier le cas du complément à deux. Si vous vous rappelez le chapitre 1, les calculs sur des nombres en complètement à deux utilisent les règles de l'arithmétique modulaire, c'est une condition nécessaire. À priori, on peut penser que dans ces conditions, les débordements d'entiers sont une chose parfaitement normale, qui nous permet d'avoir des résultats corrects. Néanmoins, certains débordements d'entiers peuvent survenir malgré tout et produire des bugs assez ennuyeux.
Si l'on tient en compte les règles du complément à deux, on sait que le bit de poids fort (le plus à gauche) permet de déterminer si le nombre est positif ou négatif : il indique le signe du nombre. Tout se passe comme si les entiers en complément à deux étaient codés sur un bit de moins, et avaient leur longueur amputé du bit de poids fort. Si le résultat d'un calcul écrase le bit de poids fort, il y a un débordement d'entiers. Il existe une règle simple qui permet de détecter ces débordements d'entiers. L'addition de deux nombres positifs ne peut pas être un nombre négatif. Si on additionne deux nombres dont le bit de signe est à 0 et que le bit de signe du résultat est à 1, on est en face d'un débordement d'entiers. Même chose pour deux nombres négatifs : le résultat de l'addition ne peut pas être positif. On peut résumer cela en une phrase : si deux nombres de même signe sont ajoutés, un débordement a lieu quand le bit du signe du résultat a le signe opposé.
Modifier les circuits d'au-dessus pour qu'ils détectent les débordements en complément à deux est simple comme bonjour : il suffit créer un petit circuit combinatoire qui prenne en entrée les bits de signe des opérandes et du résultat, et qui fasse le calcul de l'indicateur de débordements. Si l'on rédige sa table de vérité, on doit se retrouver avec la table suivante :
{|class="wikitable"
|-
!Entrées
!Sortie
|-
|000||0
|-
|001||1
|-
|010||0
|-
|011||0
|-
|100||0
|-
|101||0
|-
|110||1
|-
|111||0
|}
L'équation de ce circuit est la suivante, avec <math>S_a</math> et <math>S_b</math> les signes des deux opérandes, et <math>C_i</math> la retenue de la colonne précédente :
: <math> ( S_a . S_b . \overline{C_i} ) + ( \overline{S_a} . \overline{S_b} . C_i )</math>
En simplifiant, on obtient alors :
: <math>( S_a . S_b ) \oplus C_i</math>
Or, il se trouve que <math>S_a . S_b</math> est tout simplement la retenue en sortie du dernier additionneur, que nous noterons <math>C_o</math>. On trouve donc :
: <math>C_o \oplus C_i</math>
Il suffit donc de faire un XOR entre la dernière retenue et la précédente pour obtenir le bit de débordement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de décalage et de rotation
| prevText=Les circuits de décalage et de rotation
| next=Les circuits de comparaison
| nextText=Les circuits de comparaison
}}
</noinclude>
c5wrtd925pzx6b8lca03ug7nti19h14
Fonctionnement d'un ordinateur/Le chemin de données
0
69025
768597
768557
2026-06-25T14:16:57Z
Mewtow
31375
/* Les registres d'interfaçage mémoire */
768597
wikitext
text/x-wiki
Comme vu précédemment, le '''chemin de donnée''' est l'ensemble des composants dans lesquels circulent les données dans le processeur. Il comprend l'unité de calcul, les registres, l'unité de communication avec la mémoire, et le ou les interconnexions qui permettent à tout ce petit monde de communiquer. Dans ce chapitre, nous allons voir ces composants en détail.
==Les unités de calcul==
Le processeur contient des circuits capables de faire des calculs arithmétiques, des opérations logiques, et des comparaisons, qui sont regroupés dans une unité de calcul appelée '''unité arithmétique et logique'''. Certains préfèrent l’appellation anglaise ''arithmetic and logic unit'', ou ALU. Par défaut, ce terme est réservé aux unités de calcul qui manipulent des nombres entiers. Les unités de calcul spécialisées pour les calculs flottants sont désignées par le terme "unité de calcul flottant", ou encore FPU (''Floating Point Unit'').
L'interface d'une unité de calcul est assez simple : on a des entrées pour les opérandes et une sortie pour le résultat du calcul. De plus, les instructions de comparaisons ou de calcul peuvent mettre à jour le registre d'état, qui est relié à une autre sortie de l’unité de calcul. Une autre entrée, l''''entrée de sélection de l'instruction''', spécifie l'opération à effectuer. Elle sert à configurer l'unité de calcul pour faire une addition et pas une multiplication, par exemple. Sur cette entrée, on envoie un numéro qui précise l'opération à effectuer. La correspondance entre ce numéro et l'opération à exécuter dépend de l'unité de calcul. Sur les processeurs où l'encodage des instructions est "simple", une partie de l'opcode de l'instruction est envoyé sur cette entrée.
[[File:Unité de calcul usuelle.png|centre|vignette|upright=2|Unité de calcul usuelle.]]
Il faut signaler que les processeurs modernes possèdent plusieurs unités de calcul, toutes reliées aux registres. Cela permet d’exécuter plusieurs calculs en même temps dans des unités de calcul différentes, afin d'augmenter les performances du processeur. Diverses technologies, abordées dans la suite du cours permettent de profiter au mieux de ces unités de calcul : pipeline, exécution dans le désordre, exécution superscalaire, jeux d'instructions VLIW, etc. Mais laissons cela de côté pour le moment.
===L'ALU entière : additions, soustractions, opérations bit à bit===
Un processeur contient plusieurs ALUs spécialisées. La principale, présente sur tous les processeurs, est l''''ALU entière'''. Elle s'occupe uniquement des opérations sur des nombres entiers, les nombres flottants sont gérés par une ALU à part. Elle gère des opérations simples : additions, soustractions, opérations bit à bit, parfois des décalages/rotations. Par contre, elle ne gère pas la multiplication et la division, qui sont prises en charge par un circuit multiplieur/diviseur à part.
L'ALU entière a déjà été vue dans un chapitre antérieur, nommé "Les unités arithmétiques et logiques entières (simples)", qui expliquait comment en concevoir une. Nous avions vu qu'une ALU entière est une sorte de circuit additionneur-soustracteur amélioré, ce qui explique qu'elle gère des opérations entières simples, mais pas la multiplication ni la division. Nous ne reviendrons pas dessus. Cependant, il y a des choses à dire sur leur intégration au processeur.
Une ALU entière gère souvent une opération particulière, qui ne fait rien et recopie simplement une de ses opérandes sur sa sortie. L'opération en question est appelée l''''opération ''Pass through''''', encore appelée opération NOP. Elle est implémentée en utilisant un simple multiplexeur, placé en sortie de l'ALU. Le fait qu'une ALU puisse effectuer une opération ''Pass through'' permet de fortement simplifier le chemin de donnée, d'économiser des multiplexeurs. Mais nous verrons cela sous peu.
[[File:ALU avec opération NOP.png|centre|vignette|upright=2|ALU avec opération NOP.]]
Avant l'invention du microprocesseur, le processeur n'était pas un circuit intégré unique. L'ALU, le séquenceur et les registres étaient dans des puces séparées. Les ALU étaient vendues séparément et manipulaient des opérandes de 4/8 bits, les ALU 4 bits étaient très fréquentes. Si on voulait créer une ALU pour des opérandes plus grandes, il fallait construire l'ALU en combinant plusieurs ALU 4/8 bits. Par exemple, l'ALU des processeurs AMD Am2900 est une ALU de 16 bits composée de plusieurs sous-ALU de 4 bits. Cette technique qui consiste à créer des unités de calcul à partir d'unités de calcul plus élémentaires s'appelle en jargon technique du '''bit slicing'''. Nous en avions parlé dans le chapitre sur les unités de calcul, aussi nous n'en reparlerons pas plus ici.
L'ALU manipule des opérandes codées sur un certain nombre de bits. Par exemple, une ALU peut manipuler des entiers codés sur 8 bits, sur 16 bits, etc. En général, la taille des opérandes de l'ALU est la même que la taille des registres. Un processeur 32 bits, avec des registres de 32 bit, a une ALU de 32 bits. C'est intuitif, et cela rend l'implémentation du processeur bien plus facile. Mais il y a quelques exceptions, où l'ALU manipule des opérandes plus petits que la taille des registres. Par exemple, de nombreux processeurs 16 bits, avec des registres de 16 bits, utilisent une ALU de 8 bits. Un autre exemple assez connu est celui du Motorola 68000, qui était un processeur 32 bits, mais dont l'ALU faisait juste 16 bits. Son successeur, le 68020, avait lui une ALU de 32 bits.
Sur de tels processeurs, les calculs sont fait en plusieurs passes. Par exemple, avec une ALU 8 bit, les opérations sur des opérandes 8 bits se font en un cycle d'horloge, celles sur 16 bits se font en deux cycles, celles en 32 en quatre, etc. Si un programme manipule assez peu d'opérandes 16/32/64 bits, la perte de performance est assez faible. Diverses techniques visent à améliorer les performances, mais elles ne font pas de miracles. Par exemple, vu que l'ALU est plus courte, il est possible de la faire fonctionner à plus haute fréquence, pour réduire la perte de performance.
Pour comprendre comme est implémenté ce système de passes, prenons l'exemple du processeur 8 bit Z80. Ses registres entiers étaient des registres de 8 bits, alors que l'ALU était de 4 bits. Les calculs étaient faits en deux phases : une qui traite les 4 bits de poids faible, une autre qui traite les 4 bits de poids fort. Pour cela, les opérandes étaient placées dans des registres de 4 bits en entrée de l'ALU, plusieurs multiplexeurs sélectionnaient les 4 bits adéquats, le résultat était mémorisé dans un registre de résultat de 8 bits, un démultiplexeur plaçait les 4 bits du résultat au bon endroit dans ce registre. L'unité de contrôle s'occupait de la commande des multiplexeurs/démultiplexeurs. Les autres processeurs 8 ou 16 bits utilisent des circuits similaires pour faire leurs calculs en plusieurs fois.
[[File:ALU du Z80.png|centre|vignette|upright=2|ALU du Z80]]
Un exemple extrême est celui des des '''processeurs sériels''' (sous-entendu ''bit-sériels''), qui utilisent une '''ALU sérielle''', qui fait leurs calculs bit par bit, un bit à la fois. S'il a existé des processeurs de 1 bit, comme le Motorola MC14500B, la majeure partie des processeurs sériels étaient des processeurs 4, 8 ou 16 bits. L'avantage de ces ALU est qu'elles utilisent peu de transistors, au détriment des performances par rapport aux processeurs non-sériels. Mais un autre avantage est qu'elles peuvent gérer des opérandes de grande taille, avec plus d'une trentaine de bits, sans trop de problèmes.
===Les circuits multiplieurs et diviseurs===
Les processeurs modernes ont une ALU pour les opérations simples (additions, décalages, opérations logiques), couplée à une ALU pour les multiplications, un circuit multiplieur séparé. Précisons qu'il ne sert pas à grand chose de fusionner le circuit multiplieur avec l'ALU, mieux vaut les garder séparés par simplicité. Les processeurs haute performance disposent systématiquement d'un circuit multiplieur et gèrent la multiplication dans leur jeu d'instruction.
Le cas de la division est plus compliqué. La présence d'un circuit multiplieur est commune, mais les circuits diviseurs sont eux très rares. Leur cout en circuit est globalement le même que pour un circuit multiplieur, mais le gain en performance est plus faible. Le gain en performance pour la multiplication est modéré car il s'agit d'une opération très fréquente, alors qu'il est très faible pour la division car celle-ci est beaucoup moins fréquente.
Pour réduire le cout en circuits, il arrive que l'ALU pour les multiplications gère à la fois la multiplication et la division. Les circuits multiplieurs et diviseurs sont en effet très similaires et partagent beaucoup de points communs. Généralement, la fusion se fait pour les multiplieurs/diviseurs itératifs.
===Le ''barrel shifter''===
On vient d'expliquer que la présence de plusieurs ALU spécialisée est très utile pour implémenter des opérations compliquées à insérer dans une unité de calcul normale, comme la multiplication et la division. Mais les décalages sont aussi dans ce cas, de même que les rotations. Nous avions vu il y a quelques chapitres qu'ils sont réalisés par un circuit spécialisé, appelé un ''barrel shifter'', qu'il est difficile de fusionner avec une ALU normale. Aussi, beaucoup de processeurs incorporent un ''barrel shifter'' séparé de l'ALU.
Les processeurs ARM utilise un ''barrel shifter'', mais d'une manière un peu spéciale. On a vu il y a quelques chapitres que si on fait une opération logique, une addition, une soustraction ou une comparaison, la seconde opérande peut être décalée automatiquement. L'instruction incorpore le type de de décalage à faire et par combien de rangs il faut décaler directement à côté de l'opcode. Cela simplifie grandement les calculs d'adresse, qui se font en une seule instruction, contre deux ou trois sur d'autres architectures. Et pour cela, l'ALU proprement dite est précédée par un ''barrel shifter'',une seconde ALU spécialisée dans les décalages. Notons que les instructions MOV font aussi partie des instructions où la seconde opérande (le registre source) peut être décalé : cela signifie que les MOV passent par l'ALU, qui effectue alors un NOP, une opération logique OUI.
===Les unités de calcul spécialisées===
Un processeur peut disposer d’unités de calcul séparées de l'unité de calcul principale, spécialisées dans les décalages, les divisions, etc. Et certaines d'entre elles sont spécialisées dans des opérations spécifiques, qui ne sont techniquement pas des opérations entières, sur des nombres entiers.
[[File:Unité de calcul flottante, intérieur.png|vignette|upright=1|Unité de calcul flottante, intérieur]]
Depuis les années 90-2000, presque tous les processeurs utilisent une unité de calcul spécialisée pour les nombres flottants : la '''Floating-Point Unit''', aussi appelée FPU. En général, elle regroupe un additionneur-soustracteur flottant et un multiplieur flottant. Parfois, elle incorpore un diviseur flottant, tout dépend du processeur. Précisons que sur certains processeurs, la FPU et l'ALU entière ne vont pas à la même fréquence, pour des raisons de performance et de consommation d'énergie !
La FPU intègre un circuit multiplieur entier, utilisé pour les multiplications flottantes, afin de multiplier les mantisses entre elles. Quelques processeurs utilisaient ce multiplieur pour faire les multiplications entières. En clair, au lieu d'avoir un multiplieur entier séparé du multiplieur flottant, les deux sont fusionnés en un seul circuit. Il s'agit d'une optimisation qui a été utilisée sur quelques processeurs 32 bits, qui supportaient les flottants 64 bits (double précision). Les processeurs Atom étaient dans ce cas, idem pour l'Athlon première génération. Les processeurs modernes n'utilisent pas cette optimisation pour des raisons qu'on ne peut pas expliquer ici (réduction des dépendances structurelles, émission multiple).
De nombreux processeurs modernes disposent d'une unité de calcul spécialisée dans le calcul des conditions, tests et branchements. C’est notamment le cas sur les processeurs sans registre d'état, qui disposent de registres à prédicats. En général, les registres à prédicats sont placés à part des autres registres, dans un banc de registre séparé. L'unité de calcul normale n'est pas reliée aux registres à prédicats, alors que l'unité de calcul pour les branchements/test/conditions l'est. Les registres à prédicats sont situés juste en sortie de cette unité de calcul.
Il existe des unités de calcul spécialisées pour les calculs d'adresse. Elles ne supportent guère plus que des incrémentations/décrémentations, des additions/soustractions, et des décalages simples. Les autres opérations n'ont pas de sens avec des adresses. L'usage d'ALU spécialisées pour les adresses est un avantage sur les processeurs où les adresses ont une taille différente des données, ce qui est fréquent sur les anciens processeurs 8 bits.
==Les registres du processeur==
Après avoir vu l'unité de calcul, il est temps de passer aux registres d'un processeur. L'organisation des registres est généralement assez compliquée, avec quelques registres séparés des autres comme le registre d'état ou le ''program counter''. Les registres d'un processeur peuvent se classer en deux camps : soit ce sont des registres isolés, soit ils sont regroupés en paquets appelés banc de registres.
Un '''banc de registres''' (''register file'') est une RAM, dont chaque byte est un registre. Il regroupe un paquet de registres différents dans un seul composant, dans une seule mémoire. Dans processeur moderne, on trouve un ou plusieurs bancs de registres. La répartition des registres, à savoir quels registres sont dans le banc de registre et quels sont ceux isolés, est très variable suivant les processeurs.
[[File:Register File Simple.svg|centre|vignette|upright=1|Banc de registres simplifié.]]
===L'adressage du banc de registres===
Le banc de registre est une mémoire comme une autre, avec une entrée d'adresse qui permet de sélectionner le registre voulu. Plutot que d'adresse, nous allons parler d''''identifiant de registre'''. Le séquenceur forge l'identifiant de registre en fonction des registres sélectionnés. Dans les chapitres précédents, nous avions vu qu'il existe plusieurs méthodes pour sélectionner un registre, qui portent les noms de modes d'adressage. Et bien les modes d'adressage jouent un grand rôle dans la forge de l'identifiant de registre.
Pour rappel, sur la quasi-totalité des processeurs actuels, les registres généraux sont identifiés par un nom de registre, terme trompeur vu que ce nom est en réalité un numéro. En clair, les processeurs numérotent les registres, le numéro/nom du registre permettant de l'identifier. Par exemple, si je veux faire une addition, je dois préciser les deux registres pour les opérandes, et éventuellement le registre pour le résultat : et bien ces registres seront identifiés par un numéro. Mais tous les registres ne sont pas numérotés et ceux qui ne le sont pas sont adressés implicitement. Par exemple, le pointeur de pile sera modifié par les instructions qui manipulent la pile, sans que cela aie besoin d'être précisé par un nom de registre dans l'instruction.
Dans le cas le plus simple, les registres nommés vont dans le banc de registres, les registres adressés implicitement sont en-dehors, dans des registres isolés. L'idéntifiant de registre est alors simplement le nom de registre, le numéro. Le séquenceur extrait ce nom de registre de l'insutrction, avant de l'envoyer sur l'entrée d'adresse du banc de registre.
[[File:Adressage du banc de registres généruax.png|centre|vignette|upright=2|Adressage du banc de registres généraux]]
Dans un cas plus complexe, des registres non-nommés sont placés dans le banc de registres. Par exemple, les pointeurs de pile sont souvent placés dans le banc de registre, même s'ils sont adressés implicitement. Même des registres aussi importants que le ''program counter'' peuvent se mettre dans le banc de registre ! Nous verrons le cas du ''program counter'' dans le chapitre suivant, qui porte sur l'unité de chargement. Dans ce cas, le séquenceur forge l'identifiant de registre de lui-même. Dans le cas des registres nommés, il ajoute quelques bits aux noms de registres. Pour les registres adressés implicitement, il forge l'identifiant à partir de rien.
[[File:Adressage du banc de registre - cas général.png|centre|vignette|upright=2|Adressage du banc de registre - cas général]]
Nous verrons plus bas que dans certains cas, le nom de registre ne suffit pas à adresser un registre dans un banc de registre. Dans ce cas, le séquenceur rajoute des bits, comme dans l'exemple précédent. Tout ce qu'il faut retenir est que l'identifiant de registre est forgé par le séquenceur, qui se base entre autres sur le nom de registre s'il est présent, sur l'instruction exécutée dans le cas d'un registre adressé implicitement.
===Les registres généraux===
Pour rappel, les registres généraux peuvent mémoriser des entiers, des adresses, ou toute autre donnée codée en binaire. Ils sont souvent séparés des registres flottants sur les architectures modernes. Les registres généraux sont rassemblés dans un banc de registre dédié, appelé le '''banc de registres généraux'''. Le banc de registres généraux est une mémoire multiport, avec au moins un port d'écriture et deux ports de lecture. La raison est que les instructions lisent deux opérandes dans les registres et enregistrent leur résultat dans des registres. Le tout se marie bien avec un banc de registre à deux de lecture (pour les opérandes) et un d'écriture (pour le résultat).
[[File:Banc de registre multiports.png|centre|vignette|upright=2|Banc de registre multiports.]]
L'interface exacte dépend de si l'architecture est une architecture 2 ou 3 adresses. Pour rappel, la différence entre les deux tient dans la manière dont on précise le registre où enregistrer le résultat d'une opération. Avec les architectures 2-adresses, on précise deux registres : le premier sert à la fois comme opérande et pour mémoriser le résultat, l'autre sert uniquement d'opérande. Un des registres est donc écrasé pour enregistrer le résultat. Sur les architecture 3-adresses, on précise trois registres : deux pour les opérandes, un pour le résultat.
Les architectures 2-adresses ont un banc de registre où on doit préciser deux "adresses", deux noms de registre. L'interface du banc de registre est donc la suivante :
[[File:Register File Medium.svg|centre|vignette|upright=1.5|Register File d'une architecture à 2-adresses]]
Les architectures 3-adresses doivent rajouter une troisième entrée pour préciser un troisième nom de registre. L'interface du banc de registre est donc la suivante :
[[File:Register File Large.svg|centre|vignette|upright=1.5|Register File d'une architecture à 3-adresses]]
Rien n'empêche d'utiliser plusieurs bancs de registres sur un processeur qui utilise des registres généraux. La raison est une question d'optimisation. Au-delà d'un certain nombre de registres, il devient difficile d'utiliser un seul gros banc de registres. Il faut alors scinder le banc de registres en plusieurs bancs de registres séparés. Le problème est qu'il faut prévoir de quoi échanger des données entre les bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue du langage machine. Sur d'autres processeurs, les transferts de données entre bancs de registres se font via une instruction spéciale, souvent appelée COPY.
===Les registres flottants : banc de registre séparé ou unifié===
Passons maintenant aux registres flottants. Intuitivement, on a des registres séparés pour les entiers et les flottants. Il est alors plus simple d'utiliser un banc de registres séparé pour les nombres flottants, à côté d'un banc de registre entiers. L'avantage est que les nombres flottants et entiers n'ont pas forcément la même taille, ce qui se marie bien avec deux bancs de registres, où la taille des registres est différente dans les deux bancs.
Mais d'autres processeurs utilisent un seul '''banc de registres unifié''', qui regroupe tous les registres de données, qu'ils soient entier ou flottants. Par exemple, c'est le cas des Pentium Pro, Pentium II, Pentium III, ou des Pentium M : ces processeurs ont des registres séparés pour les flottants et les entiers, mais ils sont regroupés dans un seul banc de registres. Avec cette organisation, un registre flottant et un registre entier peuvent avoir le même nom de registre en langage machine, mais l'adresse envoyée au banc de registres ne doit pas être la même : le séquenceur ajoute des bits au nom de registre pour former l'adresse finale.
[[File:Désambiguïsation de registres sur un banc de registres unifié.png|centre|vignette|upright=2|Désambiguïsation de registres sur un banc de registres unifié.]]
===Le registre d'état===
Le registre d'état fait souvent bande à part et n'est pas placé dans un banc de registres. En effet, le registre d'état est très lié à l'unité de calcul. Il reçoit des indicateurs/''flags'' provenant de la sortie de l'unité de calcul, et met ceux-ci à disposition du reste du processeur. Son entrée est connectée à l'unité de calcul, sa sortie est reliée au séquenceur et/ou au bus interne au processeur.
Le registre d'état est relié au séquenceur afin que celui-ci puisse gérer les instructions de branchement, qui ont parfois besoin de connaitre certains bits du registre d'état pour savoir si une condition a été remplie ou non. D'autres processeurs relient aussi le registre d'état au bus interne, ce qui permet de lire son contenu et de le copier dans un registre de données. Cela permet d'implémenter certaines instructions, notamment celles qui permettent de mémoriser le registre d'état dans un registre général.
[[File:Place du registre d'état dans le chemin de données.png|centre|vignette|upright=2|Place du registre d'état dans le chemin de données]]
L'ALU fournit une sortie différente pour chaque bit du registre d'état, la connexion du registre d'état est directe, comme indiqué dans le schéma suivant. Vous remarquerez que le bit de retenue est à la fois connecté à la sortie de l'ALU, mais aussi sur son entrée. Ainsi, le bit de retenue calculé par une opération peut être utilisé pour la suivante. Sans cela, diverses instructions comme les opérations ''add with carry'' ne seraient pas possibles.
[[File:AluStatusRegister.svg|centre|vignette|upright=2|Registre d'état et unit de calcul.]]
Il est techniquement possible de mettre le registre d'état dans le banc de registre, pour économiser un registre. La principale difficulté est que les instructions doivent faire deux écritures dans le banc de registre : une pour le registre de destination, une pour le registre d'état. Soit on utilise deux ports d'écriture, soit on fait les deux écritures l'une après l'autre. Dans les deux cas, le cout en performances et en transistors n'en vaut pas le cout. D'ailleurs, je ne connais aucun processeur qui utilise cette technique.
Il faut noter que le registre d'état n'existe pas forcément en tant que tel dans le processeur. Quelques processeurs, dont le 8086 d'Intel, utilisent des bascules dispersées dans le processeur au lieu d'un vrai registre d'état. Les bascules dispersées mémorisent chacune un bit du registre d'état et sont placées là où elles sont le plus utile. Les bascules utilisées pour les branchements sont proches du séquenceur, le bascules pour les bits de retenue sont placées proche de l'ALU, etc.
===Les registres à prédicats===
Les registres à prédicats remplacent le registre d'état sur certains processeurs. Pour rappel, les registres à prédicat sont des registres de 1 bit qui mémorisent les résultats des comparaisons et instructions de test. Ils sont nommés/numérotés, mais les numéros en question sont distincts de ceux utilisés pour les registres généraux.
Ils sont placés à part, dans un banc de registres séparé. Le banc de registres à prédicats a une entrée de 1 bit connectée à l'ALU et une sortie de un bit connectée au séquenceur. Le banc de registres à prédicats est parfois relié à une unité de calcul spécialisée dans les conditions/instructions de test. Pour rappel, certaines instructions permettent de faire un ET, un OU, un XOR entre deux registres à prédicats. Pour cela, l'unité de calcul dédiée aux conditions peut lire les registres à prédicats, pour combiner le contenu de plusieurs d'entre eux.
[[File:Banc de registre pour les registres à prédicats.png|centre|vignette|upright=2|Banc de registre pour les registres à prédicats]]
===Les registres dédiés aux interruptions===
Dans le chapitre sur les registres, nous avions vu que certains processeurs dupliquaient leurs registres architecturaux, pour accélérer les interruptions ou les appels de fonction. Dans le cas qui va nous intéresser, les interruptions avaient accès à leurs propres registres, séparés des registres architecturaux. Les processeurs de ce type ont deux ensembles de registres identiques : un dédié aux interruptions, un autre pour les programmes normaux. Les registres dans les deux ensembles ont les mêmes noms, mais le processeur choisit le bon ensemble suivant s'il est dans une interruption ou non. Si on peut utiliser deux bancs de registres séparés, il est aussi possible d'utiliser un banc de registre unifié pour les deux.
Sur certains processeurs, le banc de registre est dupliqué en plusieurs exemplaires. La technique est utilisée pour les interruptions. Certains processeurs ont deux ensembles de registres identiques : un dédié aux interruptions, un autre pour les programmes normaux. Les registres dans les deux ensembles ont les mêmes noms, mais le processeur choisit le bon ensemble suivant s'il est dans une interruption ou non. On peut utiliser deux bancs de registres séparés, un pour les interruptions, et un pour les programmes.
Sur d'autres processeurs, on utilise un banc de registre unifié pour les deux ensembles de registres. Les registres pour les interruptions sont dans les adresses hautes, les registres pour les programmes dans les adresses basses. Le choix entre les deux est réalisé par un bit qui indique si on est dans une interruption ou non, disponible dans une bascule du processeur. Appelons là la bascule I.
===Le fenêtrage de registres===
[[File:Fenetre de registres.png|vignette|upright=1|Fenêtre de registres.]]
Le '''fenêtrage de registres''' fait que chaque fonction a accès à son propre ensemble de registres, sa propre fenêtre de registres. Là encore, cette technique duplique chaque registre architectural en plusieurs exemplaires qui portent le même nom. Chaque ensemble de registres architecturaux forme une fenêtre de registre, qui contient autant de registres qu'il y a de registres architecturaux. Lorsqu'une fonction s’exécute, elle se réserve une fenêtre inutilisée, et peut utiliser les registres de la fenêtre comme bon lui semble : une fonction manipule le registre architectural de la fenêtre réservée, mais pas les registres avec le même nom dans les autres fenêtres.
Il peut s'implémenter soit avec un banc de registres unifié, soit avec un banc de registre par fenêtre de registres.
Il est possible d'utiliser des bancs de registres dupliqués pour le fenêtrage de registres. Chaque fenêtre de registre a son propre banc de registres. Le choix entre le banc de registre à utiliser est fait par un registre qui mémorise le numéro de la fenêtre en cours. Ce registre commande un multiplexeur qui permet de choisir le banc de registre adéquat.
[[File:Fenêtrage de registres au niveau du banc de registres.png|vignette|Fenêtrage de registres au niveau du banc de registres.]]
L'utilisation d'un banc de registres unifié permet d'implémenter facilement le fenêtrage de registres. Il suffit pour cela de regrouper tous les registres des différentes fenêtres dans un seul banc de registres. Il suffit de faire comme vu au-dessus : rajouter des bits au nom de registre pour faire la différence entre les fenêtres. Cela implique de se souvenir dans quelle fenêtre de registre on est actuellement, cette information étant mémorisée dans un registre qui stocke le numéro de la fenêtre courante. Pour changer de fenêtre, il suffit de modifier le contenu de ce registre lors d'un appel ou retour de fonction avec un petit circuit combinatoire. Bien sûr, il faut aussi prendre en compte le cas où ce registre déborde, ce qui demande d'ajouter des circuits pour gérer la situation.
[[File:Désambiguïsation des fenêtres de registres.png|centre|vignette|upright=2|Désambiguïsation des fenêtres de registres.]]
==L'unité mémoire==
L''''interface avec la mémoire''' est, comme son nom l'indique, des circuits qui servent d'intermédiaire entre le bus mémoire et le processeur. Elle est parfois appelée l'unité mémoire, l'unité d'accès mémoire, la ''load-store unit'', et j'en oublie. Nous utiliserons le terme d''''unité mémoire''', au même titre qu'on utilise le terme d'unité de calcul.
[[File:Unité de communication avec la mémoire, de type simple port.png|centre|vignette|upright=2|Unité de communication avec la mémoire, de type simple port.]]
Sur certains processeurs, elle gère les mémoires multiport.
[[File:Unité de communication avec la mémoire, de type multiport.png|centre|vignette|upright=2|Unité de communication avec la mémoire, de type multiport.]]
===Les registres d'interfaçage mémoire===
L'interface mémoire se résume le plus souvent à des '''registres d’interfaçage mémoire''', intercalés entre le bus mémoire et le chemin de données. Généralement, il y a au moins deux registres d’interfaçage mémoire : un registre relié au bus d'adresse, et autre relié au bus de données.
[[File:Registres d’interfaçage mémoire.png|centre|vignette|upright=2|Registres d’interfaçage mémoire.]]
Au lieu de lire ou écrire directement sur le bus, le processeur lit ou écrit dans ces registres, alors que l'unité mémoire s'occupe des échanges entre registres et bus mémoire. Lors d'une écriture, le processeur place l'adresse dans le registre d'interfaçage d'adresse, met la donnée à écrire dans le registre d'interfaçage de donnée, puis laisse l'unité d'accès mémoire faire son travail. Lors d'une lecture, il place l'adresse à lire sur le registre d'interfaçage d'adresse, il attend que la donnée soit lue, puis récupère la donnée dans le registre d'interfaçage de données.
L'avantage est que le processeur n'a pas à maintenir une donnée/adresse sur le bus durant tout un accès mémoire. Par exemple, prenons le cas où la mémoire met 15 cycles processeurs pour faire une lecture ou une écriture. Sans registres d'interfaçage mémoire, le processeur doit maintenir l'adresse durant 15 cycles, et aussi la donnée dans le cas d'une écriture. Avec ces registres, le processeur écrit dans les registres d'interfaçage mémoire au premier cycle, et passe les 14 cycles suivants à faire quelque chose d'autre. Par exemple, il faut faire un calcul en parallèle, envoyer des signaux de commande au banc de registre pour qu'il soit prêt une fois la donnée lue arrivée, etc. Cet avantage simplifie l'implémentation de certains modes d'adressage, comme on le verra à la fin du chapitre.
Les registres d’interfaçage peuvent être adressables, même si c'était extrêmement rare et que ca n'a été utilisé que sur de vieux ''mainframes''. Un exemple est celui du Burroughs B1700, qui expose deux registres d’interfaçage pour les données et un registre pour le bus d'adresse. Ils sont tous les trois adressables, ce qui fait que le processeur peut lire ou écrire dans ces registres avec une instruction MOV. Ils sont appelés : READ pour la donnée lue depuis la mémoire RAM, WRITE pour la donnée à écrire lors d'une écriture, MAR pour le bus d'adresse.
Une lecture/écriture était ainsi réalisée en plusieurs étapes, le séquenceur ne se souciait pas d'implémenter les instructions LOAD/STORE en plusieurs micro-opérations. Il fallait tout faire à la main, avec des instructions machines. Pour une lecture, il fallait placer l'adresse dans le registre MAR, puis exécuter une instruction LOAD, et récupérer la donnée lue dans le registre READ. Cela prenait une instruction MOV, suivie d'une instruction LOAD, elle-même suivie par une instruction MOV. avec un second MOV. Pour une écriture, il fallait placer la donnée à écrire dans le registre WRITE, puis placer l'adresse dans le registre MAR, et lancer une instruction WRITE. Le tout prenait donc deux MOV et une instruction WRITE.
===La gestion de l'alignement et du boutisme===
L'unité mémoire gère les accès mémoire non-alignés, à cheval sur deux mots mémoire (rappelez-vous le chapitre sur l'alignement mémoire). Dans le cas où les accès non-alignés sont interdits, elle lève une exception matérielle. Dans le cas où ils sont autorisés, elle les gère automatiquement, à savoir qu'elle charge deux mots mémoire et les combine entre eux pour donner le résultat final. Dans les deux cas, cela demande d'ajouter des circuits de détection des accès non-alignés, et éventuellement des circuits pour la double lecture/écriture.
Les circuits de détection des accès non-alignés sont très simples. Dans le cas où les adresses sont alignées sur une puissance de deux (cas le plus courant), il suffit de vérifier les bits de poids faible de l'adresse à lire. Prenons l'exemple d'un processeur avec des adresses codées sur 64 bits, avec des mots mémoire de 32 bits, alignés sur 32 bits (4 octets). Un mot mémoire contient 4 octets, les contraintes d'alignement font que les adresses autorisées sont des multiples de 4. En conséquence, les 2 bits de poids faible d'une adresse valide sont censés être à 0. En vérifiant la valeur de ces deux bits, on détecte facilement les accès non-alignés.
En clair, détecter les accès non-alignés demande de tester si les bits de poids faibles adéquats sont à 0. Il suffit donc d'un circuit de comparaison avec zéro; qui est une simple porte OU. Cette porte OU génère un bit qui indique si l'accès testé est aligné ou non : 1 si l'accès est non-aligné, 0 sinon. Le signal peut être transmis au séquenceur pour générer une exception matérielle, ou utilisé dans l'unité d'accès mémoire pour la double lecture/écriture.
La gestion automatique des accès non-alignés est plus complexe. Dans ce cas, l'unité mémoire charge deux mots mémoire et les combine entre eux pour donner le résultat final. Charger deux mots mémoires consécutifs est assez simple, si le registre d'interfaçage est un compteur. L'accès initial charge le premier mot mémoire, puis l'adresse stockée dans le registre d'interfaçage est incrémentée pour démarrer un second accès. Le circuit pour combiner deux mots mémoire contient des registres, des circuits de décalage, des multiplexeurs.
===L'unité de calcul d'adresse===
Les registres d'interfaçage sont presque toujours présents, mais le circuit que nous allons voir est complétement facultatif. Il s'agit d'une unité de calcul spécialisée dans les calculs d'adresse, dont nous avons parlé rapidement dans la section sur les ALU. Elle s'appelle l''''''Address generation unit''''', ou AGU. Elle est parfois séparée de l'interface mémoire proprement dit, et est alors considérée comme une unité de calcul à part.
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>
b42aohk1odeozaiq86ng20lsgv4s40w
Fonctionnement d'un ordinateur/Sommaire
0
69596
768624
768131
2026-06-25T15:22:44Z
Mewtow
31375
/* Les circuits séquentiels */
768624
wikitext
text/x-wiki
__NOTOC__
* [[Fonctionnement d'un ordinateur/Introduction|Introduction]]
==Le codage des informations==
* [[Fonctionnement d'un ordinateur/L'encodage des données|L'encodage des données]]
* [[Fonctionnement d'un ordinateur/Le codage des nombres|Le codage des nombres]]
* [[Fonctionnement d'un ordinateur/Les codes de détection/correction d'erreur|Les codes de détection/correction d'erreur]]
==Les circuits électroniques==
* [[Fonctionnement d'un ordinateur/Les portes logiques|Les portes logiques]]
===Les circuits combinatoires===
* [[Fonctionnement d'un ordinateur/Les circuits combinatoires|Les circuits combinatoires]]
* [[Fonctionnement d'un ordinateur/Les circuits de masquage|Les circuits de masquage]]
* [[Fonctionnement d'un ordinateur/Les circuits de sélection|Les circuits de sélection]]
* [[Fonctionnement d'un ordinateur/Les circuits incrémenteurs/décrémenteurs|Les circuits incrémenteurs/décrémenteurs]]
===Les circuits séquentiels===
* [[Fonctionnement d'un ordinateur/Les bascules : des mémoires de 1 bit|Les bascules : des mémoires de 1 bit]]
* [[Fonctionnement d'un ordinateur/Les circuits synchrones et asynchrones|Les circuits synchrones et asynchrones]]
* [[Fonctionnement d'un ordinateur/Les registres et mémoires adressables|Les registres et mémoires adressables]]
* [[Fonctionnement d'un ordinateur/Les compteurs et timers|Les compteurs et timers]]
* [[Fonctionnement d'un ordinateur/Les registres à décalage et les LSFR|Les registres à décalage et les LSFR]]
===Les circuits de calcul et de comparaison===
* [[Fonctionnement d'un ordinateur/Les circuits de décalage et de rotation|Les circuits de décalage et de rotation]]
* [[Fonctionnement d'un ordinateur/Les circuits pour l'addition et la soustraction|Les circuits pour l'addition et la soustraction]]
* [[Fonctionnement d'un ordinateur/Les circuits de comparaison|Les circuits de comparaison]]
* [[Fonctionnement d'un ordinateur/Les unités arithmétiques et logiques entières (simples)|Les unités arithmétiques et logiques entières (simples)]]
* [[Fonctionnement d'un ordinateur/Les circuits pour l'addition multiopérande|Les circuits pour l'addition multiopérande]]
* [[Fonctionnement d'un ordinateur/Les circuits pour la multiplication et la division|Les circuits pour la multiplication et la division]]
* [[Fonctionnement d'un ordinateur/Les circuits de calcul logique et bit à bit|Les circuits de calcul logique et bit à bit]]
* [[Fonctionnement d'un ordinateur/Les circuits de calcul flottant|Les circuits de calcul flottant]]
* [[Fonctionnement d'un ordinateur/Les circuits de calcul trigonométriques|Les circuits de calcul trigonométriques]]
===Les circuits intégrés à semi-conducteurs===
* [[Fonctionnement d'un ordinateur/Les transistors et portes logiques|Les transistors et portes logiques]]
* [[Fonctionnement d'un ordinateur/Les circuits intégrés|Les circuits intégrés]]
* [[Fonctionnement d'un ordinateur/L'interface électrique entre circuits intégrés et bus|L'interface électrique entre circuits intégrés et bus]]
==L'architecture d'un ordinateur==
* [[Fonctionnement d'un ordinateur/L'architecture de base d'un ordinateur|L'architecture de base d'un ordinateur]]
* [[Fonctionnement d'un ordinateur/La hiérarchie mémoire|La hiérarchie mémoire]]
* [[Fonctionnement d'un ordinateur/La performance d'un ordinateur|La performance d'un ordinateur]]
* [[Fonctionnement d'un ordinateur/La loi de Moore et les tendances technologiques|La loi de Moore et les tendances technologiques]]
* [[Fonctionnement d'un ordinateur/Les techniques de réduction de la consommation électrique d'un processeur|Les techniques de réduction de la consommation électrique d'un processeur]]
==Les bus électroniques et la carte mère==
* [[Fonctionnement d'un ordinateur/La carte mère, chipset et BIOS|La carte mère, chipset et BIOS]]
* [[Fonctionnement d'un ordinateur/Les bus et liaisons point à point (généralités)|Les bus et liaisons point à point (généralités)]]
* [[Fonctionnement d'un ordinateur/Les encodages spécifiques aux bus|Les encodages spécifiques aux bus]]
* [[Fonctionnement d'un ordinateur/Les liaisons point à point|Les liaisons point à point]]
* [[Fonctionnement d'un ordinateur/Les bus électroniques|Les bus électroniques]]
* [[Fonctionnement d'un ordinateur/Quelques exemples de bus et de liaisons point à point|Quelques exemples de bus et de liaisons point à point]]
==Les mémoires RAM/ROM==
* [[Fonctionnement d'un ordinateur/Les différents types de mémoires|Les différents types de mémoires]]
* [[Fonctionnement d'un ordinateur/L'interface d'une mémoire électronique|L'interface d'une mémoire électronique]]
* [[Fonctionnement d'un ordinateur/Le bus mémoire|Le bus mémoire]]
===La micro-architecture d'une mémoire adressable===
* [[Fonctionnement d'un ordinateur/Les cellules mémoires|Les cellules mémoires]]
* [[Fonctionnement d'un ordinateur/Le plan mémoire|Le plan mémoire]]
* [[Fonctionnement d'un ordinateur/Contrôleur mémoire interne|Le contrôleur mémoire interne]]
* [[Fonctionnement d'un ordinateur/Mémoires évoluées|Les mémoires évoluées]]
===Les mémoires primaires===
* [[Fonctionnement d'un ordinateur/Les mémoires ROM|Les mémoires ROM : Mask ROM, PROM, EPROM, EEPROM, Flash]]
* [[Fonctionnement d'un ordinateur/Les mémoires SRAM synchrones|Les mémoires SRAM synchrones]]
* [[Fonctionnement d'un ordinateur/Les mémoires RAM dynamiques (DRAM)|Les mémoires RAM dynamiques (DRAM)]]
* [[Fonctionnement d'un ordinateur/Contrôleur mémoire externe|Le contrôleur mémoire externe]]
===Les mémoires exotiques===
* [[Fonctionnement d'un ordinateur/Les mémoires associatives|Les mémoires associatives]]
* [[Fonctionnement d'un ordinateur/Les mémoires FIFO et LIFO|Les mémoires FIFO et LIFO]]
==Le processeur==
===L'architecture externe===
* [[Fonctionnement d'un ordinateur/Langage machine et assembleur|Langage machine et assembleur]]
* [[Fonctionnement d'un ordinateur/Les registres du processeur|Les registres du processeur]]
* [[Fonctionnement d'un ordinateur/Le modèle mémoire : alignement et boutisme|Le modèle mémoire : alignement et boutisme]]
* [[Fonctionnement d'un ordinateur/Les modes d'adressage|Les modes d'adressage]]
* [[Fonctionnement d'un ordinateur/L'encodage des instructions|L'encodage des instructions]]
* [[Fonctionnement d'un ordinateur/Les jeux d'instructions|Les jeux d'instructions]]
* [[Fonctionnement d'un ordinateur/La pile d'appel et les fonctions|La pile d'appel et les fonctions]]
* [[Fonctionnement d'un ordinateur/Les interruptions et exceptions|Les interruptions et exceptions]]
===La micro-architecture===
* [[Fonctionnement d'un ordinateur/Les composants d'un processeur|Les composants d'un processeur]]
* [[Fonctionnement d'un ordinateur/Le chemin de données|Le chemin de données]]
* [[Fonctionnement d'un ordinateur/L'unité de chargement et le program counter|L'unité de chargement et le program counter]]
* [[Fonctionnement d'un ordinateur/L'unité de contrôle|L'unité de contrôle]]
* [[Fonctionnement d'un ordinateur/L'implémentation matérielle des branchements|L'implémentation matérielle des branchements]]
===Les jeux d'instruction anciens, avant les registres généraux ou exotiques===
* [[Fonctionnement d'un ordinateur/Les architectures à accumulateur|Les architectures à accumulateur]]
* [[Fonctionnement d'un ordinateur/Les architectures à pile et mémoire-mémoire|Les architectures à pile et mémoire-mémoire]]
* [[Fonctionnement d'un ordinateur/Les processeurs 8 bits et moins|Les processeurs 8 bits et moins]]
===L'espace d'adressage du processeur et la multiprogrammation===
* [[Fonctionnement d'un ordinateur/L'espace d'adressage du processeur|L'espace d'adressage du processeur]]
* [[Fonctionnement d'un ordinateur/L'abstraction mémoire et la mémoire virtuelle|L'abstraction mémoire et la mémoire virtuelle]]
==Les entrées-sorties et périphériques==
* [[Fonctionnement d'un ordinateur/Les méthodes de synchronisation entre processeur et périphériques|Les méthodes de synchronisation entre processeur et périphériques]]
* [[Fonctionnement d'un ordinateur/L'adressage des périphériques|L'adressage des périphériques]]
* [[Fonctionnement d'un ordinateur/Les périphériques et les cartes d'extension|Les périphériques et les cartes d'extension]]
==Les mémoires de stockage==
* [[Fonctionnement d'un ordinateur/Les mémoires de masse : généralités|Les mémoires de masse : généralités]]
* [[Fonctionnement d'un ordinateur/Les disques durs|Les disques durs]]
* [[Fonctionnement d'un ordinateur/Les solid-state drives|Les solid-state drives]]
* [[Fonctionnement d'un ordinateur/Les disques optiques|Les disques optiques]]
* [[Fonctionnement d'un ordinateur/Compléments sur les mémoires de masse|Compléments sur les mémoires de masse]]
==La ou les mémoires caches==
* [[Fonctionnement d'un ordinateur/Les mémoires cache|Les mémoires cache]]
* [[Fonctionnement d'un ordinateur/Le préchargement|Le préchargement]]
* [[Fonctionnement d'un ordinateur/Le Translation Lookaside Buffer|Le ''Translation Lookaside Buffer'']]
==Le parallélisme d’instructions==
* [[Fonctionnement d'un ordinateur/Le pipeline|Le pipeline]]
===Les branchements et le ''front-end''===
* [[Fonctionnement d'un ordinateur/La prédiction de branchement|La prédiction de branchement]]
* [[Fonctionnement d'un ordinateur/Les optimisations du chargement des instructions|Les optimisations du chargement des instructions]]
===Les pipelines multicycles simples===
* [[Fonctionnement d'un ordinateur/Les pipelines multicycles|Les pipelines multicycles]]
* [[Fonctionnement d'un ordinateur/L'émission dans l'ordre des instructions|L'émission dans l'ordre des instructions]]
* [[Fonctionnement d'un ordinateur/Le contournement (data forwarding)|Le contournement (data forwarding)]]
* [[Fonctionnement d'un ordinateur/Les premiers processeurs Intel|Les premiers processeurs Intel]]
===L’exécution dans le désordre===
* [[Fonctionnement d'un ordinateur/L'exécution dans le désordre|L'exécution dans le désordre]]
* [[Fonctionnement d'un ordinateur/Le renommage de registres|Le renommage de registres]]
* [[Fonctionnement d'un ordinateur/Le scoreboarding et l'algorithme de Tomasulo|Annexe : Le scoreboarding et l'algorithme de Tomasulo]]
===Les optimisations des accès mémoire===
* [[Fonctionnement d'un ordinateur/La désambiguïsation mémoire|La désambiguïsation mémoire]]
* [[Fonctionnement d'un ordinateur/Le parallélisme mémoire|Le parallélisme mémoire]]
===L'émission multiple===
* [[Fonctionnement d'un ordinateur/Les processeurs superscalaires|Les processeurs superscalaires]]
* [[Fonctionnement d'un ordinateur/Exemples de microarchitectures CPU : le cas du x86|Exemples de CPU superscalaires: le cas du x86]]
* [[Fonctionnement d'un ordinateur/Les processeurs VLIW et EPIC|Les processeurs VLIW et EPIC]]
* [[Fonctionnement d'un ordinateur/Les architectures dataflow|Les architectures dataflow]]
==Les architectures parallèles==
* [[Fonctionnement d'un ordinateur/Les architectures parallèles|Les architectures parallèles]]
* [[Fonctionnement d'un ordinateur/Architectures multiprocesseurs et multicœurs|Les architectures multiprocesseurs et multicœurs]]
* [[Fonctionnement d'un ordinateur/Architectures multithreadées et Hyperthreading|Les architectures multithreadées et Hyperthreading]]
* [[Fonctionnement d'un ordinateur/Les architectures à parallélisme de données|Les architectures à parallélisme de données]]
* [[Fonctionnement d'un ordinateur/La cohérence des caches|La cohérence des caches]]
* [[Fonctionnement d'un ordinateur/Les sections critiques et le modèle mémoire|Les sections critiques et le modèle mémoire]]
==Annexes==
===Les nombres flottants : FPUs et coprocesseurs===
* [[Fonctionnement d'un ordinateur/Un exemple de jeu d'instruction : l'extension x87|Un exemple de jeu d'instruction : l'extension x87]]
* [[Fonctionnement d'un ordinateur/Les coprocesseurs : FPU et IO|Les coprocesseurs : FPU et IO]]
===Les jeux d’instruction spécialisés===
* [[Fonctionnement d'un ordinateur/L'accélération matérielle de la virtualisation|L'accélération matérielle de la virtualisation]]
* [[Fonctionnement d'un ordinateur/Les ISA optimisés pour la compilation/interprétation|Les ISA optimisés pour la compilation/interprétation]]
* [[Fonctionnement d'un ordinateur/Les processeurs de traitement du signal|Les processeurs de traitement du signal]]
* [[Fonctionnement d'un ordinateur/Les architectures actionnées par déplacement|Les architectures actionnées par déplacement]]
* [[Fonctionnement d'un ordinateur/Les architectures systoliques|Les architectures systoliques]]
* [[Fonctionnement d'un ordinateur/Les architectures neuromorphiques|Les réseaux de neurones matériels]]
===Les autres annexes===
* [[Fonctionnement d'un ordinateur/Le matériel réseau|Le matériel réseau]]
* [[Fonctionnement d'un ordinateur/La tolérance aux pannes|La tolérance aux pannes]]
* [[Fonctionnement d'un ordinateur/Les ordinateurs de première génération : tubes à vide et mémoires|Les ordinateurs de première génération : tubes à vide et mémoires]]
* [[Fonctionnement d'un ordinateur/Les ordinateurs à encodages non-binaires|Les ordinateurs à encodages non-binaires]]
* [[Fonctionnement d'un ordinateur/Les circuits réversibles|Les circuits réversibles]]
* [[Fonctionnement d'un ordinateur/Les circuits de conversion analogique-numérique|Les circuits de conversion analogique-numérique]]
{{autocat}}
dzz3ka66n9dyjap593hzygnxbd3rl04
Fonctionnement d'un ordinateur/Les transistors et portes logiques
0
71050
768607
768312
2026-06-25T14:57:44Z
Mewtow
31375
/* La porte à transmission */
768607
wikitext
text/x-wiki
Dans le chapitre précédent, nous avons abordé les portes logiques. Dans ce chapitre, nous allons voir qu'elles sont fabriquées avec des composants électroniques que l'on appelle des '''transistors'''. Ces derniers sont reliés entre eux pour former des circuits plus ou moins compliqués. Pour donner un exemple, sachez que les derniers modèles de processeurs peuvent utiliser près d'un milliard de transistors.
==Les transistors MOS==
[[File:Transistor basic flow.svg|vignette|Un transistor est un morceau de conducteur, dont la conductivité est contrôlée par sa troisième broche/borne.]]
Les transistors possèdent trois '''broches''', des pattes métalliques sur lesquelles on connecte des fils électriques. On peut appliquer une tension électrique sur ces broches, qui peut représenter soit 0 soit 1. Sur ces trois broches, il y en a deux entre lesquelles circule un courant, et une troisième qui commande le courant. Le transistor s'utilise le plus souvent comme un interrupteur commandé par sa troisième broche. Le courant qui traverse les deux premières broches passe ou ne passe pas selon ce qu'on met sur la troisième.
Il existe plusieurs types de transistors, mais les deux principaux sont les transistors bipolaires et les transistors MOS. De nos jours, les transistors utilisés dans les ordinateurs sont tous des '''transistors MOS'''. Les raisons à cela sont multiples, mais les plus importantes sont les suivantes. Premièrement, les transistors bipolaires sont plus difficiles à fabriquer et sont donc plus chers. Deuxièmement, ils consomment bien plus de courant que les transistors MOS. Et enfin, les transistors bipolaires sont plus gros, ce qui n'aide pas à miniaturiser les puces électroniques. Tout cela fait que les transistors bipolaires sont aujourd'hui tombés en désuétude et ne sont utilisés que dans une minorité de circuits.
===Les types de transistors MOS : PMOS et NMOS===
Sur un transistor MOS, chaque broche a un nom, nom qui est indiqué sur le schéma ci-dessous.On distingue ainsi le '''drain''', la '''source''' et la '''grille''' On l'utilise le plus souvent comme un interrupteur commandé par sa grille. Appliquez la tension adéquate et la liaison entre la source et le drain se comportera comme un interrupteur fermé. Mettez la grille à une autre valeur et cette liaison se comportera comme un interrupteur ouvert.
Il existe deux types de transistors CMOS, qui diffèrent entre autres par le bit qu'il faut mettre sur la grille pour les ouvrir/fermer :
* les transistors NMOS qui s'ouvrent lorsqu'on envoie un zéro sur la grille et se ferment si la grille est à un ;
* et les PMOS qui se ferment lorsque la grille est à zéro, et s'ouvrent si la grille est à un.
[[File:Td7bfig2.png|centre|vignette|upright=2|Illustration du fonctionnement des transistors NMOS et PMOS.]]
Voici les symboles de chaque transistor.
{|
|[[File:Transistor CMOS.png|vignette|upright=0.5|Transistor CMOS]]
|[[File:IGFET N-Ch Enh Labelled simplified.svg|vignette|upright=0.5|Transistor MOS à canal N (NMOS).]]
|[[File:IGFET P-Ch Enh Labelled simplified.svg|vignette|upright=0.5|Transistor MOS à canal P (PMOS).]]
|}
===L'anatomie d'un transistor MOS===
À l'intérieur du transistor, on trouve simplement une plaque en métal reliée à la grille appelée l'armature, un bout de semi-conducteur entre la source et le drain, et un morceau d'isolant entre les deux. Pour rappel, un semi-conducteur est un matériau qui se comporte soit comme un isolant, soit comme un conducteur, selon les conditions auxquelles on le soumet. Dans un transistor, son rôle est de laisser passer le courant, ou de ne pas le transmettre, quand il faut. C'est grâce à ce semi-conducteur que le transistor peut fonctionner en interrupteur : interrupteur fermé quand le semi-conducteur conduit, ouvert quand il bloque le courant. La commande de la résistance du semi-conducteur (le fait qu'il laisse passer ou non le courant) est réalisée par la grille, comme nous allons le voir ci-dessous.
[[File:Transistor CMOS - 1.png|centre|vignette|upright=2|Transistor CMOS]]
Suivant la tension que l'on place sur la grille, celle-ci va se remplir avec des charges négatives ou positives. Cela va entrainer une modification de la répartition des charges dans le semi-conducteur, ce qui modulera la résistance du conducteur. Prenons par exemple le cas d'un transistor NMOS et étudions ce qui se passe selon la tension placée sur la grille. Si on met un zéro, la grille sera vide de charges et le semi-conducteur se comportera comme un isolant : le courant ne passera pas. En clair, le transistor sera équivalent à un interrupteur ouvert. Si on met un 1 sur la grille, celle-ci va se remplir de charges. Le semi-conducteur va réagir et se mettre à conduire le courant. En clair, le transistor se comporte comme un interrupteur fermé.
{|
|[[File:Transistor CMOS - 3.png|vignette|upright=1.5|Transistor NMOS fermé.]]
|[[File:Transistor CMOS - 4.png|vignette|upright=1.5|Transistor NMOS ouvert.]]
|}
===La tension de seuil d'un transistor===
Le fonctionnement d'un transistor est légèrement plus complexe que ce qui a été dit auparavant. Mais pour rester assez simple, disons que son fonctionnement exact dépend de trois paramètres : la tension d'alimentation, le courant entre drain et source, et un nouveau paramètre appelé la tension de seuil.
Appliquons une tension sur la grille d'un transistor NMOS. Si la tension de grille reste sous un certain seuil, le transistor se comporte comme un interrupteur fermé. Le seuil de tension est appelé, très simplement, la '''tension de seuil'''. Au-delà de la tension de seuil, le transistor se comporte comme un interrupteur ouvert, il laisse passer le courant. La valeur exacte du courant dépend de la tension entre drain et source, soit la tension d'alimentation. Elle aussi dépend de la différence entre tension de grille et de seuil, à savoir <math>U_G - U_\text{seuil}</math>.
Le paragraphe qui va suivre est optionnel, mais détaille un peu plus le fonctionnement d'un transistor MOS. Tout ce qu'il faut comprendre est que la tension de seuil est une tension minimale pour ouvrir le transistor. Le plus important à retenir est que l'on ne peut pas baisser la tension d'alimentation sous la tension de seuil, ce qui est un léger problème en termes de consommation énergétique. Ce détail reviendra plus tard dans ce cours, quand nous parlerons de la consommation d'énergie des circuits électroniques.
Dans les cas que nous allons voir dans ce cours, la tension d'alimentation est plus grande que <math>U_G - U_\text{seuil}</math>. Le courant est alors maximal, il est proportionnel à <math>U_G - U_\text{seuil}</math>. Le transistor ne fonctionne alors pas comme un amplificateur, le courant reste le même. Si la tension d'alimentation est plus petite que <math>U_G - U_\text{seuil}</math>, le transistor est en régime linéaire : le courant de sortie est proportionnel à <math>U_G - U_\text{seuil}</math>, ainsi qu'à la tension d'alimentation. Le transistor fonctionne alors comme un amplificateur de courant, dont l'intensité de l'amplification est commandée par la tension.
[[File:MOSFET enhancement-mode n-channel en.svg|centre|vignette|upright=2.5|Relations entre tensions et courant d'un MOSFET à dopage N.]]
==La technologie CMOS==
Les portes logiques que nous venons de voir sont actuellement fabriquées en utilisant des transistors. Il existe de nombreuses manières pour concevoir des circuits à base de transistors, qui portent les noms de DTL, RTL, TLL, CMOS et bien d'autres. Les techniques anciennes concevaient des portes logiques en utilisant des diodes, des transistors bipolaires et des résistances. Mais elles sont aujourd'hui tombées en désuétudes dans les circuits de haute performance. De nos jours, on n'utilise que des logiques MOS (''Metal Oxyde Silicium''), qui utilisent des transistors MOS vus plus haut dans ce chapitre, parfois couplés à des résistances. On distingue :
* La '''logique NMOS''', qui utilise des transistors NMOS associés à des résistances.
* La '''logique PMOS''', qui utilise des transistors PMOS associés à des résistances.
* La '''logique CMOS''', qui utilise des transistors PMOS et NMOS, sans résistances.
Dans cette section, nous allons montrer comment fabriquer des portes logiques en utilisant la '''technologie CMOS'''. Avec celle-ci, chaque porte logique est fabriquée à la fois avec des transistors NMOS et des transistors PMOS. On peut la voir comme un mélange entre la technologie PMOS et NMOS. Tout circuit CMOS est divisé en deux parties : une intégralement composée de transistors PMOS et une autre de transistors NMOS. Chacune relie la sortie du circuit soit à la masse, soit à la tension d'alimentation.
[[File:Principe de la conception de circuit en technologie CMOS.png|centre|vignette|upright=2|Principe de conception d'une porte logique/d'un circuit en technologie CMOS.]]
La première partie relie la tension d'alimentation à la sortie, mais uniquement quand la sortie doit être à 1. Si la sortie doit être à 1, des transistors PMOS vont se fermer et connecter tension et sortie. Dans le cas contraire, des transistors s'ouvrent et cela déconnecte la liaison entre sortie et tension d'alimentation. L'autre partie du circuit fonctionne de la même manière que la partie de PMOS, sauf qu'elle relie la sortie à la masse et qu'elle se ferme quand la sortie doit être mise à 0
[[File:Fonctionnement d'un circuit en logique CMOS.png|centre|vignette|upright=2|Fonctionnement d'un circuit en logique CMOS.]]
Dans ce qui va suivre, nous allons étudier la porte NON, la porte NAND et la porte NOR. La porte de base de la technologie CMOS est la porte NON, les portes NAND et NOR ne sont que des versions altérées de la porte NON qui ajoutent des entrées et quelques transistors. Les autres portes, comme la porte ET et la porte OU, sont construites à partir de ces portes. Nous parlerons aussi de la porte XOR, qui est un peu particulière.
===La porte NON===
Cette porte est fabriquée avec seulement deux transistors, comme indiqué ci-dessous.
[[File:Porte NON fabriquée avec des transistors CMOS. 01.jpg|centre|vignette|upright=1|Porte NON fabriquée avec des transistors CMOS.]]
Si on met un 1 en entrée de ce circuit, le transistor du haut va fonctionner comme un interrupteur ouvert, et celui du bas comme un interrupteur fermé : la sortie est reliée au zéro volt, et vaut donc 0. Inversement, si on met un 0 en entrée de ce petit montage électronique, le transistor du bas va fonctionner comme un interrupteur ouvert, et celui du haut comme un interrupteur fermé : la sortie est reliée à la tension d'alimentation, et vaut donc 1.
[[File:Porte NON fabriquée avec des transistors CMOS - fonctionnement.png|centre|vignette|upright=2|Porte NON fabriquée avec des transistors CMOS - fonctionnement.]]
===Les portes NAND et NOR===
Passons maintenant aux portes logiques à plusieurs entrées. Pour celles-ci, on va devoir utiliser plus de transistors que pour la porte NON, ce qui demande de les organiser un minium. Une porte logique à deux entrées demande d'utiliser au moins deux transistors par entrée : un transistor PMOS et un NMOS par entrée. Rappelons qu'un transistor est associé à une entrée : l'entrée est directement envoyée sur la grille du transistor et commande son ouverture/fermeture. Pour les portes logiques à 3, 4, 5 entrées, la logiques est la même : au minimum deux transistors par entrée, un PMOS et un NMOS.
Nous allons d'abord voir le cas d'une porte NOR/NAND en CMOS. Avec elles, les transistors sont organisées de deux manières, appelées '''transistors en série''' (l'un après l'autre, sur le même fil) et '''transistors en parallèle''' (sur des fils différents). Le tout est illustré ci-dessous. Avec des transistors en série, plusieurs transistors NMOS ou deux PMOS se suivent sur le même fil, mais on ne peut pas mélanger NMOS et PMOS sur le même fil.
[[File:Transistors CMOS en série et en parallèle.png|centre|vignette|upright=2|Transistors CMOS en série et en parallèle]]
====Les portes NAND/NOR à deux entrées====
Voyons d'abord le cas des portes NAND/NOR à deux entrées. Elles utilisent deux transistors NMOS et deux PMOS.
Avec des transistors en série, deux transistors NMOS ou deux PMOS se suivent sur le même fil, mais on ne peut pas mélanger NMOS et PMOS sur le même fil. Avec des transistors en parallèle, c'est l'exact inverse. L'idée est de relier la tension d'alimentation à la sortie à travers deux PMOS transistors distincts, chacun sur son propre fil, sa propre connexion indépendante des autres. Pour la masse (0 volt), il faut utiliser deux transistors NMOS pour la relier à la sortie, avec là encore chaque transistor NMOS ayant sa propre connexion indépendante des autres. En clair, chaque entrée commande un transistor qui peut à lui seul fermer le circuit.
On rappelle deux choses : chaque transistor est associée à une entrée sur sa grille, un transistor se ferme si l'entrée vaut 0 pour des transistors PMOS et 1 pour des NMOS. Avec ces deux détails, on peut expliquer comment fonctionnent des transistors en série et en parallèle. Pour résumer, les transistors en série ferment la connexion quand toutes les entrées sont à 1 (NMOS) ou 0 (PMOS). Avec les transistors en parallèle, il faut qu'une seule entrée soit à 1 (NMOS) ou 0 (PMOS) pour que la connexion se fasse.
Une porte NOR met sa sortie à 1 si toutes les entrées sont à 0, à 0 si une seule entrée vaut 1. Pour reformuler, il faut connecter la sortie à la tension d'alimentation si toutes les entrées sont à 0, ce qui demande d'utiliser des transistors PMOS en série. Pour gérer le cas d'une seule entrée à 1, il faut utiliser deux transistors en parallèle entre la masse et la sortie. Le circuit obtenu est donc celui obtenu dans le premier schéma. Le même raisonnement pour une porte NAND donne le second schéma.
{|
|[[File:Porte NOR fabriquée avec des transistors. 02.png|centre|vignette|upright=1|Porte NOR fabriquée avec des transistors.]]
|[[File:Porte NAND fabriquée avec des transistors. 04.png|centre|vignette|upright=1|Porte NAND fabriquée avec des transistors.]]
|}
Leur fonctionnement s'explique assez bien si on regarde ce qu'il se passe en fonction des entrées. Suivant la valeur de chaque entrée, les transistors vont se fermer ou s'ouvrir, ce qui va connecter la sortie soit à la tension d'alimentation, soit à la masse.
Voici ce que cela donne pour une porte NAND :
[[File:Porte NAND fabriquée avec des transistors - Fonctionnement.png|centre|vignette|upright=2|Porte NAND fabriquée avec des transistors.]]
Voici ce que cela donne pour une porte NOR :
[[File:Porte NOR fabriquée avec des transistors - Fonctionnement.png|centre|vignette|upright=2|Porte NOR fabriquée avec des transistors.]]
====Les portes NAND/NOR/ET/OU à plusieurs entrées====
Les portes NOR/NAND à plusieurs entrées sont construites à partir de portes NAND/NOR à deux entrées auxquelles on rajoute des transistors. Il y a autant de transistors en série que d'entrée, pareil pour les transistors en parallèle. Leur fonctionnement est similaire à leurs cousines à deux entrées. Les portes ET et OU à plusieurs entrées sont construites à partie de NAND/NOR suivies d'une porte NON.
{|
|[[File:NAND plusieurs entrées.png|vignette|NAND plusieurs entrées]]
|[[File:NOR plusieurs entrées.png|vignette|NOR plusieurs entrées]]
|}
En théorie, on pourrait créer des portes avec un nombre arbitraire d'entrées avec cette méthode. Cependant, au-delà d'un certain nombre de transistors en série/parallèle, les performances s'effondrent rapidement. Le circuit devient alors trop lent, sans compter que des problèmes purement électriques surviennent. En pratique, difficile de dépasser la dizaine d'entrées. Dans ce cas, les portes sont construites en assemblant plusieurs portes NAND/NOR ensemble. Et faire ainsi marche nettement mieux pour fabriquer des portes ET/OU que pour des portes NAND/NOR.
====Les portes ET/OU sont fabriquées à partir de NAND/NOR en CMOS====
En logique CMOS, les portes logiques ET et OU sont construites en prenant une porte NAND/NOR et en mettant une porte NON sur sa sortie. Il est théoriquement possible d'utiliser uniquement des transistors en série et en parallèle, mais cette solution utilise plus de transistors.
{|
|[[File:CMOS AND Layout.svg|vignette|Porte ET en CMOS]]
|[[File:CMOS OR.svg|vignette|Porte OU en CMOS]]
|}
Pour ce qui est des portes ET/OU avec beaucoup d'entrées, il est fréquent qu'elles soit construites en combinant plusieurs portes ET/OU moins complexes. Par exemple, une porte ET à 32 entrées sera construite à partir de portes à seulement 4 ou 5 entrées. Il existe cependant une alternative qui se marie nettement mieux avec la logique CMOS. Rappelons qu'en logique CMOS, les portes NAND et NOR sont les portes à plusieurs entrées les plus simples à fabriquer. L'idée est alors de combiner des portes NAND/NOR pour créer une porte ET/OU.
Voici la comparaison entre les deux solutions pour une porte ET :
{|
|[[File:12 input AND gate via cascade of AND gates.svg|vignette|ET plusieurs entrées]]
|[[File:12-input AND gate from NAND and NOR.svg|vignette|ET plusieurs entrées]]
|}
Voici la comparaison entre les deux solutions pour une porte OU :
{|
|[[File:12-input OR gate via cascade of OR gates.svg|vignette|OU plusieurs entrées]]
|[[File:12-input OR gate via NOR and NAND gates.svg|vignette|OU plusieurs entrées]]
|}
D'autres portes mélangent transistors en série et en parallèle d'une manière différente. Les portes ET-OU-NON et OU-ET-NON en sont un bon exemple.
===Une méthode générale===
Il existe une méthode générale pour créer des portes logiques à deux entrées. Avec elle, il faut repartir du montage avec deux transistors NMOS/PMOS en série. En théorie, il permet de relier la sortie à la tension d'alimentation/zéro volt si toutes les entrées sont à 0 (PMOS) ou 1 (NMOS). L'idée est de regarder ce qui se passe si on fait précéder l'entrée d'un transistor par une porte NON. Pour deux transistors, cela fait 4 possibilités, 8 au total si on fait la différence entre PMOS et NMOS. Voici les valeurs d'entrées qui ferment le montage à transistor en série, suivant l’endroit où on place la porte NON.
[[File:Transistors CMOS en série.png|centre|vignette|upright=2|Transistors CMOS en série]]
Mine de rien, avec ces 8 montages de base, on peut créer n'importe quelle porte logique à deux entrées. Il faut juste se souvenir que d'après les règles du CMOS, les deux transistors PMOS se placent entre la tension d'alimentation et la sortie, et servent à mettre la sortie à 1. Pour les deux transistors NMOS, ils sont reliés à la masse et mettent la sortie à 0. Pour mieux comprendre, prenons l'exemple d'une porte XOR.
Appliquons la méthode que je viens d'expliquer avec une porte XOR. Le résultat est sous-optimal, mieux vaut fabriquer une porte XOR en combinant d'autres portes logiques, mais c'est pour l'exemple. L'idée est très simple : on prend la table de vérité de la porte logique, et on associe deux transistors en série pour chaque ligne. Regardons d'abord la table de vérité ligne par ligne :
{|class="wikitable"
|-
!Entrée 1!!Entrée 2!!Sortie
|-
||0||0||0
|-
||0||1||1
|-
||1||0||1
|-
||1||1||0
|}
La première ligne a ses deux entrées à 0 et sort un 0. La sortie est à 0, ce qui signifie qu'il faut regarder sur la ligne des transistors NMOS, qui connectent la sortie à la masse. Le montage qui se ferme quand les deux entrées sont à 0 est celui tout en bas à droite du tableau précédent, à savoir deux transistors NMOS avec deux portes NON.
Les deux lignes du milieu ont une entrée à 0 et une à 1, et leur sortie à 1. La sortie à 1 signifie qu'il faut regarder sur la ligne des transistors PMOS, qui connectent la tension d'alimentation à la sortie. Les deux montages avec deux entrées différentes sont les deux situés au milieu, avec deux transistors PMOS et une porte logique.
La dernière ligne a ses deux entrées à 1 et sort un 0. La sortie est à 0, ce qui signifie qu'il faut regarder sur la ligne des transistors NMOS, qui connectent la sortie à la masse. Le montage qui se ferme quand les deux entrées sont à 1 est celui tout en bas à gauche du tableau précédent, à savoir deux transistors NMOS seuls.
En combinant ces quatre montages, on trouve le circuit suivant. Notons qu'il n'y a que deux portes NON marquées en vert et bleu : on a juste besoin d'inverser la première entrée et la seconde, pas besoin de portes en plus. Les portes NOn sont en quelque sorte partagées entre les transistors PMOS et NMOS.
[[File:Cmos xor.svg|centre|vignette|upright=1|class=transparent|Porte XOR en logique CMOS.]]
Si les deux entrées sont à 1, alors les deux transistors en bas à gauche vont se fermer et connecter la sortie au 0 volt, les trois autres groupes ayant au moins un transistor ouvert. Si les deux entrées sont à 0, alors les deux transistors en bas à droite vont se fermer et connecter la sortie au 0 volt, les autres quadrants ayant au moins un transistor ouvert. Et pareil quand les deux bits sont différents : un des deux quadrants aura ses deux transistors fermés, alors que les autres auront au moins un transistor ouvert, ce qui connecte la sortie à la tension d'alimentation.
On peut construire la porte NXOR sur la même logique. Et toutes les portes logiques peuvent se construire avec cette méthode. Le nombre de transistors est alors le même : on utilise 12 transistors au total : 4 paires de transistors en série, 4 transistors en plus pour les portes NON. Que ce soit pour la porte XOR ou NXOR, on économise beaucoup de transistors comparés à la solution naïve, qui consiste à utiliser plusieurs portes NON/ET/OU. Si on ne peut pas faire mieux dans le cas de la porte XOR/NXOR, sachez cependant que les autres portes construites avec cette méthode utilisent plus de transistors que nécessaire. De nombreuses simplifications sont possibles, comme on le verra plus bas.
Dans les faits, la méthode n'est pas utilisée pour les portes XOR. À la place, les portes XOR sont construites à base d'autres portes logiques plus simples, comme des portes NAND/NOR/ET/OU. Le résultat est que l'on a un circuit à 10 transistors, contre 12 avec la méthode précédente.
[[File:CMOS10TrXOR.svg|centre|vignette|Porte XOR en CMOS en 10 transistors.]]
===Les circuits plus complexes (''full adder'', ...)===
Il est possible de fusionner plusieurs portes ET-OU-NON en un seul circuit à transistors CMOS, ce qui permet des simplifications assez impressionnantes. Pour donner un exemple, le schéma suivant compare l'implémentation d'un circuit qui fait un ET entre les deux premières entrées, puis un NOR entre le résultat du ET et la troisième entrée. L'implémentation à droite du schéma avec une porte ET et une porte NOR prend 10 transistors. L'implémentation la plus simple, à gauche du schéma, prend seulement 6 transistors.
[[File:AOI21 complex vs standard gates.svg|centre|vignette|upright=1.5|Porte ET-OU-NON à trois entrées (de type 2-1) à gauche, contre la combinaison de plusieurs portes à droite.]]
Une conséquence est que des circuits assez complexes gagnent à être fabriqués directement avec des transistors. Prenons l'exemple de l'additionneur complet. Une implémentation naïve, avec 5 portes logiques, utilise beaucoup de transistors. Deux portes XOR, deux portes OU et une porte ET, cela dépasse la trentaine de transistors. Faisons le compte : 10 transistors par porte XOR, 6 pour les trois autres portes, cela fait 38 transistors. Les additionneurs des processeurs modernes sont optimisés directement au niveau des transistors, pour leur permettre d'économiser des transistors. Par exemple, l'implémentation suivante en utilise seulement 24 !
[[File:Inverting full adder CMOS 24T.svg|centre|vignette|upright=1.5|Additionneur complet fabriqué avec 24 transistors.]]
Et c'est sans compter que l'additionneur complet naïf n'est pas forcément le top du top en termes de performances. Là encore, une implémentation avec des transistors peut être optimisée pour être plus rapide, notamment au niveau du calcul de la retenue, ou au contraire d'économiser des transistors. Tout dépend de l'objectif visé, certains circuit optimisant à fond pour la vitesse, d'autres pour le nombre de transistors, d'autres font un compromis entre les deux. Les circuits de ce genre sont très nombreux, trop pour qu'on puisse les citer.
==La ''pass transistor logic''==
La '''''pass transistor logic''''' est une forme particulière de technologie CMOS, une version non-conventionnelle. Avec le CMOS normal, la porte de base est la porte NON. En modifiant celle-ci, on arrive à fabriquer des portes NAND, NOR, puis les autres portes logiques. Les transistors sont conçus de manière à connecter la sortie, soit la tension d'alimentation, soit la masse. Avec la ''pass transistor logic'', le montage de base est un circuit interrupteur, qui connecte l'entrée directement sur la sortie. Le circuit interrupteur n'est autre que les portes à transmission vues il y a quelques chapitres.
La ''pass transistor logic'' a été utilisée dans des processeurs commerciaux, comme dans l'ARM1, le premier processeur ARM. Sur l'ARM1, les concepteurs ont décidé d'implémenter certains circuits avec des multiplexeurs. La raison n'est pas une question de performance ou d'économie de transistors, juste que c'était plus pratique à fabriquer, sachant que le processeur était le premier CPU ARM de l'entreprise.
S'il est intéressant de voir la ''pass transistor logic'', c'est qu'elle est souvent utilisée pour simplifier certains circuits CMOS normaux. Par exemple, il est possible d'implémenter toutes les portes logiques en CMOS normal, sauf la porte XOR qui est implémentée avec la ''pass transistor logic''. Cela permet une petite économie de circuits, vu que la porte XOR est bien plus simple en ''pass transistor logic''. La ''pass transistor logic'' est aussi utilisée pour simplifier les multiplexeurs et les démultiplexeurs, et certains additionneurs. Aussi, ne soyez pas étonné si nous revenons sur certains circuits vus dans les chapitres précédents, dans cette section.
===La porte à transmission===
Le circuit de base est une porte logique que nous n'avons pas encore vu pour le moment, appelée la '''porte à transmission'''. Elle agit comme un interrupteur commandé par une entrée de commande. Pour rappel, un interrupteur fermé laisse passer le courant, alors qu'un interrupteur fermé ne le laisse pas passer. La porte à transmission fait pareil : soit elle connecte l'entrée et la sortie, soit elle les déconnecte. Pour choisir entre les deux, une porte à transmission possède une entrée de commande sur laquelle on envoie un bit de commande. La porte est fermée si le bit de commande est à 1, ouvert s'il est à 1.
[[File:Tristate buffer corrected.svg|centre|vignette|upright=2|Porte à transmission.]]
Il est possible de la voir comme une porte OUI améliorée dont la table de vérité est celle-ci :
{|class="wikitable"
|-
!Commande!!Entrée!!Sortie
|-
||0||0||Déconnexion
|-
||0||1||Déconnexion
|-
||1||0||0
|-
||1||1||1
|}
Intuitivement, on se dit qu'une porte à transmission est faite avec un seul, vu qu'un transistor fonctionne déjà comme un interrupteur commandable. Mais une porte à transmission est construite avec deux transistors. La raison la plus intuitive est que la logique CMOS associe toujours un transistor PMOS à un transistor NMOS. Mais une autre raison, plus importante, est que les transistors NMOS et PMOS ne sont pas des interrupteurs parfaits. Les NMOS laissent passer les 0, mais laissent mal passer les 1 : la tension en sortie, pour un 1, est atténuée. Et c'est l'inverse pour les PMOS, qui laissent bien passer les 1 mais fournissent une tension de sortie peut adéquate pour les 0. Donc, deux transistors permettent d'obtenir une tension de sortie convenable.
Le montage de base est illustré ci-dessous. Les deux entrées A et /A sont l'inverse l'une de l'autre, ce qui fait qu'il faut en théorie rajouter une porte NON CMOS normale, pour obtenir le circuit complet. Mais dans les faits, on arrive souvent à s'en passer. Ce qui fait que la porte à transmission est définie comme étant le circuit à deux transistors précédents.
[[File:CMOS transmission gate.PNG|centre|vignette|upright=1|CMOS Transmission gate]]
Une porte logique en logique CMOS connecte directement sa sortie sur la tension d'alimentation ou la masse. Mais dans une porte logique en ''pass transistor logic'', il n'y a ni tension d'alimentation, ni masse (O Volts). La sortie est connectée sur l'entrée, rien de plus. Et cela explique plusieurs différences entre CMOS et ''pass transistor logic''.
La première différence est que certaines portes logiques sont impossibles avec la ''pass transistor logic'' pure. Les portes logiques CMOS peuvent générer un 1 ou un 0 distinct de ce qu'il y a sur leur entrée. Par exemple, elles peuvent sortir un 1 même si toutes leurs entrées sont à 0, car elles reliées à la tension d'alimentation. Les portes à transmission ne peuvent pas le faire. Elles se contentent de recopier une entrée sur leur sortie : impossible d'avoir un 1 en sortie avec uniquement des zéros en entrée. La conséquence est qu'il n'est pas possible de créer de porte NON, ni de porte NOR/NAND directement.
Une autre différence est que l’électricité est fournie par l'entrée, ce qui fait qu'elle se dissipe un peu lors du passage dans une porte à transmission. Le résultat est que si on enchaine les portes à transmission, la tension de sortie a tendance à diminuer, et ce d'autant plus vite qu'on a enchainé de portes à transmission. Il faut souvent rajouter des portes OUI pour restaurer les tensions adéquates, à divers endroits du circuit. La ''pass transistor logic'' mélange donc porte OUI/NON CMOS normales avec des portes à transmission. Afin de faire des économies de circuit, on utilise parfois une seule porte NON CMOS comme amplificateur, ce qui fait que de nombreux signaux sont inversés dans les circuits, sans que cela ne change grand chose si le circuit est bien conçu.
Par contre, ce défaut entraine aussi des avantages. Notamment, la consommation d'énergie est fortement diminuée. Seules les portes amplificatrices, les portes NON CMOS, sont alimentées en tension/courant. Le reste des circuits n'est pas alimenté, car il n'y a pas de connexion à la tension d'alimentation et la masse. De même, la ''pass transistor logic'' utilise généralement moins de transistors pour implémenter une porte logique, et un circuit électronique en général. L'exemple avec la porte XOR est assez parlant : on passe de 12 à 6 transistors par porte XOR. Des circuits riches en portes XOR, comme les circuits additionneurs, gagnent beaucoup à utiliser des portes à transmission.
===Les multiplexeurs en ''pass transistor logic''===
Les portes à transmission sont très utilisées dans les multiplexeurs et les démultiplexeurs. Prenons l'exemple d'un multiplexeur 2 vers 1. L'idée est de relier chaque entrée à la sortie par l'intermédiaire d'une porte à transmission. Quand l'une sera ouverte, l'autre sera fermée. Le résultat n'utilise que deux portes à transmission et une porte NON. Voici le circuit qui en découle :
[[File:Multiplexeur fabriqué avec des portes à transmission et-ou des tampons trois-états.png|centre|vignette|upright=1.5|Multiplexeur fabriqué avec des portes à transmission]]
En utilisant les portes à transmission CMOS vues plus haut, on obtient le circuit suivant :
[[File:Multiplexeur fabriqué avec des portes à transmission.png|centre|vignette|upright=1.5|Multiplexeur fabriqué avec des portes à transmission CMOS.]]
La même méthode fonctionne pour les multiplexeurs avec plus de deux entrées. Pour rappel, un multiplexeur est composé d'un décodeur qui commande une couche de portes ET, les sorties des portes ET sont combinées avec une porte OU.
[[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]]
Il est possible de remplacer les portes ET par des portes à transmission. L'idée est de ne connecter sur la sortie que l'entrée qui a été sélectionnée et de déconnecter les autres. En faisant ainsi, on peut se passer de la porte OU, qui est remplacée par un simple fil. Il n'y a qu'une seule entrée qui est connectée à la sortie à chaque instant, pas besoin d'utiliser de porte OU. Le résultat est le circuit suivant :
[[File:Mux Funktionsprinzip.svg|centre|vignette|upright=1.5|Multiplexeur basé sur des interrupteurs.]]
Les multiplexeurs en ''pass transistor logic'' sont plus simple que leurs cousins en CMOS normal. Beaucoup de circuits utilisent des multiplexeurs et nous en avons déjà vu pas mal : les circuits de décalage, les bascules, les additionneurs, quelques autres. Comment se comportent-ils si leurs MUX sont implémentés avec la ''pass transistor logic'' ? La réponse est que l'usage de la ''pass transistor logic'' ne change pas la donne pour les circuits de décalage, alors qu'elle change drastiquement la donne pour les bascules et les additionneurs. Voyons cela dans le détail.
===Les bascules D avec des portes à transmission===
[[File:Multiplexer-based latch (positive).svg|class=transparent|right|Bascule D créée avec un multiplexeur.]]
Une bascule D est, pour rappel, un circuit qui mémorise un bit. Elle peut être implémenté avec un multiplexeur 2 vers 1, en bouclant la sortie du multiplexeur sur une entrée. Pour un multiplexeur fabriqué avec des portes CMOS, boucler sa sortie sur son entrée ne pose pas de problème. Mais avec des portes à transmission, le circuit ne fonctionne pas. Le problème est qu'une porte à transmission est électriquement équivalente à un simple interrupteur, ce qui réduit le circuit à une boucle entre un interrupteur et un fil. Le courant qui circule dans le fil et l'interrupteur se dissipe rapidement du fait de la résistance du fil et disparait en quelques micro- ou millisecondes.
La solution est de rajouter une porte OUI (celle qui recopie son entrée sur sa sortie) dans la boucle pour régénérer le signal électrique. Et la manière la plus simple de fabriquer une porte OUI utilise deux portes NON qui se suivent, ce qui donne le circuit ci-dessous. Cela garantit que la boucle est alimentée en courant/tension quand elle est fermée. Son contenu ne s'efface pas avec le temps, mais est automatiquement régénéré par les portes NON. L'ensemble sera stable tant que la boucle est fermée.
[[File:Implémentation conceptuelle d'une bascule D.png|centre|vignette|upright=2|Implémentation d'une bascule D avec des portes à transmission.]]
Le circuit précédent utilise seulement 10 transistors, alors qu'un multiplexeur en CMOS normal en utilise 14. Un autre avantage est que ce circuit permet d'avoir les deux sorties Q : la sortie Q inversée est prise en sortie de la première porte NON. Une variante du circuit précédent est utilisée dans les mémoires dites SRAM, qui sont utilisées pour les registres du processeur ou ses mémoires caches. Mais nous verrons cela plus en détail dans le chapitre sur les cellules mémoires.
Certaines bascules D ont une entrée R, qui met à zéro le bit mémorisé dans la bascule quand l'entrée R est à 1. Pour cela, elles ajoutent un circuit de mise à zéro, que nous avons déjà vu dans le chapitre sur les opérations bit à bit. Ce circuit de mise à zéro est placé après la seconde porte NON, et sa sortie est bouclée sur l'entrée du circuit. Le circuit obtenu est le suivant :
[[File:Bascule D avec entrée Reset.png|centre|vignette|upright=2|Bascule D avec entrée Reset]]
Le circuit peut se simplement fortement en fusionnant les trois portes situées entre les deux sorties Q, à savoir la porte ET et les deux portes NON qui la précédent. La loi de De Morgan nous dit que l'ensemble est équivalent à une porte NOR, ce qui donne le circuit suivant :
[[File:Bascule D avec entrée Reset, simplifiée.png|centre|vignette|upright=2|Bascule D avec entrée Reset, simplifiée]]
===L'additionneur ''Manchester Carry Chain''===
Les portes à transmission étaient autrefois utilisées pour simplifier les additionneurs, et plus précisément les additionneurs à propagation de retenue. Pour rappel, un additionneur à propagation de retenue additionne deux opérandes, bit par bit. Elle additionne les deux bits de poids faible, ce qui donne un bit de résultat et un bit de retenue. Le bit de retenue est alors envoyé à la colonne suivante, où deux bits sont additionnés avec la retenue, et ainsi de suite. De tels circuits sont composées en enchainant des additionneurs complets, des circuits qui additionnent trois bits : deux bits d'opérandes et une retenue.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Un défaut des additionneurs à propagation de retenue est leur lenteur. Le résultat n'est connu qu'une fois que les retenues ont été propagées d'une colonne à l'autre. Et cette propagation est assez lente. Les additionneurs modernes utilisent des techniques très complexes pour résoudre ce problème, comme nous l'avons vu il y a quelques chapitres. Mais ces solutions utilisent beaucoup plus de transistors. De nombreux processeurs comme le 8086 d'Intel, ou d'autres processeurs 8-16 bits de cette époque, ne pouvaient pas se le permettre. À la place, ils utilisaient une optimisation à base de portes à transmission.
L'optimisation en question s'appelle la ''Manchester Carry Chain''. Elle utilise des additionneurs complets construits avec un multiplexeur, comme illustré ci-dessous,. Nous avions vu ce circuit dans le chapitre sur les additionneurs. Avec la ''Manchester Carry Chain'', le multiplexeur est implémenté avec des portes à transmission, comme illustré ci-dessous. L'avantage est que la propagation de la retenue est beaucoup plus rapide. Pas besoin de traverser des portes logiques, la propagation de la retenue ne rencontre pas d'obstacle, si ce n'est la résistance des fils, elle ne subit pas de délai lié au temps de propagation des portes logiques.
{|
|[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=2|Additionneur complet basé sur un MUX]]
|[[File:Manchester carry chain.png|centre|vignette|upright=2|Manchester carry chain]]
|}
Cependant, l'usage de portes à transmission a quelques défauts. Le principal est que, vu que la retenue d'entrée est envoyée sur la sortie à travers des interrupteurs, la tension sur la retenue de sortie est plus faible que la tension de la retenue d'entrée. Ce qui pose des problèmes quand on doit enchainer plusieurs additionneurs de ce type, mais laissons cela pour plus tard. Il existe une version de cet additionneur en logique dynamique, où les portes à transmission sont utilisées comme des condensateurs, mais nous n'en parlerons pas ici.
===Les portes logiques construites avec des multiplexeurs 2 vers 1===
La ''pass transistor logic'' est rarement utilisée, à une exception de taille : la porte XOR. Pour rappel, une porte XOR est une sorte d'inverseur commandable, à savoir un circuit prend un bit d'entrée A, et l'inverse ou non suivant la valeur d'un bit de commande B. Et cela nous dit comment implémenter une porte XOR avec un multiplexeur. Un multiplexeur choisit sa sortie parmi deux entrées : A et <math>\overline{A}</math>, le second bit B est envoyé sur l'entrée de commande ! Le circuit obtenu, est celui-ci :
[[File:XOR implémenté avec un MUX.png|centre|vignette|upright=2.5|XOR implémenté avec un MUX.]]
Il est possible de simplifier le circuit en rusant un peu, ce qui donne le circuit ci-dessous. Comme vous pouvez les voir, il mélange porte à transmission et portes NON CMOS normales.
[[File:CmosXORGate.svg|centre|vignette|upright=1|XOR en ''pass transistor logic'']]
Dans les deux cas, l'économie en transistors est drastique comparé au CMOS normal. Plus haut, nous avons illustré plusieurs versions possibles d'une porte XOR en CMOS normal, toutes de 12 transistors. Avec ''pass transistor logic'', une porte XOR utilise 4 à 8 transistors. Le gain est clairement significatif, suffisamment pour utiliser la ''pass transistor logic'' pour la porte XOR, quitte à utiliser des portes CMOS normales pour le reste. Quelques processeurs faisaient cela dans le temps, comme le mythique processeur Z80.
La ''pass transistor logic'' implémente les autres portes logiques avec un multiplexeur 2 vers 1 couplé à quelques portes NON ! Et intuitivement, vous vous dites que les deux entrées de la porte logique correspondent aux deux entrées de donnée du multiplexeur. Sauf qu'en réalité, un bit d'entrée est envoyé sur l'entrée de commande, et l'autre bit sur une entrée de donnée du multiplexeur. Suivant ce qu'on met sur la seconde entrée du multiplexeur, on obtient une porte ET, OU, XOR, etc. Il y a quatre choix possibles : soit on envoie un 0, soit un 1, soit l'inverse du bit d'entrée, soit envoyer deux fois le bit d'entrée.
: Plus haut, nous avions dit que les portes à transmission ne permettaient pas d'implémenter certaines portes logiques, car elles recopient leur entrée sur leur sortie. Impossible d'avoir un 1 en sortie si les entrées valent 0. Mais remarquez que les circuits précédents utilisent les portes NON. Ce sont ces portes NON qui fournissent l'électricité en sortie nécessaire pour avoir un 1 en sortie alors que les entrées sont à 0.
[[File:Portes logiques faites à partir de multiplexeurs.png|centre|vignette|upright=2|Portes logiques faites à partir de multiplexeurs]]
==Les technologies PMOS et NMOS==
Dans ce qui va suivre, nous allons voir la technologie NMOS et POMS. Pour simplifier, la technologie NMOS est équivalente aux circuits CMOS, sauf que les transistors PMOS sont remplacés par une résistance. Pareil avec la technologie PMOS, sauf que c'est les transistors NMOS qui sont remplacés par une résistance. Les deux technologies étaient utilisées avant l'invention de la technologie CMOS, quand on ne savait pas comment faire pour avoir à la fois des transistors PMOS et NMOS sur la même puce électronique, mais sont aujourd'hui révolues. Nous en parlons ici, car nous évoquerons quelques circuits en PMOS/NMOS dans le chapitre sur les cellules mémoire, mais vous pouvez considérer que cette section est facultative.
===Le fonctionnement des logiques NMOS et PMOS===
Avec la technologie NMOS, les portes logiques sont fabriqués avec des transistors NMOS intercalés avec une résistance.
[[File:Circuit en logique NMOS.png|centre|vignette|upright=2|Circuit en logique NMOS.]]
Leur fonctionnement est assez facile à expliquer. Quand la sortie doit être à 1, tous les transistors sont ouverts. La sortie est connectée à la tension d'alimentation et déconnectée de la masse, ce qui fait qu'elle est mise à 1. La résistance est là pour éviter que le courant qui arrive dans la sortie soit trop fort. Quand au moins un transistor NMOS qui se ferme, il connecte l'alimentation à la masse, les choses changent. Les lois compliquées de l'électricité nous disent alors que la sortie est connectée à la masse, elle est donc mise à 0.
[[File:Fonctionnement d'un circuit en technologie NMOS.png|centre|vignette|upright=2|Fonctionnement d'un circuit en technologie NMOS.]]
Les circuits PMOS sont construits d'une manière assez similaire aux circuits CMOS, si ce n'est que les transistors NMOS sont remplacés par une résistance qui relie ici la masse à la sortie. Rien d'étonnant à cela, les deux types de transistors, PMOS et NMOS, ayant un fonctionnement inverse.
===Les portes logiques en NMOS et PMOS===
Que ce soit en logique PMOS ou NMOS, les portes de base sont les portes NON, NAND et NOR. Les autres portes sont fabriquées en combinant des portes de base. Voici les circuits obtenus en NMOS et PMOS:
{|class="wikitable flexible"
|-
! colspan="5 | NMOS
|-
| class="transparent" | [[File:NMOS NOT.svg|class=transparent|100px|Porte NON NMOS.]]
| class="transparent" | [[File:NMOS NAND.svg|class=transparent|100px|NMOS-NAND]]
| class="transparent" | [[File:NMOS NOR.png|100px|NMOS-NOR]]
| class="transparent" | [[File:NMOS AND gate.png|100px|NMOS AND]]
| class="transparent" | [[File:NMOS OR gate.png|100px|NMOS OR]]
|-
! colspan="5 | PMOS
|-
| class="transparent" | [[File:PMOS NOT.png|100px|PMOS NOT]]
| class="transparent" | [[File:PMOS NAND corr.png|100px|PMOS NAND]]
| class="transparent" | [[File:PMOS NOR corr.png|100px|PMOS NOR]]
| class="transparent" |
| class="transparent" | [[File:PMOS OR gate.png|100px|PMOS OR]]
|}
====Les portes logiques de base en NMOS====
Le circuit d'une porte NON en technologie NMOS est illustré ci-dessous. Le principe de ce circuit est similaire au CMOS, avec quelques petites différences. Si on envoie un 0 sur la grille du transistor, il s'ouvre et connecte la sortie à la tension d'alimentation à travers la résistance. À l'inverse, quand on met un 1 sur la grille, le transistor se ferme et la sortie est reliée à la masse, donc mise à 0. Le résultat est bien un circuit inverseur.
{|class="wikitable flexible"
|[[File:NMOS NOT.svg|class=transparent|Porte NON NMOS.]]
|[[File:Not.PNG|class=transparent|Porte NON NMOS : fonctionnement.]]
|}
La porte NOR est similaire à la porte NON, si ce n'est qu'il y a maintenant deux transistors en parallèle. Si l'une des grilles est mise à 1, son transistor se fermera et la sortie sera mise à 0. Par contre, quand les deux entrées sont à 0, les transistors sont tous les deux ouverts, et la sortie est mise à 1. Le comportement obtenu est bien celui d'une porte NOR.
{|class="wikitable flexible"
|[[File:NMOS NOR.png|NMOS-NOR-gate]]
|[[File:Funktionsprinzip eines NOR-Gatters.png|class=transparent|Fonctionnement d'une porte NOR NMOS.]]
|}
La porte NAND fonctionne sur un principe similaire au précédent, si ce n'est qu'il faut que les deux grilles soient à zéro pour obtenir une sortie à 1. Pour mettre la sortie à 0 quand seulement les deux transistors sont ouverts, il suffit de les mettre en série, comme dans le schéma ci-dessous. Le circuit obtenu est bien une porte NAND.
{|class="wikitable flexible"
|[[File:NMOS NAND.svg|class=transparent|centre|NMOS-NAND-gate]]
|[[File:Funktionsprinzip eines NAND-Gatters.png|class=transparent|centre|Funktionsprinzip eines NAND-Gatters]]
|}
===Les avantages et inconvénients des technologies CMOS, PMOS et NMOS===
La technologie PMOS et NMOS ne sont pas totalement équivalentes, niveau performances. Ces technologies se distinguent sur plusieurs points : la vitesse des transistors et leur consommation énergétique.
La vitesse des circuits NMOS/PMOS/CMOS dépend des transistors eux-mêmes. Les transistors PMOS sont plus lents que les transistors NMOS, ce qui fait que les circuits NMOS sont plus rapides que les circuits PMOS. Les circuits CMOS ont une vitesse intermédiaire, car ils contiennent à la fois des transistors NMOS et PMOS.
Pour la consommation électrique, les résistances sont plus goumandes que les transistors. En PMOS et NMOS, la résistance est traversée par du courant en permanence, peu importe l'état des transistors. Et résistance traversée par du courant signifie consommation d'énergie, dissipée sous forme de chaleur par la résistance. Il s'agit d'une perte sèche d'énergie, une consommation d'énergie inutile. En CMOS, l'absence de résistance fait que la consommation d'énergie est liée aux transistors, et celle-ci est beaucoup plus faible que pour une résistance.
Les transistors PMOS sont plus simples à fabriquer que les NMOS, ils sont plus simples à sortir d'usine. Les premiers processeurs étaient fabriqués en logique PMOS, plus simple à fabriquer. Puis, une fois la fabrication des circuits NMOS maitrisée, les processeurs sont tous passés en logique NMOS du fait de sa rapidité. La logique CMOS a mis du temps à remplacer les logiques PMOS et NMOS, car il a fallu maitriser les techniques pour mettre à la fois des transistors NMOS et PMOS sur la même puce. Les premières puces électroniques étaient fabriquées en PMOS ou en NMOS, parce qu'on n’avait pas le choix. Mais une fois la technologie CMOS maitrisée, elle s'est imposée en raison de deux gros avantages : une meilleure fiabilité (une meilleure tolérance au bruit électrique), et une consommation électrique plus faible.
==La logique dynamique MOS==
La '''logique dynamique''' permet de créer des portes logiques ou des bascules d'une manière assez intéressante. Et aussi étonnant que cela puisse paraître, le signal d’horloge est alors utilisé pour fabriquer des circuits combinatoires !
===Un transistor MOS peut servir de condensateur===
Les technologies CMOS conventionnelles mettent la sortie d'une porte logique à 0/1 en la connectant à la tension d'alimentation ou à la masse. La logique ''pass transistor'' transfère la tension et le courant de l'entrée vers la sortie. Dans les deux cas, la sortie est connectée directement ou indirectement à la tension d'alimentation quand on veut lui faire sortie un 1. Avec la logique dynamique, ce n'est pas le cas. La sortie est maintenue à 0 ou à 1 en utilisant un réservoir d'électron qui remplace la tension d'alimentation.
En électronique, il existe un composant qui sert de réservoir à électricité : il s'agit du '''condensateur'''. On peut le charger en électricité, ou le vider pour fournir un courant durant une petite durée de temps. Par convention, un condensateur stocke un 1 s'il est rempli, un 0 s'il est vide. L'intérieur d'un condensateur est formé de deux couches de métal conducteur, séparées par un isolant électrique. Les deux plaques de conducteur sont appelées les armatures du condensateur. C'est sur celles-ci que les charges électriques s'accumulent lors de la charge/décharge d'un condensateur. L'isolant empêche la fuite des charges d'une armature à l'autre, ce qui permet au condensateur de fonctionner comme un réservoir, et non comme un simple fil.
Il est possible de fabriquer un pseudo-condensateur avec un transistor MOS. En effet, tout transistor MOS a un pseudo-condensateur caché entre la grille et la liaison source-drain. Pour comprendre ce qui se passe dans ce transistor de mémorisation, il faut savoir ce qu'il y a dans un transistor CMOS. À l'intérieur, on trouve une plaque en métal appelée l'armature, un bout de semi-conducteur entre la source et le drain, et un morceau d'isolant entre les deux. L'ensemble forme donc un condensateur, certes imparfait, qui porte le nom de capacité parasite du transistor. Suivant la tension qu'on envoie sur la grille, l'armature va se remplir d’électrons ou se vider, ce qui permet de stocker un bit : une grille pleine compte pour un 1, une grille vide compte pour un 0.
[[File:Transistor CMOS - 1.png|centre|vignette|upright=2|Anatomie d'un transistor CMOS]]
L'utilisation de transistors MOS comme condensateur n'est pas spécifique à la logique dynamique. Certains mémoires RAM le font, comme nous le verrons dans le chapitre sur les cellules mémoires. Aussi, il est intéressant d'en parler maintenant, histoire de préparer le terrain. D'ailleurs, les mémoires RAM sont remplies de logique dynamique.
===L'utilisation des pseudo-condensateurs en logique dynamique===
Un circuit conçu en logique dynamique contient un transistor est utilisé comme condensateur. Il s’insère entre la tension d'alimentation et la sortie du circuit. Son rôle est simple : lorsqu'on utilise la sortie, le condensateur se vide, ce qui place la sortie à 1. le reste du temps, le condensateur est relié à la tension d'alimentation et se charge. Un circuit en logique dynamique effectue son travail en deux phases : une phase d'inactivité où il remplit ses condensateurs, et une phase où sa sortie fonctionne. Les deux phases sont appelées la '''phase de précharge''' et la '''phase d'évaluation'''. La succession de ces deux phases est réalisée par le signal d'horloge : la première phase a lieu quand le signal d'horloge est à 1, l'autre quand il est à 0.
Voici un exemple de porte NAND en logique dynamique MOS. La porte est alors réalisée avec des transistors NMOS et PMOS, le circuit ressemble à ce qu'on a en logique NMOS. En bas, on trouve les transistors NMOS pour relier la sortie au 0 volt. Mais au-dessus, on trouve un transistor CMOS qui remplace la résistance. Le fonctionnement du circuit est simple. Quand l'entrée ''clock'' est à 1, le condensateur se charge, les deux transistors NMOS sont déconnectés de la masse et le circuit est inactif. Puis, quand ''clock'' passe à 0, Le transistor PMOS se comporte en circuit ouvert, ce qui déconnecte la tension d'alimentation. Et son pseudo-condensateur se vide, ce qui fournit une tension d'alimentation de remplacement temporaire. Le transistor NMOS du bas se ferme, ce qui fait que les deux transistors A et B décident de si la sortie est connectée au 0 volt ou non. Si c'est le cas, le pseudo-condensateur se vide dans le 0 volt et la sortie est à 0. Sinon, le pseudo-condensateur se vide dans la sortie, ce qui la met à 1.
[[File:Dlnand.svg|centre|vignette|Porte NAND en logique CMOS.]]
Il est aussi possible de créer une bascule D en utilisant la logique dynamique. L'idée est de prendre une bascule D normale, mais d'ajouter un fonctionnement en deux étapes en ajoutant des transistors/interrupteurs. Pour rappel, une bascule D normale est composée de deux inverseurs reliés l'un à l'autre en formant une boucle, avec un multiplexeur pour permettre les écritures dans la boucle.
[[File:Implémentation conceptuelle d'une bascule D.png|centre|vignette|upright=2|Implémentation conceptuelle d'une bascule D]]
[[File:Animation du fonctionnement d'une bascule.gif|vignette|upright=2|Animation du fonctionnement de la bascule précédente.]]
Le circuit final ajoute deux transistors entre les inverseurs tête-bêche. Les transistors en question sont reliés à l'horloge, l'un étant ouvert quand l'autre est fermé. Grâce à eux, le bit mémorisé circule d'un inverseur à l'autre : il est dans le premier inverseur quand le signal d'horloge est à 1, dans l'autre inverseur quand il est à 0 (en fait son inverse, comme vous l'aurez compris). Le tout est illustré ci-contre. Cette implémentation a été utilisée autrefois, notamment dans le processeur Intel 8086.
[[File:Bascule D en logique Dynamique, avec entrée Enable.png|centre|vignette|upright=2|Bascule D en logique Dynamique, avec entrée Enable]]
Il existe une variante très utilisée, qui permet de remplacer le multiplexeur par un circuit légèrement plus simple. Avec elle, on a deux entrées pour commander la bascule, et non une seule entrée Enable. L'entrée Enable autorise les écriture, l'entrée Hold ferme la boucle qui relie la sortie du second inverseur au premier. Chaque entrée est associé à un transistor/interrupteur. Le transistor sur lequel on envoie l'entrée Enable se ferme uniquement lors des écritures et reste fermé sinon. À l'inverse, le transistor relié au signal Hold est fermé en permanence, sauf lors des écritures. En clair, les deux signaux sont l'inverse l'un de l'autre. Il permet de fermer le circuit, de bien relier les deux inverseurs en tête-bêche, sauf lors des écritures. On envoie donc l'inverse de l'entrée Enable sur ce transistor.
[[File:Bascule D en logique dynamique.png|centre|vignette|upright=2|Bascule D en logique dynamique]]
Une manière de comprendre le circuit précédent est de le comparer à celui avec le multiplexeur. Le multiplexeur est composé d'une porte NON et de deux transistors. Il se trouve que les deux transistors en question sont placés au même endroit que les transistors connectés aux signaux Hold et Enable. En prenant retirant la porte NON du multiplexeur, on se retrouve avec le circuit. Au lieu de prendre un Signal Enable qui commande les deux transistors, ce qui demande d'ajouter une porte NON vu que les deux transistors doivent faire l'inverse l'un de l'autre, on se contente d'envoyer deux signaux séparés pour commander chaque transistor indépendamment.
Des circuits nettement plus complexes peuvent être créés en logique dynamique. Pour les curieux, sachez que le ''barrel shifter'' du processeur Intel 386 était construit en logique dynamique. Le ''barrel shifter'' en question était un circuit capable de décaler des opérandes de 32 bits, ce qui était beaucoup pour l'époque. Il faisait environ 2000 transistors, ce qui correspondait à environ la moitié des transistors du CPU 6502 de Motorola, un CPU 8 bits. Et il aurait sans doute fait plus s'il n'avait pas utilisé la logique dynamique, ainsi que diverses optimisations. L'article suivant, assez complet, détaille ce circuit : [https://www.righto.com/2023/12/386-barrel-shifter.html Reverse engineering the barrel shifter circuit on the Intel 386 processor die ]
===Avantages et inconvénients===
Les circuits en logique dynamique sont opposés aux circuits en logique statique, ces derniers étant les circuits CMOS, PMOS, NMOS ou TTL vu jusqu'à présent. Les circuits dynamiques et statiques ont des différences notables, ainsi que des avantages et inconvénients divers. Si on devait résumer :
* la logique dynamique utilise généralement un peu plus de transistors qu'un circuit CMOS normal ;
* la logique dynamique est souvent très rapide par rapport à la concurrence, car elle n'utilise que des transistors NMOS, plus rapides ;
* la consommation d'énergie est généralement supérieure comparé au CMOS.
Un désavantage de la logique dynamique est qu'elle utilise plus de transistors. On économise certes des transistors MOS, mais il faut rajouter les transistors pour déconnecter les transistors NMOS de la masse (0 volt). Le second surcompense le premier.
Un autre désavantage est que le signal d'horloge ne doit pas tomber en-dessous d'une fréquence minimale. Avec une logique statique, on a une fréquence maximale, mais pas de fréquence minimale. Avec un circuit statique peut réduire la fréquence d'un circuit pour économiser de l'énergie, pour améliorer sa stabilité, et de nombreux processeurs modernes ne s'en privent pas. On peut même stopper le signal d'horloge et figer le circuit, ce qui permet de le mettre en veille, d'en stopper le fonctionnement, etc. Impossible avec la logique dynamique, qui demande de ne pas tomber sous la fréquence minimale. Cela a un impact sur la consommation d'énergie, sans compter que cela se marie assez mal avec certaines applications. Un processeur moderne ne peut pas être totalement fabriqué en logique dynamique, car il a besoin d'être mis en veille et qu'il a besoin de varier sa fréquence en fonction des besoins.
Le dernier désavantage implique l'arbre d'horloge, le système d'interconnexion qui distribue le signal d'horloge à toutes les bascules d'un circuit. L'arbre d'horloge est beaucoup plus compliqué avec la logique dynamique qu'avec la logique statique. Avec la logique statique, seules les bascules doivent recevoir le signal d'horloge, avec éventuellement quelques rares circuits annexes. Mais avec la logique dynamique, toutes les portes logiques doivent recevoir le signal d'horloge, ce qui rend la distribution de l'hrologe beaucoup plus compliquée. C'est un point qui fait que la logique dynamique est assez peu utilisée, et souvent limitée à quelques portions bien précise d'un processeur.
==La logique TTL : un apercu rapide==
Tous ce que nous avons vu depuis le début de ce chapitre porte sur les transistors MOS et les technologies associées. Mais les transistors MOS n'ont pas été les premiers inventés. Ils ont été précédés par les '''transistors bipolaires'''. Nous ne parlerons pas en détail du fonctionnement d'un transistor bipolaire, car celui-ci est extraordinairement compliqué. Cependant, nous devons parler rapidement de la logique TTL, qui permet de fabriquer des portes logiques avec ces transistors bipolaires. Là encore, rassurez-vous, nous n'allons pas voir comment fabriquer des portes logiques en logique TTL, cela serait trop compliqué, sans compter que le but n'est pas de faire un cours d'électronique. Mais nous devons fait quelques remarques et donner quelques explications superficielles.
La raison à cela est double. La première raison est que certains circuits présents dans les mémoires RAM sont fabriqués avec des transistors bipolaires. C'est notamment le cas des amplificateurs de lecture ou d'autres circuits de ce genre. De tels circuits ne peuvent pas être implémentés facilement avec des transistors CMOS et nous expliquerons rapidement pourquoi dans ce qui suit. La seconde raison est que ce cours parlera occasionnellement de circuits anciens et qu'il faut quelques bases sur le TTL pour en parler.
Dans la suite du cours, nous verrons occasionnellement quelques circuits anciens, pour la raison suivante : ils sont très simples, très pédagogiques, et permettent d'expliquer simplement certains concepts du cours. Rien de mieux que d'étudier des circuits réels pour donner un peu de chair à des explications abstraites. Par exemple, pour expliquer comment fabriquer une unité logique de calcul bit à bit, je pourrais utiliser l'exemple du Motorola MC14500B, un processeur 1 bit qui est justement une unité logique sous stéroïdes. Ou encore, dans le chapitre sur les circuits additionneurs, je parlerais du circuit additionneur présent dans l'Intel 8008 et dans l'Intel 4004, les deux premiers microprocesseurs commerciaux. Malheureusement, malgré leurs aspects pédagogiques indéniables, ces circuits ont le défaut d'être des circuits TTL. Ce qui est intuitif : les circuits les plus simples ont été inventés en premier et utilisent du TTL plus ancien. Beaucoup de ces circuits ont été inventés avant même que le CMOS ou même les transistors MOS existent. D'où le fait que nous devons faire quelques explications mineures sur le TTL.
===Les transistors bipolaires===
Les '''transistors bipolaires''' ressemblent beaucoup aux transistors MOS. Les transistors bipolaires ont trois broches, appelées le collecteur, la base et l'émetteur. Notez que ces trois termes sont différents de ceux utilisés pour les transistors MOS, où on parle de la grille, du drain et de la source.
Là encore, comme pour les transistors PMOS et NMOS, il existe deux types de transistors bipolaires : les NPN et les PNP. Là encore, il est possible de fabriquer une puce en utilisant seulement des NPN, seulement des PNP, ou en mixant les deux. Mais les ressemblances s'arrêtent là. La différence entre PNP et NPN tient dans la manière dont les courants entrent ou sortent du transistor. La flèche des symboles ci-dessous indique si le courant rentre ou sort par l'émetteur : il rentre pour un PNP, sort pour un NPN. Dans la suite du cours, nous n'utiliserons que des transistors NPN, les plus couramment utilisés.
{|
|[[File:BJT PNP symbol.svg|vignette|BJT PNP]]
|[[File:BJT NPN symbol.svg|vignette|BJT NPN]]
|}
Plus haut nous avons dit que les transistors CMOS sont des interrupteurs. La réalité est que tout transistor peut être utilisé de deux manières : soit comme interrupteur, soit comme amplificateur de tension/courant. Pour simplifier, le transistor bipolaire NPN prend en entrée un courant sur sa base et fournit un courant amplifié sur l'émetteur. Pour s'en servir comme amplificateur, il faut fournir une source de courant sur le collecteur. Le fonctionnement exact est cependant plus compliqué.
[[File:Transistor bipolaire, explication simplifiée de son fonctionnement.png|centre|vignette|upright=1.5|Transistor bipolaire, explication simplifiée de son fonctionnement]]
Les transistors bipolaires sont de bons amplificateurs, mais de piètres interrupteurs. À l'inverse, les transistors CMOS sont généralement de bons interrupteurs, mais de moyens amplificateurs. Pour des circuits numériques, la fonction d'interrupteur est clairement plus adaptée, car elle-même binaire (un transistor est fermé ou ouvert : deux choix possibles). Aussi, les circuits modernes privilégient des transistors CMOS aux transistors bipolaires. À l'inverse, la fonction d'amplification est adaptée aux circuits analogiques.
C'est pour ça que nous rencontrerons les transistors bipolaires soit dans des portions de l'ordinateur qui sont au contact de circuits analogiques. Pensez par exemple aux cartes sons ou au vieux écrans cathodiques, qui gèrent des signaux analogiques (le son pour la carte son, les signaux vidéo analogique pour les vieux écrans). On les croisera aussi dans les mémoires DRAM, dont la conception est un mix entre circuits analogiques et numériques. Nous les croiserons aussi dans de vieux circuits antérieurs aux transistors MOS. Les anciens circuits faisaient avec les transistors bipolaires car ils n'avaient pas le choix, mais ils ont été partiellement remplacés dès l'apparition des transistors CMOS.
===Les portes logiques complexes en TTL===
Le détail le plus important qui nous concernera dans la suite du cours est le suivant : on peut créer des portes logiques exceptionnellement complexes en TTL. Pour comprendre pourquoi, sachez qu'il existe des transistors bipolaires qui possèdent plusieurs émetteurs. Ils sont très utilisés pour fabriquer des portes logiques à plusieurs entrées. Les émetteurs correspondent alors à des entrées de la porte logique. Ainsi, une porte logique à plusieurs entrées se fait non pas en ajoutant des transistors, comme c'est le cas avec les transistors MOS, mais en ajoutant un émetteur sur un transistor. Cela permet à une porte NAND à trois entrées de n'utiliser que deux transistors bipolaires, au lieu de quatre transistors MOS.
[[File:Multiemitter Transistor.svg|centre|vignette|upright=1|Transistor bipolaire avec plusieurs émetteurs.]]
De plus, là où les logiques PMOS/NMOS/CMOS permettent de fabriquer les portes de base que nous avons précédemment, elles ne peuvent pas faire plus. Au pire, on peut implémenter des portes ET/OU/NAND/NOR à plusieurs entrées, mais pas plus. En TTL, on peut parfaitement créer des portes de type ET/OU/NON ou OU/ET/NON, avec seulement quatre transistors. Par exemple, une '''porte ET/OU/NON''' de type 2-2 entrées (pour rappel, qui effectue un ET par paire d’entrée puis fait un NOR entre le résultat des deux ET) est bien implémenté en une seule porte logique, pas en enchainant deux ou trois portes à la suite.
[[File:TTL AND-OR-INVERT 1961.png|centre|vignette|upright=2|TTL AND-OR-INVERT 1961]]
===Les désavantages et avantages des circuits TTL===
Pour résumer, le TTL à l'avantage de pouvoir fabriquer des portes logiques avec peu de transistors comparé au CMOS, surtout pour les portes logiques complexes. Et autant vous dire que les concepteurs de puce électroniques ne se gênaient pas pour utiliser ces portes complexes, capables de fusionner 3 à 5 portes en une seule : les économies de transistors étaient conséquentes.
Et pourtant, les circuits TTL étaient beaucoup plus gros que leurs équivalents CMOS. La raison est qu'un transistor bipolaire prend beaucoup de place : il est environ 10 fois plus gros qu'un transistor MOS. Autant dire que les économies réalisées avec des portes logiques complexes ne faisaient que compenser la taille énorme des transistors bipolaires. Et encore, cette compensation n'était que partielle, ce qui fait que les circuits PMOS/NMOS/CMOS se miniaturisent beaucoup plus facilement. Un avantage pour le transistor MOS !
De plus, les schémas précédents montrent que les portes logiques en TTL utilisent une résistance, elle aussi difficile à miniaturiser. Et cette résistance est parcourue en permanence par un courant, ce qui fait qu'elle consomme de l'énergie et chauffe. C'est la même chose en logique NMOS et PMOS, ce qui explique leur forte consommation d'énergie. Les circuits TTL ont donc le même problème.
[[File:TTL Input voltage.svg|vignette|upright=0.5|TTL voltage.]]
Un autre défaut est lié à la une tension d'alimentation. Les circuits TTL utilisent une tension d'alimentation de 5 volts, alors que les circuits CMOS ont une tension d'alimentation beaucoup plus variable. Les circuits CMOS vont de 3 volts à 18 volts pour les circuits commerciaux, avec des tensions de 1 à 3 volts pour les circuits optimisés. Les circuits CMOS sont généralement bien optimisés et utilisent une tension d'alimentation plus basse que les circuits TTL, ce qui fait qu'ils consomment moins d'énergie et de courant.
De plus, rappelons que coder un zéro demande que la tension soit sous un seuil, alors que coder un 1 demande qu'elle dépasse un autre seuil, avec une petite marge de sécurité entre les deux. Les seuils en question sont indiqués dans le diagramme ci-dessous. Il s'agit des seuils VIH et VIL. On voit que sur les circuits TTL, la marge de sécurité est plus faible qu'avec les circuits CMOS. De plus, les marges sont bien équilibrées en CMOS, à savoir que la marge de sécurité est en plein milieu entre la tension max et le zéro volt. Avec le TTL normal, la marge de sécurité est très proche du zéro volt. Un 1 est codé par une tension entre 2 et 5 volts en TTL ! Une version améliorée du TTL, le LVTTL, corrige ce défaut. Elle baisse la tension d'alimentation à 3,3 Volts, mais elle demande des efforts de fabrication conséquents.
[[File:Niveaux logiques CMOS-TTL-LVTTL.png|centre|vignette|upright=2|Niveaux logiques CMOS-TTL-LVTTL]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de calcul trigonométriques
| prevText=Les circuits de calcul trigonométriques
| next=Les circuits intégrés
| nextText=Les circuits intégrés
}}
{{autocat}}
</noinclude>
bkdhqzlxyrfskpr7tc42upg743uzw3k
768608
768607
2026-06-25T15:04:07Z
Mewtow
31375
/* L'additionneur Manchester Carry Chain */
768608
wikitext
text/x-wiki
Dans le chapitre précédent, nous avons abordé les portes logiques. Dans ce chapitre, nous allons voir qu'elles sont fabriquées avec des composants électroniques que l'on appelle des '''transistors'''. Ces derniers sont reliés entre eux pour former des circuits plus ou moins compliqués. Pour donner un exemple, sachez que les derniers modèles de processeurs peuvent utiliser près d'un milliard de transistors.
==Les transistors MOS==
[[File:Transistor basic flow.svg|vignette|Un transistor est un morceau de conducteur, dont la conductivité est contrôlée par sa troisième broche/borne.]]
Les transistors possèdent trois '''broches''', des pattes métalliques sur lesquelles on connecte des fils électriques. On peut appliquer une tension électrique sur ces broches, qui peut représenter soit 0 soit 1. Sur ces trois broches, il y en a deux entre lesquelles circule un courant, et une troisième qui commande le courant. Le transistor s'utilise le plus souvent comme un interrupteur commandé par sa troisième broche. Le courant qui traverse les deux premières broches passe ou ne passe pas selon ce qu'on met sur la troisième.
Il existe plusieurs types de transistors, mais les deux principaux sont les transistors bipolaires et les transistors MOS. De nos jours, les transistors utilisés dans les ordinateurs sont tous des '''transistors MOS'''. Les raisons à cela sont multiples, mais les plus importantes sont les suivantes. Premièrement, les transistors bipolaires sont plus difficiles à fabriquer et sont donc plus chers. Deuxièmement, ils consomment bien plus de courant que les transistors MOS. Et enfin, les transistors bipolaires sont plus gros, ce qui n'aide pas à miniaturiser les puces électroniques. Tout cela fait que les transistors bipolaires sont aujourd'hui tombés en désuétude et ne sont utilisés que dans une minorité de circuits.
===Les types de transistors MOS : PMOS et NMOS===
Sur un transistor MOS, chaque broche a un nom, nom qui est indiqué sur le schéma ci-dessous.On distingue ainsi le '''drain''', la '''source''' et la '''grille''' On l'utilise le plus souvent comme un interrupteur commandé par sa grille. Appliquez la tension adéquate et la liaison entre la source et le drain se comportera comme un interrupteur fermé. Mettez la grille à une autre valeur et cette liaison se comportera comme un interrupteur ouvert.
Il existe deux types de transistors CMOS, qui diffèrent entre autres par le bit qu'il faut mettre sur la grille pour les ouvrir/fermer :
* les transistors NMOS qui s'ouvrent lorsqu'on envoie un zéro sur la grille et se ferment si la grille est à un ;
* et les PMOS qui se ferment lorsque la grille est à zéro, et s'ouvrent si la grille est à un.
[[File:Td7bfig2.png|centre|vignette|upright=2|Illustration du fonctionnement des transistors NMOS et PMOS.]]
Voici les symboles de chaque transistor.
{|
|[[File:Transistor CMOS.png|vignette|upright=0.5|Transistor CMOS]]
|[[File:IGFET N-Ch Enh Labelled simplified.svg|vignette|upright=0.5|Transistor MOS à canal N (NMOS).]]
|[[File:IGFET P-Ch Enh Labelled simplified.svg|vignette|upright=0.5|Transistor MOS à canal P (PMOS).]]
|}
===L'anatomie d'un transistor MOS===
À l'intérieur du transistor, on trouve simplement une plaque en métal reliée à la grille appelée l'armature, un bout de semi-conducteur entre la source et le drain, et un morceau d'isolant entre les deux. Pour rappel, un semi-conducteur est un matériau qui se comporte soit comme un isolant, soit comme un conducteur, selon les conditions auxquelles on le soumet. Dans un transistor, son rôle est de laisser passer le courant, ou de ne pas le transmettre, quand il faut. C'est grâce à ce semi-conducteur que le transistor peut fonctionner en interrupteur : interrupteur fermé quand le semi-conducteur conduit, ouvert quand il bloque le courant. La commande de la résistance du semi-conducteur (le fait qu'il laisse passer ou non le courant) est réalisée par la grille, comme nous allons le voir ci-dessous.
[[File:Transistor CMOS - 1.png|centre|vignette|upright=2|Transistor CMOS]]
Suivant la tension que l'on place sur la grille, celle-ci va se remplir avec des charges négatives ou positives. Cela va entrainer une modification de la répartition des charges dans le semi-conducteur, ce qui modulera la résistance du conducteur. Prenons par exemple le cas d'un transistor NMOS et étudions ce qui se passe selon la tension placée sur la grille. Si on met un zéro, la grille sera vide de charges et le semi-conducteur se comportera comme un isolant : le courant ne passera pas. En clair, le transistor sera équivalent à un interrupteur ouvert. Si on met un 1 sur la grille, celle-ci va se remplir de charges. Le semi-conducteur va réagir et se mettre à conduire le courant. En clair, le transistor se comporte comme un interrupteur fermé.
{|
|[[File:Transistor CMOS - 3.png|vignette|upright=1.5|Transistor NMOS fermé.]]
|[[File:Transistor CMOS - 4.png|vignette|upright=1.5|Transistor NMOS ouvert.]]
|}
===La tension de seuil d'un transistor===
Le fonctionnement d'un transistor est légèrement plus complexe que ce qui a été dit auparavant. Mais pour rester assez simple, disons que son fonctionnement exact dépend de trois paramètres : la tension d'alimentation, le courant entre drain et source, et un nouveau paramètre appelé la tension de seuil.
Appliquons une tension sur la grille d'un transistor NMOS. Si la tension de grille reste sous un certain seuil, le transistor se comporte comme un interrupteur fermé. Le seuil de tension est appelé, très simplement, la '''tension de seuil'''. Au-delà de la tension de seuil, le transistor se comporte comme un interrupteur ouvert, il laisse passer le courant. La valeur exacte du courant dépend de la tension entre drain et source, soit la tension d'alimentation. Elle aussi dépend de la différence entre tension de grille et de seuil, à savoir <math>U_G - U_\text{seuil}</math>.
Le paragraphe qui va suivre est optionnel, mais détaille un peu plus le fonctionnement d'un transistor MOS. Tout ce qu'il faut comprendre est que la tension de seuil est une tension minimale pour ouvrir le transistor. Le plus important à retenir est que l'on ne peut pas baisser la tension d'alimentation sous la tension de seuil, ce qui est un léger problème en termes de consommation énergétique. Ce détail reviendra plus tard dans ce cours, quand nous parlerons de la consommation d'énergie des circuits électroniques.
Dans les cas que nous allons voir dans ce cours, la tension d'alimentation est plus grande que <math>U_G - U_\text{seuil}</math>. Le courant est alors maximal, il est proportionnel à <math>U_G - U_\text{seuil}</math>. Le transistor ne fonctionne alors pas comme un amplificateur, le courant reste le même. Si la tension d'alimentation est plus petite que <math>U_G - U_\text{seuil}</math>, le transistor est en régime linéaire : le courant de sortie est proportionnel à <math>U_G - U_\text{seuil}</math>, ainsi qu'à la tension d'alimentation. Le transistor fonctionne alors comme un amplificateur de courant, dont l'intensité de l'amplification est commandée par la tension.
[[File:MOSFET enhancement-mode n-channel en.svg|centre|vignette|upright=2.5|Relations entre tensions et courant d'un MOSFET à dopage N.]]
==La technologie CMOS==
Les portes logiques que nous venons de voir sont actuellement fabriquées en utilisant des transistors. Il existe de nombreuses manières pour concevoir des circuits à base de transistors, qui portent les noms de DTL, RTL, TLL, CMOS et bien d'autres. Les techniques anciennes concevaient des portes logiques en utilisant des diodes, des transistors bipolaires et des résistances. Mais elles sont aujourd'hui tombées en désuétudes dans les circuits de haute performance. De nos jours, on n'utilise que des logiques MOS (''Metal Oxyde Silicium''), qui utilisent des transistors MOS vus plus haut dans ce chapitre, parfois couplés à des résistances. On distingue :
* La '''logique NMOS''', qui utilise des transistors NMOS associés à des résistances.
* La '''logique PMOS''', qui utilise des transistors PMOS associés à des résistances.
* La '''logique CMOS''', qui utilise des transistors PMOS et NMOS, sans résistances.
Dans cette section, nous allons montrer comment fabriquer des portes logiques en utilisant la '''technologie CMOS'''. Avec celle-ci, chaque porte logique est fabriquée à la fois avec des transistors NMOS et des transistors PMOS. On peut la voir comme un mélange entre la technologie PMOS et NMOS. Tout circuit CMOS est divisé en deux parties : une intégralement composée de transistors PMOS et une autre de transistors NMOS. Chacune relie la sortie du circuit soit à la masse, soit à la tension d'alimentation.
[[File:Principe de la conception de circuit en technologie CMOS.png|centre|vignette|upright=2|Principe de conception d'une porte logique/d'un circuit en technologie CMOS.]]
La première partie relie la tension d'alimentation à la sortie, mais uniquement quand la sortie doit être à 1. Si la sortie doit être à 1, des transistors PMOS vont se fermer et connecter tension et sortie. Dans le cas contraire, des transistors s'ouvrent et cela déconnecte la liaison entre sortie et tension d'alimentation. L'autre partie du circuit fonctionne de la même manière que la partie de PMOS, sauf qu'elle relie la sortie à la masse et qu'elle se ferme quand la sortie doit être mise à 0
[[File:Fonctionnement d'un circuit en logique CMOS.png|centre|vignette|upright=2|Fonctionnement d'un circuit en logique CMOS.]]
Dans ce qui va suivre, nous allons étudier la porte NON, la porte NAND et la porte NOR. La porte de base de la technologie CMOS est la porte NON, les portes NAND et NOR ne sont que des versions altérées de la porte NON qui ajoutent des entrées et quelques transistors. Les autres portes, comme la porte ET et la porte OU, sont construites à partir de ces portes. Nous parlerons aussi de la porte XOR, qui est un peu particulière.
===La porte NON===
Cette porte est fabriquée avec seulement deux transistors, comme indiqué ci-dessous.
[[File:Porte NON fabriquée avec des transistors CMOS. 01.jpg|centre|vignette|upright=1|Porte NON fabriquée avec des transistors CMOS.]]
Si on met un 1 en entrée de ce circuit, le transistor du haut va fonctionner comme un interrupteur ouvert, et celui du bas comme un interrupteur fermé : la sortie est reliée au zéro volt, et vaut donc 0. Inversement, si on met un 0 en entrée de ce petit montage électronique, le transistor du bas va fonctionner comme un interrupteur ouvert, et celui du haut comme un interrupteur fermé : la sortie est reliée à la tension d'alimentation, et vaut donc 1.
[[File:Porte NON fabriquée avec des transistors CMOS - fonctionnement.png|centre|vignette|upright=2|Porte NON fabriquée avec des transistors CMOS - fonctionnement.]]
===Les portes NAND et NOR===
Passons maintenant aux portes logiques à plusieurs entrées. Pour celles-ci, on va devoir utiliser plus de transistors que pour la porte NON, ce qui demande de les organiser un minium. Une porte logique à deux entrées demande d'utiliser au moins deux transistors par entrée : un transistor PMOS et un NMOS par entrée. Rappelons qu'un transistor est associé à une entrée : l'entrée est directement envoyée sur la grille du transistor et commande son ouverture/fermeture. Pour les portes logiques à 3, 4, 5 entrées, la logiques est la même : au minimum deux transistors par entrée, un PMOS et un NMOS.
Nous allons d'abord voir le cas d'une porte NOR/NAND en CMOS. Avec elles, les transistors sont organisées de deux manières, appelées '''transistors en série''' (l'un après l'autre, sur le même fil) et '''transistors en parallèle''' (sur des fils différents). Le tout est illustré ci-dessous. Avec des transistors en série, plusieurs transistors NMOS ou deux PMOS se suivent sur le même fil, mais on ne peut pas mélanger NMOS et PMOS sur le même fil.
[[File:Transistors CMOS en série et en parallèle.png|centre|vignette|upright=2|Transistors CMOS en série et en parallèle]]
====Les portes NAND/NOR à deux entrées====
Voyons d'abord le cas des portes NAND/NOR à deux entrées. Elles utilisent deux transistors NMOS et deux PMOS.
Avec des transistors en série, deux transistors NMOS ou deux PMOS se suivent sur le même fil, mais on ne peut pas mélanger NMOS et PMOS sur le même fil. Avec des transistors en parallèle, c'est l'exact inverse. L'idée est de relier la tension d'alimentation à la sortie à travers deux PMOS transistors distincts, chacun sur son propre fil, sa propre connexion indépendante des autres. Pour la masse (0 volt), il faut utiliser deux transistors NMOS pour la relier à la sortie, avec là encore chaque transistor NMOS ayant sa propre connexion indépendante des autres. En clair, chaque entrée commande un transistor qui peut à lui seul fermer le circuit.
On rappelle deux choses : chaque transistor est associée à une entrée sur sa grille, un transistor se ferme si l'entrée vaut 0 pour des transistors PMOS et 1 pour des NMOS. Avec ces deux détails, on peut expliquer comment fonctionnent des transistors en série et en parallèle. Pour résumer, les transistors en série ferment la connexion quand toutes les entrées sont à 1 (NMOS) ou 0 (PMOS). Avec les transistors en parallèle, il faut qu'une seule entrée soit à 1 (NMOS) ou 0 (PMOS) pour que la connexion se fasse.
Une porte NOR met sa sortie à 1 si toutes les entrées sont à 0, à 0 si une seule entrée vaut 1. Pour reformuler, il faut connecter la sortie à la tension d'alimentation si toutes les entrées sont à 0, ce qui demande d'utiliser des transistors PMOS en série. Pour gérer le cas d'une seule entrée à 1, il faut utiliser deux transistors en parallèle entre la masse et la sortie. Le circuit obtenu est donc celui obtenu dans le premier schéma. Le même raisonnement pour une porte NAND donne le second schéma.
{|
|[[File:Porte NOR fabriquée avec des transistors. 02.png|centre|vignette|upright=1|Porte NOR fabriquée avec des transistors.]]
|[[File:Porte NAND fabriquée avec des transistors. 04.png|centre|vignette|upright=1|Porte NAND fabriquée avec des transistors.]]
|}
Leur fonctionnement s'explique assez bien si on regarde ce qu'il se passe en fonction des entrées. Suivant la valeur de chaque entrée, les transistors vont se fermer ou s'ouvrir, ce qui va connecter la sortie soit à la tension d'alimentation, soit à la masse.
Voici ce que cela donne pour une porte NAND :
[[File:Porte NAND fabriquée avec des transistors - Fonctionnement.png|centre|vignette|upright=2|Porte NAND fabriquée avec des transistors.]]
Voici ce que cela donne pour une porte NOR :
[[File:Porte NOR fabriquée avec des transistors - Fonctionnement.png|centre|vignette|upright=2|Porte NOR fabriquée avec des transistors.]]
====Les portes NAND/NOR/ET/OU à plusieurs entrées====
Les portes NOR/NAND à plusieurs entrées sont construites à partir de portes NAND/NOR à deux entrées auxquelles on rajoute des transistors. Il y a autant de transistors en série que d'entrée, pareil pour les transistors en parallèle. Leur fonctionnement est similaire à leurs cousines à deux entrées. Les portes ET et OU à plusieurs entrées sont construites à partie de NAND/NOR suivies d'une porte NON.
{|
|[[File:NAND plusieurs entrées.png|vignette|NAND plusieurs entrées]]
|[[File:NOR plusieurs entrées.png|vignette|NOR plusieurs entrées]]
|}
En théorie, on pourrait créer des portes avec un nombre arbitraire d'entrées avec cette méthode. Cependant, au-delà d'un certain nombre de transistors en série/parallèle, les performances s'effondrent rapidement. Le circuit devient alors trop lent, sans compter que des problèmes purement électriques surviennent. En pratique, difficile de dépasser la dizaine d'entrées. Dans ce cas, les portes sont construites en assemblant plusieurs portes NAND/NOR ensemble. Et faire ainsi marche nettement mieux pour fabriquer des portes ET/OU que pour des portes NAND/NOR.
====Les portes ET/OU sont fabriquées à partir de NAND/NOR en CMOS====
En logique CMOS, les portes logiques ET et OU sont construites en prenant une porte NAND/NOR et en mettant une porte NON sur sa sortie. Il est théoriquement possible d'utiliser uniquement des transistors en série et en parallèle, mais cette solution utilise plus de transistors.
{|
|[[File:CMOS AND Layout.svg|vignette|Porte ET en CMOS]]
|[[File:CMOS OR.svg|vignette|Porte OU en CMOS]]
|}
Pour ce qui est des portes ET/OU avec beaucoup d'entrées, il est fréquent qu'elles soit construites en combinant plusieurs portes ET/OU moins complexes. Par exemple, une porte ET à 32 entrées sera construite à partir de portes à seulement 4 ou 5 entrées. Il existe cependant une alternative qui se marie nettement mieux avec la logique CMOS. Rappelons qu'en logique CMOS, les portes NAND et NOR sont les portes à plusieurs entrées les plus simples à fabriquer. L'idée est alors de combiner des portes NAND/NOR pour créer une porte ET/OU.
Voici la comparaison entre les deux solutions pour une porte ET :
{|
|[[File:12 input AND gate via cascade of AND gates.svg|vignette|ET plusieurs entrées]]
|[[File:12-input AND gate from NAND and NOR.svg|vignette|ET plusieurs entrées]]
|}
Voici la comparaison entre les deux solutions pour une porte OU :
{|
|[[File:12-input OR gate via cascade of OR gates.svg|vignette|OU plusieurs entrées]]
|[[File:12-input OR gate via NOR and NAND gates.svg|vignette|OU plusieurs entrées]]
|}
D'autres portes mélangent transistors en série et en parallèle d'une manière différente. Les portes ET-OU-NON et OU-ET-NON en sont un bon exemple.
===Une méthode générale===
Il existe une méthode générale pour créer des portes logiques à deux entrées. Avec elle, il faut repartir du montage avec deux transistors NMOS/PMOS en série. En théorie, il permet de relier la sortie à la tension d'alimentation/zéro volt si toutes les entrées sont à 0 (PMOS) ou 1 (NMOS). L'idée est de regarder ce qui se passe si on fait précéder l'entrée d'un transistor par une porte NON. Pour deux transistors, cela fait 4 possibilités, 8 au total si on fait la différence entre PMOS et NMOS. Voici les valeurs d'entrées qui ferment le montage à transistor en série, suivant l’endroit où on place la porte NON.
[[File:Transistors CMOS en série.png|centre|vignette|upright=2|Transistors CMOS en série]]
Mine de rien, avec ces 8 montages de base, on peut créer n'importe quelle porte logique à deux entrées. Il faut juste se souvenir que d'après les règles du CMOS, les deux transistors PMOS se placent entre la tension d'alimentation et la sortie, et servent à mettre la sortie à 1. Pour les deux transistors NMOS, ils sont reliés à la masse et mettent la sortie à 0. Pour mieux comprendre, prenons l'exemple d'une porte XOR.
Appliquons la méthode que je viens d'expliquer avec une porte XOR. Le résultat est sous-optimal, mieux vaut fabriquer une porte XOR en combinant d'autres portes logiques, mais c'est pour l'exemple. L'idée est très simple : on prend la table de vérité de la porte logique, et on associe deux transistors en série pour chaque ligne. Regardons d'abord la table de vérité ligne par ligne :
{|class="wikitable"
|-
!Entrée 1!!Entrée 2!!Sortie
|-
||0||0||0
|-
||0||1||1
|-
||1||0||1
|-
||1||1||0
|}
La première ligne a ses deux entrées à 0 et sort un 0. La sortie est à 0, ce qui signifie qu'il faut regarder sur la ligne des transistors NMOS, qui connectent la sortie à la masse. Le montage qui se ferme quand les deux entrées sont à 0 est celui tout en bas à droite du tableau précédent, à savoir deux transistors NMOS avec deux portes NON.
Les deux lignes du milieu ont une entrée à 0 et une à 1, et leur sortie à 1. La sortie à 1 signifie qu'il faut regarder sur la ligne des transistors PMOS, qui connectent la tension d'alimentation à la sortie. Les deux montages avec deux entrées différentes sont les deux situés au milieu, avec deux transistors PMOS et une porte logique.
La dernière ligne a ses deux entrées à 1 et sort un 0. La sortie est à 0, ce qui signifie qu'il faut regarder sur la ligne des transistors NMOS, qui connectent la sortie à la masse. Le montage qui se ferme quand les deux entrées sont à 1 est celui tout en bas à gauche du tableau précédent, à savoir deux transistors NMOS seuls.
En combinant ces quatre montages, on trouve le circuit suivant. Notons qu'il n'y a que deux portes NON marquées en vert et bleu : on a juste besoin d'inverser la première entrée et la seconde, pas besoin de portes en plus. Les portes NOn sont en quelque sorte partagées entre les transistors PMOS et NMOS.
[[File:Cmos xor.svg|centre|vignette|upright=1|class=transparent|Porte XOR en logique CMOS.]]
Si les deux entrées sont à 1, alors les deux transistors en bas à gauche vont se fermer et connecter la sortie au 0 volt, les trois autres groupes ayant au moins un transistor ouvert. Si les deux entrées sont à 0, alors les deux transistors en bas à droite vont se fermer et connecter la sortie au 0 volt, les autres quadrants ayant au moins un transistor ouvert. Et pareil quand les deux bits sont différents : un des deux quadrants aura ses deux transistors fermés, alors que les autres auront au moins un transistor ouvert, ce qui connecte la sortie à la tension d'alimentation.
On peut construire la porte NXOR sur la même logique. Et toutes les portes logiques peuvent se construire avec cette méthode. Le nombre de transistors est alors le même : on utilise 12 transistors au total : 4 paires de transistors en série, 4 transistors en plus pour les portes NON. Que ce soit pour la porte XOR ou NXOR, on économise beaucoup de transistors comparés à la solution naïve, qui consiste à utiliser plusieurs portes NON/ET/OU. Si on ne peut pas faire mieux dans le cas de la porte XOR/NXOR, sachez cependant que les autres portes construites avec cette méthode utilisent plus de transistors que nécessaire. De nombreuses simplifications sont possibles, comme on le verra plus bas.
Dans les faits, la méthode n'est pas utilisée pour les portes XOR. À la place, les portes XOR sont construites à base d'autres portes logiques plus simples, comme des portes NAND/NOR/ET/OU. Le résultat est que l'on a un circuit à 10 transistors, contre 12 avec la méthode précédente.
[[File:CMOS10TrXOR.svg|centre|vignette|Porte XOR en CMOS en 10 transistors.]]
===Les circuits plus complexes (''full adder'', ...)===
Il est possible de fusionner plusieurs portes ET-OU-NON en un seul circuit à transistors CMOS, ce qui permet des simplifications assez impressionnantes. Pour donner un exemple, le schéma suivant compare l'implémentation d'un circuit qui fait un ET entre les deux premières entrées, puis un NOR entre le résultat du ET et la troisième entrée. L'implémentation à droite du schéma avec une porte ET et une porte NOR prend 10 transistors. L'implémentation la plus simple, à gauche du schéma, prend seulement 6 transistors.
[[File:AOI21 complex vs standard gates.svg|centre|vignette|upright=1.5|Porte ET-OU-NON à trois entrées (de type 2-1) à gauche, contre la combinaison de plusieurs portes à droite.]]
Une conséquence est que des circuits assez complexes gagnent à être fabriqués directement avec des transistors. Prenons l'exemple de l'additionneur complet. Une implémentation naïve, avec 5 portes logiques, utilise beaucoup de transistors. Deux portes XOR, deux portes OU et une porte ET, cela dépasse la trentaine de transistors. Faisons le compte : 10 transistors par porte XOR, 6 pour les trois autres portes, cela fait 38 transistors. Les additionneurs des processeurs modernes sont optimisés directement au niveau des transistors, pour leur permettre d'économiser des transistors. Par exemple, l'implémentation suivante en utilise seulement 24 !
[[File:Inverting full adder CMOS 24T.svg|centre|vignette|upright=1.5|Additionneur complet fabriqué avec 24 transistors.]]
Et c'est sans compter que l'additionneur complet naïf n'est pas forcément le top du top en termes de performances. Là encore, une implémentation avec des transistors peut être optimisée pour être plus rapide, notamment au niveau du calcul de la retenue, ou au contraire d'économiser des transistors. Tout dépend de l'objectif visé, certains circuit optimisant à fond pour la vitesse, d'autres pour le nombre de transistors, d'autres font un compromis entre les deux. Les circuits de ce genre sont très nombreux, trop pour qu'on puisse les citer.
==La ''pass transistor logic''==
La '''''pass transistor logic''''' est une forme particulière de technologie CMOS, une version non-conventionnelle. Avec le CMOS normal, la porte de base est la porte NON. En modifiant celle-ci, on arrive à fabriquer des portes NAND, NOR, puis les autres portes logiques. Les transistors sont conçus de manière à connecter la sortie, soit la tension d'alimentation, soit la masse. Avec la ''pass transistor logic'', le montage de base est un circuit interrupteur, qui connecte l'entrée directement sur la sortie. Le circuit interrupteur n'est autre que les portes à transmission vues il y a quelques chapitres.
La ''pass transistor logic'' a été utilisée dans des processeurs commerciaux, comme dans l'ARM1, le premier processeur ARM. Sur l'ARM1, les concepteurs ont décidé d'implémenter certains circuits avec des multiplexeurs. La raison n'est pas une question de performance ou d'économie de transistors, juste que c'était plus pratique à fabriquer, sachant que le processeur était le premier CPU ARM de l'entreprise.
S'il est intéressant de voir la ''pass transistor logic'', c'est qu'elle est souvent utilisée pour simplifier certains circuits CMOS normaux. Par exemple, il est possible d'implémenter toutes les portes logiques en CMOS normal, sauf la porte XOR qui est implémentée avec la ''pass transistor logic''. Cela permet une petite économie de circuits, vu que la porte XOR est bien plus simple en ''pass transistor logic''. La ''pass transistor logic'' est aussi utilisée pour simplifier les multiplexeurs et les démultiplexeurs, et certains additionneurs. Aussi, ne soyez pas étonné si nous revenons sur certains circuits vus dans les chapitres précédents, dans cette section.
===La porte à transmission===
Le circuit de base est une porte logique que nous n'avons pas encore vu pour le moment, appelée la '''porte à transmission'''. Elle agit comme un interrupteur commandé par une entrée de commande. Pour rappel, un interrupteur fermé laisse passer le courant, alors qu'un interrupteur fermé ne le laisse pas passer. La porte à transmission fait pareil : soit elle connecte l'entrée et la sortie, soit elle les déconnecte. Pour choisir entre les deux, une porte à transmission possède une entrée de commande sur laquelle on envoie un bit de commande. La porte est fermée si le bit de commande est à 1, ouvert s'il est à 1.
[[File:Tristate buffer corrected.svg|centre|vignette|upright=2|Porte à transmission.]]
Il est possible de la voir comme une porte OUI améliorée dont la table de vérité est celle-ci :
{|class="wikitable"
|-
!Commande!!Entrée!!Sortie
|-
||0||0||Déconnexion
|-
||0||1||Déconnexion
|-
||1||0||0
|-
||1||1||1
|}
Intuitivement, on se dit qu'une porte à transmission est faite avec un seul, vu qu'un transistor fonctionne déjà comme un interrupteur commandable. Mais une porte à transmission est construite avec deux transistors. La raison la plus intuitive est que la logique CMOS associe toujours un transistor PMOS à un transistor NMOS. Mais une autre raison, plus importante, est que les transistors NMOS et PMOS ne sont pas des interrupteurs parfaits. Les NMOS laissent passer les 0, mais laissent mal passer les 1 : la tension en sortie, pour un 1, est atténuée. Et c'est l'inverse pour les PMOS, qui laissent bien passer les 1 mais fournissent une tension de sortie peut adéquate pour les 0. Donc, deux transistors permettent d'obtenir une tension de sortie convenable.
Le montage de base est illustré ci-dessous. Les deux entrées A et /A sont l'inverse l'une de l'autre, ce qui fait qu'il faut en théorie rajouter une porte NON CMOS normale, pour obtenir le circuit complet. Mais dans les faits, on arrive souvent à s'en passer. Ce qui fait que la porte à transmission est définie comme étant le circuit à deux transistors précédents.
[[File:CMOS transmission gate.PNG|centre|vignette|upright=1|CMOS Transmission gate]]
Une porte logique en logique CMOS connecte directement sa sortie sur la tension d'alimentation ou la masse. Mais dans une porte logique en ''pass transistor logic'', il n'y a ni tension d'alimentation, ni masse (O Volts). La sortie est connectée sur l'entrée, rien de plus. Et cela explique plusieurs différences entre CMOS et ''pass transistor logic''.
La première différence est que certaines portes logiques sont impossibles avec la ''pass transistor logic'' pure. Les portes logiques CMOS peuvent générer un 1 ou un 0 distinct de ce qu'il y a sur leur entrée. Par exemple, elles peuvent sortir un 1 même si toutes leurs entrées sont à 0, car elles reliées à la tension d'alimentation. Les portes à transmission ne peuvent pas le faire. Elles se contentent de recopier une entrée sur leur sortie : impossible d'avoir un 1 en sortie avec uniquement des zéros en entrée. La conséquence est qu'il n'est pas possible de créer de porte NON, ni de porte NOR/NAND directement.
Une autre différence est que l’électricité est fournie par l'entrée, ce qui fait qu'elle se dissipe un peu lors du passage dans une porte à transmission. Le résultat est que si on enchaine les portes à transmission, la tension de sortie a tendance à diminuer, et ce d'autant plus vite qu'on a enchainé de portes à transmission. Il faut souvent rajouter des portes OUI pour restaurer les tensions adéquates, à divers endroits du circuit. La ''pass transistor logic'' mélange donc porte OUI/NON CMOS normales avec des portes à transmission. Afin de faire des économies de circuit, on utilise parfois une seule porte NON CMOS comme amplificateur, ce qui fait que de nombreux signaux sont inversés dans les circuits, sans que cela ne change grand chose si le circuit est bien conçu.
Par contre, ce défaut entraine aussi des avantages. Notamment, la consommation d'énergie est fortement diminuée. Seules les portes amplificatrices, les portes NON CMOS, sont alimentées en tension/courant. Le reste des circuits n'est pas alimenté, car il n'y a pas de connexion à la tension d'alimentation et la masse. De même, la ''pass transistor logic'' utilise généralement moins de transistors pour implémenter une porte logique, et un circuit électronique en général. L'exemple avec la porte XOR est assez parlant : on passe de 12 à 6 transistors par porte XOR. Des circuits riches en portes XOR, comme les circuits additionneurs, gagnent beaucoup à utiliser des portes à transmission.
===Les multiplexeurs en ''pass transistor logic''===
Les portes à transmission sont très utilisées dans les multiplexeurs et les démultiplexeurs. Prenons l'exemple d'un multiplexeur 2 vers 1. L'idée est de relier chaque entrée à la sortie par l'intermédiaire d'une porte à transmission. Quand l'une sera ouverte, l'autre sera fermée. Le résultat n'utilise que deux portes à transmission et une porte NON. Voici le circuit qui en découle :
[[File:Multiplexeur fabriqué avec des portes à transmission et-ou des tampons trois-états.png|centre|vignette|upright=1.5|Multiplexeur fabriqué avec des portes à transmission]]
En utilisant les portes à transmission CMOS vues plus haut, on obtient le circuit suivant :
[[File:Multiplexeur fabriqué avec des portes à transmission.png|centre|vignette|upright=1.5|Multiplexeur fabriqué avec des portes à transmission CMOS.]]
La même méthode fonctionne pour les multiplexeurs avec plus de deux entrées. Pour rappel, un multiplexeur est composé d'un décodeur qui commande une couche de portes ET, les sorties des portes ET sont combinées avec une porte OU.
[[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]]
Il est possible de remplacer les portes ET par des portes à transmission. L'idée est de ne connecter sur la sortie que l'entrée qui a été sélectionnée et de déconnecter les autres. En faisant ainsi, on peut se passer de la porte OU, qui est remplacée par un simple fil. Il n'y a qu'une seule entrée qui est connectée à la sortie à chaque instant, pas besoin d'utiliser de porte OU. Le résultat est le circuit suivant :
[[File:Mux Funktionsprinzip.svg|centre|vignette|upright=1.5|Multiplexeur basé sur des interrupteurs.]]
Les multiplexeurs en ''pass transistor logic'' sont plus simple que leurs cousins en CMOS normal. Beaucoup de circuits utilisent des multiplexeurs et nous en avons déjà vu pas mal : les circuits de décalage, les bascules, les additionneurs, quelques autres. Comment se comportent-ils si leurs MUX sont implémentés avec la ''pass transistor logic'' ? La réponse est que l'usage de la ''pass transistor logic'' ne change pas la donne pour les circuits de décalage, alors qu'elle change drastiquement la donne pour les bascules et les additionneurs. Voyons cela dans le détail.
===Les bascules D avec des portes à transmission===
[[File:Multiplexer-based latch (positive).svg|class=transparent|right|Bascule D créée avec un multiplexeur.]]
Une bascule D est, pour rappel, un circuit qui mémorise un bit. Elle peut être implémenté avec un multiplexeur 2 vers 1, en bouclant la sortie du multiplexeur sur une entrée. Pour un multiplexeur fabriqué avec des portes CMOS, boucler sa sortie sur son entrée ne pose pas de problème. Mais avec des portes à transmission, le circuit ne fonctionne pas. Le problème est qu'une porte à transmission est électriquement équivalente à un simple interrupteur, ce qui réduit le circuit à une boucle entre un interrupteur et un fil. Le courant qui circule dans le fil et l'interrupteur se dissipe rapidement du fait de la résistance du fil et disparait en quelques micro- ou millisecondes.
La solution est de rajouter une porte OUI (celle qui recopie son entrée sur sa sortie) dans la boucle pour régénérer le signal électrique. Et la manière la plus simple de fabriquer une porte OUI utilise deux portes NON qui se suivent, ce qui donne le circuit ci-dessous. Cela garantit que la boucle est alimentée en courant/tension quand elle est fermée. Son contenu ne s'efface pas avec le temps, mais est automatiquement régénéré par les portes NON. L'ensemble sera stable tant que la boucle est fermée.
[[File:Implémentation conceptuelle d'une bascule D.png|centre|vignette|upright=2|Implémentation d'une bascule D avec des portes à transmission.]]
Le circuit précédent utilise seulement 10 transistors, alors qu'un multiplexeur en CMOS normal en utilise 14. Un autre avantage est que ce circuit permet d'avoir les deux sorties Q : la sortie Q inversée est prise en sortie de la première porte NON. Une variante du circuit précédent est utilisée dans les mémoires dites SRAM, qui sont utilisées pour les registres du processeur ou ses mémoires caches. Mais nous verrons cela plus en détail dans le chapitre sur les cellules mémoires.
Certaines bascules D ont une entrée R, qui met à zéro le bit mémorisé dans la bascule quand l'entrée R est à 1. Pour cela, elles ajoutent un circuit de mise à zéro, que nous avons déjà vu dans le chapitre sur les opérations bit à bit. Ce circuit de mise à zéro est placé après la seconde porte NON, et sa sortie est bouclée sur l'entrée du circuit. Le circuit obtenu est le suivant :
[[File:Bascule D avec entrée Reset.png|centre|vignette|upright=2|Bascule D avec entrée Reset]]
Le circuit peut se simplement fortement en fusionnant les trois portes situées entre les deux sorties Q, à savoir la porte ET et les deux portes NON qui la précédent. La loi de De Morgan nous dit que l'ensemble est équivalent à une porte NOR, ce qui donne le circuit suivant :
[[File:Bascule D avec entrée Reset, simplifiée.png|centre|vignette|upright=2|Bascule D avec entrée Reset, simplifiée]]
===L'additionneur ''Manchester Carry Chain''===
Les portes à transmission étaient autrefois utilisées pour simplifier les additionneurs, et plus précisément les additionneurs à propagation de retenue. Pour rappel, un additionneur à propagation de retenue additionne deux opérandes, bit par bit. Elle additionne les deux bits de poids faible, ce qui donne un bit de résultat et un bit de retenue. Le bit de retenue est alors envoyé à la colonne suivante, où deux bits sont additionnés avec la retenue, et ainsi de suite. De tels circuits sont composées en enchainant des additionneurs complets, des circuits qui additionnent trois bits : deux bits d'opérandes et une retenue.
[[File:Ripplecarryadder.png|centre|vignette|upright=2.5|Additionneur à propagation de retenue.]]
Un défaut des additionneurs à propagation de retenue est leur lenteur. Le résultat n'est connu qu'une fois que les retenues ont été propagées d'une colonne à l'autre. Et cette propagation est assez lente. Les additionneurs modernes utilisent des techniques très complexes pour résoudre ce problème, comme nous l'avons vu il y a quelques chapitres. Mais ces solutions utilisent beaucoup plus de transistors. De nombreux processeurs comme le 8086 d'Intel, ou d'autres processeurs 8-16 bits de cette époque, ne pouvaient pas se le permettre. À la place, ils utilisaient une optimisation à base de portes à transmission.
L'optimisation en question s'appelle la ''Manchester Carry Chain''. Elle utilise des additionneurs complets construits avec un multiplexeur, comme illustré ci-dessous,. Nous avions vu ce circuit dans le chapitre sur les additionneurs. Avec la ''Manchester Carry Chain'', le multiplexeur est implémenté avec des portes à transmission, comme illustré ci-dessous. L'avantage est que la propagation de la retenue est beaucoup plus rapide. Pas besoin de traverser des portes logiques, la propagation de la retenue ne rencontre pas d'obstacle, si ce n'est la résistance des fils, elle ne subit pas de délai lié au temps de propagation des portes logiques.
{|
|[[File:Additionneur complet basé sur un MUX.png|centre|vignette|upright=1|Additionneur complet basé sur un MUX]]
|[[File:Manchester carry chain.png|centre|vignette|upright=1|Manchester carry chain]]
|}
Cependant, l'usage de portes à transmission a quelques défauts. Le principal est que, vu que la retenue d'entrée est envoyée sur la sortie à travers des interrupteurs, la tension sur la retenue de sortie est plus faible que la tension de la retenue d'entrée. Ce qui pose des problèmes quand on doit enchainer plusieurs additionneurs de ce type, mais laissons cela pour plus tard. Il existe une version de cet additionneur en logique dynamique, où les portes à transmission sont utilisées comme des condensateurs, mais nous n'en parlerons pas ici.
===Les portes logiques construites avec des multiplexeurs 2 vers 1===
La ''pass transistor logic'' est rarement utilisée, à une exception de taille : la porte XOR. Pour rappel, une porte XOR est une sorte d'inverseur commandable, à savoir un circuit prend un bit d'entrée A, et l'inverse ou non suivant la valeur d'un bit de commande B. Et cela nous dit comment implémenter une porte XOR avec un multiplexeur. Un multiplexeur choisit sa sortie parmi deux entrées : A et <math>\overline{A}</math>, le second bit B est envoyé sur l'entrée de commande ! Le circuit obtenu, est celui-ci :
[[File:XOR implémenté avec un MUX.png|centre|vignette|upright=2.5|XOR implémenté avec un MUX.]]
Il est possible de simplifier le circuit en rusant un peu, ce qui donne le circuit ci-dessous. Comme vous pouvez les voir, il mélange porte à transmission et portes NON CMOS normales.
[[File:CmosXORGate.svg|centre|vignette|upright=1|XOR en ''pass transistor logic'']]
Dans les deux cas, l'économie en transistors est drastique comparé au CMOS normal. Plus haut, nous avons illustré plusieurs versions possibles d'une porte XOR en CMOS normal, toutes de 12 transistors. Avec ''pass transistor logic'', une porte XOR utilise 4 à 8 transistors. Le gain est clairement significatif, suffisamment pour utiliser la ''pass transistor logic'' pour la porte XOR, quitte à utiliser des portes CMOS normales pour le reste. Quelques processeurs faisaient cela dans le temps, comme le mythique processeur Z80.
La ''pass transistor logic'' implémente les autres portes logiques avec un multiplexeur 2 vers 1 couplé à quelques portes NON ! Et intuitivement, vous vous dites que les deux entrées de la porte logique correspondent aux deux entrées de donnée du multiplexeur. Sauf qu'en réalité, un bit d'entrée est envoyé sur l'entrée de commande, et l'autre bit sur une entrée de donnée du multiplexeur. Suivant ce qu'on met sur la seconde entrée du multiplexeur, on obtient une porte ET, OU, XOR, etc. Il y a quatre choix possibles : soit on envoie un 0, soit un 1, soit l'inverse du bit d'entrée, soit envoyer deux fois le bit d'entrée.
: Plus haut, nous avions dit que les portes à transmission ne permettaient pas d'implémenter certaines portes logiques, car elles recopient leur entrée sur leur sortie. Impossible d'avoir un 1 en sortie si les entrées valent 0. Mais remarquez que les circuits précédents utilisent les portes NON. Ce sont ces portes NON qui fournissent l'électricité en sortie nécessaire pour avoir un 1 en sortie alors que les entrées sont à 0.
[[File:Portes logiques faites à partir de multiplexeurs.png|centre|vignette|upright=2|Portes logiques faites à partir de multiplexeurs]]
==Les technologies PMOS et NMOS==
Dans ce qui va suivre, nous allons voir la technologie NMOS et POMS. Pour simplifier, la technologie NMOS est équivalente aux circuits CMOS, sauf que les transistors PMOS sont remplacés par une résistance. Pareil avec la technologie PMOS, sauf que c'est les transistors NMOS qui sont remplacés par une résistance. Les deux technologies étaient utilisées avant l'invention de la technologie CMOS, quand on ne savait pas comment faire pour avoir à la fois des transistors PMOS et NMOS sur la même puce électronique, mais sont aujourd'hui révolues. Nous en parlons ici, car nous évoquerons quelques circuits en PMOS/NMOS dans le chapitre sur les cellules mémoire, mais vous pouvez considérer que cette section est facultative.
===Le fonctionnement des logiques NMOS et PMOS===
Avec la technologie NMOS, les portes logiques sont fabriqués avec des transistors NMOS intercalés avec une résistance.
[[File:Circuit en logique NMOS.png|centre|vignette|upright=2|Circuit en logique NMOS.]]
Leur fonctionnement est assez facile à expliquer. Quand la sortie doit être à 1, tous les transistors sont ouverts. La sortie est connectée à la tension d'alimentation et déconnectée de la masse, ce qui fait qu'elle est mise à 1. La résistance est là pour éviter que le courant qui arrive dans la sortie soit trop fort. Quand au moins un transistor NMOS qui se ferme, il connecte l'alimentation à la masse, les choses changent. Les lois compliquées de l'électricité nous disent alors que la sortie est connectée à la masse, elle est donc mise à 0.
[[File:Fonctionnement d'un circuit en technologie NMOS.png|centre|vignette|upright=2|Fonctionnement d'un circuit en technologie NMOS.]]
Les circuits PMOS sont construits d'une manière assez similaire aux circuits CMOS, si ce n'est que les transistors NMOS sont remplacés par une résistance qui relie ici la masse à la sortie. Rien d'étonnant à cela, les deux types de transistors, PMOS et NMOS, ayant un fonctionnement inverse.
===Les portes logiques en NMOS et PMOS===
Que ce soit en logique PMOS ou NMOS, les portes de base sont les portes NON, NAND et NOR. Les autres portes sont fabriquées en combinant des portes de base. Voici les circuits obtenus en NMOS et PMOS:
{|class="wikitable flexible"
|-
! colspan="5 | NMOS
|-
| class="transparent" | [[File:NMOS NOT.svg|class=transparent|100px|Porte NON NMOS.]]
| class="transparent" | [[File:NMOS NAND.svg|class=transparent|100px|NMOS-NAND]]
| class="transparent" | [[File:NMOS NOR.png|100px|NMOS-NOR]]
| class="transparent" | [[File:NMOS AND gate.png|100px|NMOS AND]]
| class="transparent" | [[File:NMOS OR gate.png|100px|NMOS OR]]
|-
! colspan="5 | PMOS
|-
| class="transparent" | [[File:PMOS NOT.png|100px|PMOS NOT]]
| class="transparent" | [[File:PMOS NAND corr.png|100px|PMOS NAND]]
| class="transparent" | [[File:PMOS NOR corr.png|100px|PMOS NOR]]
| class="transparent" |
| class="transparent" | [[File:PMOS OR gate.png|100px|PMOS OR]]
|}
====Les portes logiques de base en NMOS====
Le circuit d'une porte NON en technologie NMOS est illustré ci-dessous. Le principe de ce circuit est similaire au CMOS, avec quelques petites différences. Si on envoie un 0 sur la grille du transistor, il s'ouvre et connecte la sortie à la tension d'alimentation à travers la résistance. À l'inverse, quand on met un 1 sur la grille, le transistor se ferme et la sortie est reliée à la masse, donc mise à 0. Le résultat est bien un circuit inverseur.
{|class="wikitable flexible"
|[[File:NMOS NOT.svg|class=transparent|Porte NON NMOS.]]
|[[File:Not.PNG|class=transparent|Porte NON NMOS : fonctionnement.]]
|}
La porte NOR est similaire à la porte NON, si ce n'est qu'il y a maintenant deux transistors en parallèle. Si l'une des grilles est mise à 1, son transistor se fermera et la sortie sera mise à 0. Par contre, quand les deux entrées sont à 0, les transistors sont tous les deux ouverts, et la sortie est mise à 1. Le comportement obtenu est bien celui d'une porte NOR.
{|class="wikitable flexible"
|[[File:NMOS NOR.png|NMOS-NOR-gate]]
|[[File:Funktionsprinzip eines NOR-Gatters.png|class=transparent|Fonctionnement d'une porte NOR NMOS.]]
|}
La porte NAND fonctionne sur un principe similaire au précédent, si ce n'est qu'il faut que les deux grilles soient à zéro pour obtenir une sortie à 1. Pour mettre la sortie à 0 quand seulement les deux transistors sont ouverts, il suffit de les mettre en série, comme dans le schéma ci-dessous. Le circuit obtenu est bien une porte NAND.
{|class="wikitable flexible"
|[[File:NMOS NAND.svg|class=transparent|centre|NMOS-NAND-gate]]
|[[File:Funktionsprinzip eines NAND-Gatters.png|class=transparent|centre|Funktionsprinzip eines NAND-Gatters]]
|}
===Les avantages et inconvénients des technologies CMOS, PMOS et NMOS===
La technologie PMOS et NMOS ne sont pas totalement équivalentes, niveau performances. Ces technologies se distinguent sur plusieurs points : la vitesse des transistors et leur consommation énergétique.
La vitesse des circuits NMOS/PMOS/CMOS dépend des transistors eux-mêmes. Les transistors PMOS sont plus lents que les transistors NMOS, ce qui fait que les circuits NMOS sont plus rapides que les circuits PMOS. Les circuits CMOS ont une vitesse intermédiaire, car ils contiennent à la fois des transistors NMOS et PMOS.
Pour la consommation électrique, les résistances sont plus goumandes que les transistors. En PMOS et NMOS, la résistance est traversée par du courant en permanence, peu importe l'état des transistors. Et résistance traversée par du courant signifie consommation d'énergie, dissipée sous forme de chaleur par la résistance. Il s'agit d'une perte sèche d'énergie, une consommation d'énergie inutile. En CMOS, l'absence de résistance fait que la consommation d'énergie est liée aux transistors, et celle-ci est beaucoup plus faible que pour une résistance.
Les transistors PMOS sont plus simples à fabriquer que les NMOS, ils sont plus simples à sortir d'usine. Les premiers processeurs étaient fabriqués en logique PMOS, plus simple à fabriquer. Puis, une fois la fabrication des circuits NMOS maitrisée, les processeurs sont tous passés en logique NMOS du fait de sa rapidité. La logique CMOS a mis du temps à remplacer les logiques PMOS et NMOS, car il a fallu maitriser les techniques pour mettre à la fois des transistors NMOS et PMOS sur la même puce. Les premières puces électroniques étaient fabriquées en PMOS ou en NMOS, parce qu'on n’avait pas le choix. Mais une fois la technologie CMOS maitrisée, elle s'est imposée en raison de deux gros avantages : une meilleure fiabilité (une meilleure tolérance au bruit électrique), et une consommation électrique plus faible.
==La logique dynamique MOS==
La '''logique dynamique''' permet de créer des portes logiques ou des bascules d'une manière assez intéressante. Et aussi étonnant que cela puisse paraître, le signal d’horloge est alors utilisé pour fabriquer des circuits combinatoires !
===Un transistor MOS peut servir de condensateur===
Les technologies CMOS conventionnelles mettent la sortie d'une porte logique à 0/1 en la connectant à la tension d'alimentation ou à la masse. La logique ''pass transistor'' transfère la tension et le courant de l'entrée vers la sortie. Dans les deux cas, la sortie est connectée directement ou indirectement à la tension d'alimentation quand on veut lui faire sortie un 1. Avec la logique dynamique, ce n'est pas le cas. La sortie est maintenue à 0 ou à 1 en utilisant un réservoir d'électron qui remplace la tension d'alimentation.
En électronique, il existe un composant qui sert de réservoir à électricité : il s'agit du '''condensateur'''. On peut le charger en électricité, ou le vider pour fournir un courant durant une petite durée de temps. Par convention, un condensateur stocke un 1 s'il est rempli, un 0 s'il est vide. L'intérieur d'un condensateur est formé de deux couches de métal conducteur, séparées par un isolant électrique. Les deux plaques de conducteur sont appelées les armatures du condensateur. C'est sur celles-ci que les charges électriques s'accumulent lors de la charge/décharge d'un condensateur. L'isolant empêche la fuite des charges d'une armature à l'autre, ce qui permet au condensateur de fonctionner comme un réservoir, et non comme un simple fil.
Il est possible de fabriquer un pseudo-condensateur avec un transistor MOS. En effet, tout transistor MOS a un pseudo-condensateur caché entre la grille et la liaison source-drain. Pour comprendre ce qui se passe dans ce transistor de mémorisation, il faut savoir ce qu'il y a dans un transistor CMOS. À l'intérieur, on trouve une plaque en métal appelée l'armature, un bout de semi-conducteur entre la source et le drain, et un morceau d'isolant entre les deux. L'ensemble forme donc un condensateur, certes imparfait, qui porte le nom de capacité parasite du transistor. Suivant la tension qu'on envoie sur la grille, l'armature va se remplir d’électrons ou se vider, ce qui permet de stocker un bit : une grille pleine compte pour un 1, une grille vide compte pour un 0.
[[File:Transistor CMOS - 1.png|centre|vignette|upright=2|Anatomie d'un transistor CMOS]]
L'utilisation de transistors MOS comme condensateur n'est pas spécifique à la logique dynamique. Certains mémoires RAM le font, comme nous le verrons dans le chapitre sur les cellules mémoires. Aussi, il est intéressant d'en parler maintenant, histoire de préparer le terrain. D'ailleurs, les mémoires RAM sont remplies de logique dynamique.
===L'utilisation des pseudo-condensateurs en logique dynamique===
Un circuit conçu en logique dynamique contient un transistor est utilisé comme condensateur. Il s’insère entre la tension d'alimentation et la sortie du circuit. Son rôle est simple : lorsqu'on utilise la sortie, le condensateur se vide, ce qui place la sortie à 1. le reste du temps, le condensateur est relié à la tension d'alimentation et se charge. Un circuit en logique dynamique effectue son travail en deux phases : une phase d'inactivité où il remplit ses condensateurs, et une phase où sa sortie fonctionne. Les deux phases sont appelées la '''phase de précharge''' et la '''phase d'évaluation'''. La succession de ces deux phases est réalisée par le signal d'horloge : la première phase a lieu quand le signal d'horloge est à 1, l'autre quand il est à 0.
Voici un exemple de porte NAND en logique dynamique MOS. La porte est alors réalisée avec des transistors NMOS et PMOS, le circuit ressemble à ce qu'on a en logique NMOS. En bas, on trouve les transistors NMOS pour relier la sortie au 0 volt. Mais au-dessus, on trouve un transistor CMOS qui remplace la résistance. Le fonctionnement du circuit est simple. Quand l'entrée ''clock'' est à 1, le condensateur se charge, les deux transistors NMOS sont déconnectés de la masse et le circuit est inactif. Puis, quand ''clock'' passe à 0, Le transistor PMOS se comporte en circuit ouvert, ce qui déconnecte la tension d'alimentation. Et son pseudo-condensateur se vide, ce qui fournit une tension d'alimentation de remplacement temporaire. Le transistor NMOS du bas se ferme, ce qui fait que les deux transistors A et B décident de si la sortie est connectée au 0 volt ou non. Si c'est le cas, le pseudo-condensateur se vide dans le 0 volt et la sortie est à 0. Sinon, le pseudo-condensateur se vide dans la sortie, ce qui la met à 1.
[[File:Dlnand.svg|centre|vignette|Porte NAND en logique CMOS.]]
Il est aussi possible de créer une bascule D en utilisant la logique dynamique. L'idée est de prendre une bascule D normale, mais d'ajouter un fonctionnement en deux étapes en ajoutant des transistors/interrupteurs. Pour rappel, une bascule D normale est composée de deux inverseurs reliés l'un à l'autre en formant une boucle, avec un multiplexeur pour permettre les écritures dans la boucle.
[[File:Implémentation conceptuelle d'une bascule D.png|centre|vignette|upright=2|Implémentation conceptuelle d'une bascule D]]
[[File:Animation du fonctionnement d'une bascule.gif|vignette|upright=2|Animation du fonctionnement de la bascule précédente.]]
Le circuit final ajoute deux transistors entre les inverseurs tête-bêche. Les transistors en question sont reliés à l'horloge, l'un étant ouvert quand l'autre est fermé. Grâce à eux, le bit mémorisé circule d'un inverseur à l'autre : il est dans le premier inverseur quand le signal d'horloge est à 1, dans l'autre inverseur quand il est à 0 (en fait son inverse, comme vous l'aurez compris). Le tout est illustré ci-contre. Cette implémentation a été utilisée autrefois, notamment dans le processeur Intel 8086.
[[File:Bascule D en logique Dynamique, avec entrée Enable.png|centre|vignette|upright=2|Bascule D en logique Dynamique, avec entrée Enable]]
Il existe une variante très utilisée, qui permet de remplacer le multiplexeur par un circuit légèrement plus simple. Avec elle, on a deux entrées pour commander la bascule, et non une seule entrée Enable. L'entrée Enable autorise les écriture, l'entrée Hold ferme la boucle qui relie la sortie du second inverseur au premier. Chaque entrée est associé à un transistor/interrupteur. Le transistor sur lequel on envoie l'entrée Enable se ferme uniquement lors des écritures et reste fermé sinon. À l'inverse, le transistor relié au signal Hold est fermé en permanence, sauf lors des écritures. En clair, les deux signaux sont l'inverse l'un de l'autre. Il permet de fermer le circuit, de bien relier les deux inverseurs en tête-bêche, sauf lors des écritures. On envoie donc l'inverse de l'entrée Enable sur ce transistor.
[[File:Bascule D en logique dynamique.png|centre|vignette|upright=2|Bascule D en logique dynamique]]
Une manière de comprendre le circuit précédent est de le comparer à celui avec le multiplexeur. Le multiplexeur est composé d'une porte NON et de deux transistors. Il se trouve que les deux transistors en question sont placés au même endroit que les transistors connectés aux signaux Hold et Enable. En prenant retirant la porte NON du multiplexeur, on se retrouve avec le circuit. Au lieu de prendre un Signal Enable qui commande les deux transistors, ce qui demande d'ajouter une porte NON vu que les deux transistors doivent faire l'inverse l'un de l'autre, on se contente d'envoyer deux signaux séparés pour commander chaque transistor indépendamment.
Des circuits nettement plus complexes peuvent être créés en logique dynamique. Pour les curieux, sachez que le ''barrel shifter'' du processeur Intel 386 était construit en logique dynamique. Le ''barrel shifter'' en question était un circuit capable de décaler des opérandes de 32 bits, ce qui était beaucoup pour l'époque. Il faisait environ 2000 transistors, ce qui correspondait à environ la moitié des transistors du CPU 6502 de Motorola, un CPU 8 bits. Et il aurait sans doute fait plus s'il n'avait pas utilisé la logique dynamique, ainsi que diverses optimisations. L'article suivant, assez complet, détaille ce circuit : [https://www.righto.com/2023/12/386-barrel-shifter.html Reverse engineering the barrel shifter circuit on the Intel 386 processor die ]
===Avantages et inconvénients===
Les circuits en logique dynamique sont opposés aux circuits en logique statique, ces derniers étant les circuits CMOS, PMOS, NMOS ou TTL vu jusqu'à présent. Les circuits dynamiques et statiques ont des différences notables, ainsi que des avantages et inconvénients divers. Si on devait résumer :
* la logique dynamique utilise généralement un peu plus de transistors qu'un circuit CMOS normal ;
* la logique dynamique est souvent très rapide par rapport à la concurrence, car elle n'utilise que des transistors NMOS, plus rapides ;
* la consommation d'énergie est généralement supérieure comparé au CMOS.
Un désavantage de la logique dynamique est qu'elle utilise plus de transistors. On économise certes des transistors MOS, mais il faut rajouter les transistors pour déconnecter les transistors NMOS de la masse (0 volt). Le second surcompense le premier.
Un autre désavantage est que le signal d'horloge ne doit pas tomber en-dessous d'une fréquence minimale. Avec une logique statique, on a une fréquence maximale, mais pas de fréquence minimale. Avec un circuit statique peut réduire la fréquence d'un circuit pour économiser de l'énergie, pour améliorer sa stabilité, et de nombreux processeurs modernes ne s'en privent pas. On peut même stopper le signal d'horloge et figer le circuit, ce qui permet de le mettre en veille, d'en stopper le fonctionnement, etc. Impossible avec la logique dynamique, qui demande de ne pas tomber sous la fréquence minimale. Cela a un impact sur la consommation d'énergie, sans compter que cela se marie assez mal avec certaines applications. Un processeur moderne ne peut pas être totalement fabriqué en logique dynamique, car il a besoin d'être mis en veille et qu'il a besoin de varier sa fréquence en fonction des besoins.
Le dernier désavantage implique l'arbre d'horloge, le système d'interconnexion qui distribue le signal d'horloge à toutes les bascules d'un circuit. L'arbre d'horloge est beaucoup plus compliqué avec la logique dynamique qu'avec la logique statique. Avec la logique statique, seules les bascules doivent recevoir le signal d'horloge, avec éventuellement quelques rares circuits annexes. Mais avec la logique dynamique, toutes les portes logiques doivent recevoir le signal d'horloge, ce qui rend la distribution de l'hrologe beaucoup plus compliquée. C'est un point qui fait que la logique dynamique est assez peu utilisée, et souvent limitée à quelques portions bien précise d'un processeur.
==La logique TTL : un apercu rapide==
Tous ce que nous avons vu depuis le début de ce chapitre porte sur les transistors MOS et les technologies associées. Mais les transistors MOS n'ont pas été les premiers inventés. Ils ont été précédés par les '''transistors bipolaires'''. Nous ne parlerons pas en détail du fonctionnement d'un transistor bipolaire, car celui-ci est extraordinairement compliqué. Cependant, nous devons parler rapidement de la logique TTL, qui permet de fabriquer des portes logiques avec ces transistors bipolaires. Là encore, rassurez-vous, nous n'allons pas voir comment fabriquer des portes logiques en logique TTL, cela serait trop compliqué, sans compter que le but n'est pas de faire un cours d'électronique. Mais nous devons fait quelques remarques et donner quelques explications superficielles.
La raison à cela est double. La première raison est que certains circuits présents dans les mémoires RAM sont fabriqués avec des transistors bipolaires. C'est notamment le cas des amplificateurs de lecture ou d'autres circuits de ce genre. De tels circuits ne peuvent pas être implémentés facilement avec des transistors CMOS et nous expliquerons rapidement pourquoi dans ce qui suit. La seconde raison est que ce cours parlera occasionnellement de circuits anciens et qu'il faut quelques bases sur le TTL pour en parler.
Dans la suite du cours, nous verrons occasionnellement quelques circuits anciens, pour la raison suivante : ils sont très simples, très pédagogiques, et permettent d'expliquer simplement certains concepts du cours. Rien de mieux que d'étudier des circuits réels pour donner un peu de chair à des explications abstraites. Par exemple, pour expliquer comment fabriquer une unité logique de calcul bit à bit, je pourrais utiliser l'exemple du Motorola MC14500B, un processeur 1 bit qui est justement une unité logique sous stéroïdes. Ou encore, dans le chapitre sur les circuits additionneurs, je parlerais du circuit additionneur présent dans l'Intel 8008 et dans l'Intel 4004, les deux premiers microprocesseurs commerciaux. Malheureusement, malgré leurs aspects pédagogiques indéniables, ces circuits ont le défaut d'être des circuits TTL. Ce qui est intuitif : les circuits les plus simples ont été inventés en premier et utilisent du TTL plus ancien. Beaucoup de ces circuits ont été inventés avant même que le CMOS ou même les transistors MOS existent. D'où le fait que nous devons faire quelques explications mineures sur le TTL.
===Les transistors bipolaires===
Les '''transistors bipolaires''' ressemblent beaucoup aux transistors MOS. Les transistors bipolaires ont trois broches, appelées le collecteur, la base et l'émetteur. Notez que ces trois termes sont différents de ceux utilisés pour les transistors MOS, où on parle de la grille, du drain et de la source.
Là encore, comme pour les transistors PMOS et NMOS, il existe deux types de transistors bipolaires : les NPN et les PNP. Là encore, il est possible de fabriquer une puce en utilisant seulement des NPN, seulement des PNP, ou en mixant les deux. Mais les ressemblances s'arrêtent là. La différence entre PNP et NPN tient dans la manière dont les courants entrent ou sortent du transistor. La flèche des symboles ci-dessous indique si le courant rentre ou sort par l'émetteur : il rentre pour un PNP, sort pour un NPN. Dans la suite du cours, nous n'utiliserons que des transistors NPN, les plus couramment utilisés.
{|
|[[File:BJT PNP symbol.svg|vignette|BJT PNP]]
|[[File:BJT NPN symbol.svg|vignette|BJT NPN]]
|}
Plus haut nous avons dit que les transistors CMOS sont des interrupteurs. La réalité est que tout transistor peut être utilisé de deux manières : soit comme interrupteur, soit comme amplificateur de tension/courant. Pour simplifier, le transistor bipolaire NPN prend en entrée un courant sur sa base et fournit un courant amplifié sur l'émetteur. Pour s'en servir comme amplificateur, il faut fournir une source de courant sur le collecteur. Le fonctionnement exact est cependant plus compliqué.
[[File:Transistor bipolaire, explication simplifiée de son fonctionnement.png|centre|vignette|upright=1.5|Transistor bipolaire, explication simplifiée de son fonctionnement]]
Les transistors bipolaires sont de bons amplificateurs, mais de piètres interrupteurs. À l'inverse, les transistors CMOS sont généralement de bons interrupteurs, mais de moyens amplificateurs. Pour des circuits numériques, la fonction d'interrupteur est clairement plus adaptée, car elle-même binaire (un transistor est fermé ou ouvert : deux choix possibles). Aussi, les circuits modernes privilégient des transistors CMOS aux transistors bipolaires. À l'inverse, la fonction d'amplification est adaptée aux circuits analogiques.
C'est pour ça que nous rencontrerons les transistors bipolaires soit dans des portions de l'ordinateur qui sont au contact de circuits analogiques. Pensez par exemple aux cartes sons ou au vieux écrans cathodiques, qui gèrent des signaux analogiques (le son pour la carte son, les signaux vidéo analogique pour les vieux écrans). On les croisera aussi dans les mémoires DRAM, dont la conception est un mix entre circuits analogiques et numériques. Nous les croiserons aussi dans de vieux circuits antérieurs aux transistors MOS. Les anciens circuits faisaient avec les transistors bipolaires car ils n'avaient pas le choix, mais ils ont été partiellement remplacés dès l'apparition des transistors CMOS.
===Les portes logiques complexes en TTL===
Le détail le plus important qui nous concernera dans la suite du cours est le suivant : on peut créer des portes logiques exceptionnellement complexes en TTL. Pour comprendre pourquoi, sachez qu'il existe des transistors bipolaires qui possèdent plusieurs émetteurs. Ils sont très utilisés pour fabriquer des portes logiques à plusieurs entrées. Les émetteurs correspondent alors à des entrées de la porte logique. Ainsi, une porte logique à plusieurs entrées se fait non pas en ajoutant des transistors, comme c'est le cas avec les transistors MOS, mais en ajoutant un émetteur sur un transistor. Cela permet à une porte NAND à trois entrées de n'utiliser que deux transistors bipolaires, au lieu de quatre transistors MOS.
[[File:Multiemitter Transistor.svg|centre|vignette|upright=1|Transistor bipolaire avec plusieurs émetteurs.]]
De plus, là où les logiques PMOS/NMOS/CMOS permettent de fabriquer les portes de base que nous avons précédemment, elles ne peuvent pas faire plus. Au pire, on peut implémenter des portes ET/OU/NAND/NOR à plusieurs entrées, mais pas plus. En TTL, on peut parfaitement créer des portes de type ET/OU/NON ou OU/ET/NON, avec seulement quatre transistors. Par exemple, une '''porte ET/OU/NON''' de type 2-2 entrées (pour rappel, qui effectue un ET par paire d’entrée puis fait un NOR entre le résultat des deux ET) est bien implémenté en une seule porte logique, pas en enchainant deux ou trois portes à la suite.
[[File:TTL AND-OR-INVERT 1961.png|centre|vignette|upright=2|TTL AND-OR-INVERT 1961]]
===Les désavantages et avantages des circuits TTL===
Pour résumer, le TTL à l'avantage de pouvoir fabriquer des portes logiques avec peu de transistors comparé au CMOS, surtout pour les portes logiques complexes. Et autant vous dire que les concepteurs de puce électroniques ne se gênaient pas pour utiliser ces portes complexes, capables de fusionner 3 à 5 portes en une seule : les économies de transistors étaient conséquentes.
Et pourtant, les circuits TTL étaient beaucoup plus gros que leurs équivalents CMOS. La raison est qu'un transistor bipolaire prend beaucoup de place : il est environ 10 fois plus gros qu'un transistor MOS. Autant dire que les économies réalisées avec des portes logiques complexes ne faisaient que compenser la taille énorme des transistors bipolaires. Et encore, cette compensation n'était que partielle, ce qui fait que les circuits PMOS/NMOS/CMOS se miniaturisent beaucoup plus facilement. Un avantage pour le transistor MOS !
De plus, les schémas précédents montrent que les portes logiques en TTL utilisent une résistance, elle aussi difficile à miniaturiser. Et cette résistance est parcourue en permanence par un courant, ce qui fait qu'elle consomme de l'énergie et chauffe. C'est la même chose en logique NMOS et PMOS, ce qui explique leur forte consommation d'énergie. Les circuits TTL ont donc le même problème.
[[File:TTL Input voltage.svg|vignette|upright=0.5|TTL voltage.]]
Un autre défaut est lié à la une tension d'alimentation. Les circuits TTL utilisent une tension d'alimentation de 5 volts, alors que les circuits CMOS ont une tension d'alimentation beaucoup plus variable. Les circuits CMOS vont de 3 volts à 18 volts pour les circuits commerciaux, avec des tensions de 1 à 3 volts pour les circuits optimisés. Les circuits CMOS sont généralement bien optimisés et utilisent une tension d'alimentation plus basse que les circuits TTL, ce qui fait qu'ils consomment moins d'énergie et de courant.
De plus, rappelons que coder un zéro demande que la tension soit sous un seuil, alors que coder un 1 demande qu'elle dépasse un autre seuil, avec une petite marge de sécurité entre les deux. Les seuils en question sont indiqués dans le diagramme ci-dessous. Il s'agit des seuils VIH et VIL. On voit que sur les circuits TTL, la marge de sécurité est plus faible qu'avec les circuits CMOS. De plus, les marges sont bien équilibrées en CMOS, à savoir que la marge de sécurité est en plein milieu entre la tension max et le zéro volt. Avec le TTL normal, la marge de sécurité est très proche du zéro volt. Un 1 est codé par une tension entre 2 et 5 volts en TTL ! Une version améliorée du TTL, le LVTTL, corrige ce défaut. Elle baisse la tension d'alimentation à 3,3 Volts, mais elle demande des efforts de fabrication conséquents.
[[File:Niveaux logiques CMOS-TTL-LVTTL.png|centre|vignette|upright=2|Niveaux logiques CMOS-TTL-LVTTL]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de calcul trigonométriques
| prevText=Les circuits de calcul trigonométriques
| next=Les circuits intégrés
| nextText=Les circuits intégrés
}}
{{autocat}}
</noinclude>
spz2ccjgdojmsjhlqbuyc1y320dcqcg
Fonctionnement d'un ordinateur/Les bus électroniques
0
72818
768604
768544
2026-06-25T14:57:07Z
Mewtow
31375
/* L'interfaçage avec le bus avec des circuits trois-états */
768604
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 corrected.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>
o3kdv3y22jc0ptr7zxuuo22b9bv2s3k
768678
768604
2026-06-25T20:13:39Z
Mewtow
31375
/* Les différents types d'arbitrage de bus */
768678
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 corrected.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.
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>
i8h4dspn4vziqz2cv0705d9gdw5rk9j
Python pour le calcul scientifique/Découverte de Python et de Jupyter
0
72864
768629
768578
2026-06-25T15:26:54Z
Cdang
1202
/* Vocabulaire */ matrix -> array
768629
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 > 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.array</code>, on dit que « la matrice est une instance de la classe <code>numpy.array</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.array</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.array</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>&''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><</code>, <code>></code>, <code><=</code> et <code>>=</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 > 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 > 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]] < [[../|↑]] > [[../Premiers programmes|Premiers programmes]]
{{DEFAULTSORT:Decouverte de Python et de Jupyter}}
[[Catégorie:Python pour le calcul scientifique (livre)]]
d8aodj5gq5pqdoye397vpi9dr2uyl37
Python pour le calcul scientifique/Manipulation de matrices
0
72889
768614
768595
2026-06-25T15:20:15Z
Cdang
1202
/* Assemblage de matrices */ matrix -> array
768614
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 > 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.array([[1 ,2], [3, 4]])
B = np.array([[5 ,6], [7, 8]])
C = np.concatenate((A, B), axis = 0)
# [[1 2]
# [3 4]
# [5 6]
# [7 8]]
D = 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.array([1 ,2])
B = np.array([3 ,4])
C = 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]] < [[../|↑]] > [[../Polynômes|Polynômes]]
[[Catégorie:Python pour le calcul scientifique (livre)]]
rggmp1xrp6avoqrvph0uzt08w4rdipm
768625
768614
2026-06-25T15:23:16Z
Cdang
1202
/* Opérations matricielles */ matrix -> array
768625
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 > 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.array([[1 ,2], [3, 4]])
B = np.array([[5 ,6], [7, 8]])
C = np.concatenate((A, B), axis = 0)
# [[1 2]
# [3 4]
# [5 6]
# [7 8]]
D = 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.array([1 ,2])
B = np.array([3 ,4])
C = 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.array([[1, 2], [3, 4]])
b = np.array([[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.array([1, 2, 3])
B = np.array([4, 5, 6])
# 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]] < [[../|↑]] > [[../Polynômes|Polynômes]]
[[Catégorie:Python pour le calcul scientifique (livre)]]
8nzw0h5wdbo4830kitlfxapiqam0nqc
768627
768625
2026-06-25T15:25:19Z
Cdang
1202
/* Différence entre array et matrix */
768627
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 > 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'' ==
Il existe deux types d'objet NumPy : les objets ''array'' (tableau, classe <code>ndarray</code>) et les objets ''matrix'' (matrice, classe <code>matrix</code>).
<syntaxhighlight lang="Python">
A = np.array([[1, 2], [3, 4], [5, 6]])
B = np.matrix([[1, 2], [3, 4], [5, 6]])
</syntaxhighlight>
Il y a trois 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>. Nous n'utiliserons donc pas la classe <code>matrix</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.array([[1 ,2], [3, 4]])
B = np.array([[5 ,6], [7, 8]])
C = np.concatenate((A, B), axis = 0)
# [[1 2]
# [3 4]
# [5 6]
# [7 8]]
D = 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.array([1 ,2])
B = np.array([3 ,4])
C = 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.array([[1, 2], [3, 4]])
b = np.array([[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.array([1, 2, 3])
B = np.array([4, 5, 6])
# 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]] < [[../|↑]] > [[../Polynômes|Polynômes]]
[[Catégorie:Python pour le calcul scientifique (livre)]]
rl1yeffldqlpk5v5bzifbxz1imiegxm
768631
768627
2026-06-25T15:28:20Z
Cdang
1202
/* Différence entre array et matrix */ précisions
768631
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 > 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'' ==
Il existe deux types d'objet NumPy pour décrire les matrices : les objets ''array'' (tableau, classe <code>ndarray</code>) et les objets ''matrix'' (matrice, classe <code>matrix</code>).
<syntaxhighlight lang="Python">
A = np.array([[1, 2], [3, 4], [5, 6]])
B = np.matrix([[1, 2], [3, 4], [5, 6]])
</syntaxhighlight>
Il y a trois 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>. Nous n'utiliserons donc pas la classe <code>matrix</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.array([[1 ,2], [3, 4]])
B = np.array([[5 ,6], [7, 8]])
C = np.concatenate((A, B), axis = 0)
# [[1 2]
# [3 4]
# [5 6]
# [7 8]]
D = 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.array([1 ,2])
B = np.array([3 ,4])
C = 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.array([[1, 2], [3, 4]])
b = np.array([[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.array([1, 2, 3])
B = np.array([4, 5, 6])
# 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]] < [[../|↑]] > [[../Polynômes|Polynômes]]
[[Catégorie:Python pour le calcul scientifique (livre)]]
o5f4jdne2455aw3dnwm0pgvgwgefdg9
768704
768631
2026-06-26T08:27:29Z
Cdang
1202
/* Extraction matricielle */ where()
768704
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 > 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'' ==
Il existe deux types d'objet NumPy pour décrire les matrices : les objets ''array'' (tableau, classe <code>ndarray</code>) et les objets ''matrix'' (matrice, classe <code>matrix</code>).
<syntaxhighlight lang="Python">
A = np.array([[1, 2], [3, 4], [5, 6]])
B = np.matrix([[1, 2], [3, 4], [5, 6]])
</syntaxhighlight>
Il y a trois 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>. Nous n'utiliserons donc pas la classe <code>matrix</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>
== Trouver une valeur ==
Pour trouver une valeur, on utilise la fonction <code>where()</code> :
<syntaxhighlight lang="python">
A = np.array([[1, 2], [3, 4]])
print(A)
# [[1 2]
# [3 4]]
B = np.where(A == 3)
print(B)
# (array([1]), array([0])
print(B[0][0], B[1][0])
# 1 0
</syntaxhighlight>
Notez que <code>where()</code> peut servir à d'autres choses.
== 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.array([[1 ,2], [3, 4]])
B = np.array([[5 ,6], [7, 8]])
C = np.concatenate((A, B), axis = 0)
# [[1 2]
# [3 4]
# [5 6]
# [7 8]]
D = 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.array([1 ,2])
B = np.array([3 ,4])
C = 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.array([[1, 2], [3, 4]])
b = np.array([[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.array([1, 2, 3])
B = np.array([4, 5, 6])
# 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]] < [[../|↑]] > [[../Polynômes|Polynômes]]
[[Catégorie:Python pour le calcul scientifique (livre)]]
j2iv5evrio4x6vkljxkpn1g37vpwhzu
768705
768704
2026-06-26T08:35:13Z
Cdang
1202
/* Trouver une valeur */
768705
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 > 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'' ==
Il existe deux types d'objet NumPy pour décrire les matrices : les objets ''array'' (tableau, classe <code>ndarray</code>) et les objets ''matrix'' (matrice, classe <code>matrix</code>).
<syntaxhighlight lang="Python">
A = np.array([[1, 2], [3, 4], [5, 6]])
B = np.matrix([[1, 2], [3, 4], [5, 6]])
</syntaxhighlight>
Il y a trois 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>. Nous n'utiliserons donc pas la classe <code>matrix</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>
== Trouver une valeur ==
Pour trouver une valeur, on utilise la fonction <code>where()</code> :
<syntaxhighlight lang="python">
A = np.array([[1, 2], [3, 4]])
print(A)
# [[1 2]
# [3 4]]
B = np.where(A == 3)
print(B)
# (array([1]), array([0])
print(B[0][0], B[1][0])
# 1 0
</syntaxhighlight>
Notez que <code>where()</code> peut servir à construire une matrice à partir de deux autres matrices selon une condition. Par exemple, pour comparer les éléments deux à deux et sélectionner le plus grand :
<syntaxhighlight lang="python">
A = np.array([[1, 2], [3, 4]])
B = np.array([[0, 1], [4, 5]])
C = np.where(A > B, A, B)
print(C)
# [[1 2]
# [4 5]
</syntaxhighlight>
La syntaxyte générale est <code>C = np.where(condition, A, B)</code> où <condition</code> est une matrice de booléens ; pour un indice (''i'', ''j''), si <code>condition[i, j]</code> est vrai, alors <code>C[i, j]</code> vaut <code>A[i, j]</code>, sinon elle vaut <code>B[i, j]</code>.
== 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.array([[1 ,2], [3, 4]])
B = np.array([[5 ,6], [7, 8]])
C = np.concatenate((A, B), axis = 0)
# [[1 2]
# [3 4]
# [5 6]
# [7 8]]
D = 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.array([1 ,2])
B = np.array([3 ,4])
C = 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.array([[1, 2], [3, 4]])
b = np.array([[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.array([1, 2, 3])
B = np.array([4, 5, 6])
# 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]] < [[../|↑]] > [[../Polynômes|Polynômes]]
[[Catégorie:Python pour le calcul scientifique (livre)]]
6psrdovonyt26cvz579t90xmkiphjic
768706
768705
2026-06-26T08:42:12Z
Cdang
1202
/* Trouver une valeur */
768706
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 > 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'' ==
Il existe deux types d'objet NumPy pour décrire les matrices : les objets ''array'' (tableau, classe <code>ndarray</code>) et les objets ''matrix'' (matrice, classe <code>matrix</code>).
<syntaxhighlight lang="Python">
A = np.array([[1, 2], [3, 4], [5, 6]])
B = np.matrix([[1, 2], [3, 4], [5, 6]])
</syntaxhighlight>
Il y a trois 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>. Nous n'utiliserons donc pas la classe <code>matrix</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>
== Trouver une valeur ==
Pour trouver une valeur, on utilise la fonction <code>where()</code> :
<syntaxhighlight lang="python">
A = np.array([[1, 2], [3, 4]])
print(A)
# [[1 2]
# [3 4]]
B = np.where(A == 3)
print(B)
# (array([1]), array([0])
print(B[0][0], B[1][0])
# 1 0
C = np.where(A == 5)
print(C)
# (array([], dtype=int64), array([], dtype=int64))
print(len(C))
# 0
</syntaxhighlight>
Notez que <code>where()</code> peut servir à construire une matrice à partir de deux autres matrices selon une condition. Par exemple, pour comparer les éléments deux à deux et sélectionner le plus grand :
<syntaxhighlight lang="python">
A = np.array([[1, 2], [3, 4]])
B = np.array([[0, 1], [4, 5]])
C = np.where(A > B, A, B)
print(C)
# [[1 2]
# [4 5]
</syntaxhighlight>
La syntaxyte générale est <code>C = np.where(condition, A, B)</code> où <condition</code> est une matrice de booléens ; pour un indice (''i'', ''j''), si <code>condition[i, j]</code> est vrai, alors <code>C[i, j]</code> vaut <code>A[i, j]</code>, sinon elle vaut <code>B[i, j]</code>.
== 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.array([[1 ,2], [3, 4]])
B = np.array([[5 ,6], [7, 8]])
C = np.concatenate((A, B), axis = 0)
# [[1 2]
# [3 4]
# [5 6]
# [7 8]]
D = 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.array([1 ,2])
B = np.array([3 ,4])
C = 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.array([[1, 2], [3, 4]])
b = np.array([[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.array([1, 2, 3])
B = np.array([4, 5, 6])
# 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]] < [[../|↑]] > [[../Polynômes|Polynômes]]
[[Catégorie:Python pour le calcul scientifique (livre)]]
4yyi3awgkgbp38vdq6q27c771ujzwm9
Python pour le calcul scientifique/Fonctions mathématiques générales
0
73043
768630
767636
2026-06-25T15:27:35Z
Cdang
1202
/* Vecteurs et matrices */ matrix -> array
768630
wikitext
text/x-wiki
Par défaut, Python ne propose que les quatre opérations (<code>+</code>, <code>-</code>, <code>*</code> et <code>/</code>), la division euclidienne (<code>//</code>, et <code>%</code> pour le reste) et l'élévation à la puissance (<code>**</code>)<ref>Voir ''[[../Découverte de Python et de Jupyter#Commandes élémentaires|Découverte de Python et de Jupyter > Commandes élémentaires]]''.</ref>. Le module NumPy fournit les opérations mathématiques avancées. En particulier, il propose des fonctions « universelles » ''(ufunc)'' qui peuvent travailler directement sur des matrices.
== Rappel sur les opérations de base ==
* Addition (''a'' + ''b'') : <code>a + b</code> ;
* soustraction (''a'' – ''b'') : <code>a - b</code> ;
* multiplication (''a'' × ''b'') : <code>a / b</code> ;
* division (''a'' ÷ ''b'') : <code>a / b</code> ;
* division euclidienne (''a'' ÷ ''b'') : <code>quotient = a // b ; reste = a % b</code><br />ou bien <code>[quotient, reste] = divmod(a, b)</code> ;
* élévation à la puissance (''a''<sup> ''b'' </sup>) : <code>a ** b</code> ou bien <code>pow(a, b)</code> ;
* arrondi à l'entier le plus proche de ''a'' : <code>round(a)</code>.
{{note|L'opérateur « + » sert également à la concaténation (de chaînes de caractères, de listes, de n-uplets).}}
<syntaxhighlight lang="python">
print([1, 2] + [, 4]) # [1, 2, 3, 4]
</syntaxhighlight>
== Importer le module Numpy ==
Toutes les fonctions évoquées dans ce chapitre supposent l'import du module NumPy sous le nom {{g|np}} :
<syntaxhighlight lang="python">
# Importer NumPy, sous le nom np
import numpy as np
# Ensuite, le code utilisant les fonctions de np
# ...
</syntaxhighlight>
== Vecteurs et matrices ==
Les vecteurs et matrices sont étudiés plus loin. Pour l'instant, indiquons que pour créer un vecteur, on applique la commande <code>np.array()</code> à une liste :
<syntaxhighlight lang="python">
vecteur = np.array([x, y z])
</syntaxhighlight>
pour le vecteur
: <math>\begin{pmatrix}x & y & z \end{pmatrix}</math> ou bien <math>\begin{pmatrix}x \\ y \\ z \end{pmatrix}</math>.
Une matrice est une liste de lignes, chaque ligne étant une liste de valeurs :
<syntaxhighlight lang="python">
matrice = np.array([[a11, a12], [a21, a22]])
# [ligne 1 ] [ligne 2 ]
</syntaxhighlight>
pour la matrice
: <math>\begin{pmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \end{pmatrix}</math>.
Sauf indication contraire, avec des vecteurs ou matrices, les opérateurs s'appliquent élément par élément, à l'exception des opérateurs <code>*</code>, <code>/</code> et <code>**</code>.
<syntaxhighlight lang="python">
print(np.array([[1, 2], [3, 4]]) + np.array([[5, 6], [7, 8]]))
# [[ 6 8]
# [10 12]]
print(np.cos(np.array([[0, np.pi/2], [np.pi/3, np.pi/4]])))
# [[1.0 0.0]
# [0.5 0.707]]
</syntaxhighlight>
== Fonctions universelles de base ==
On retrouve les quatre opérations mais sous la forme de fonctions de deux variables :
* <code>np.add(x1, x2)</code> : ''x''<sub>1</sub> + ''x''<sub>2</sub> ;
* <code>np.subtract(x1, x2)</code> : ''x''<sub>1</sub> – ''x''<sub>2</sub> ;
* <code>np.multiply(x1, x2)</code> : ''x''<sub>1</sub> × ''x''<sub>2</sub> ;
* <code>np.divide(x1, x2)</code>, <code>np.true_divide(x1, x2)</code> : ''x''<sub>1</sub> ÷ ''x''<sub>2</sub>.
Les variables ''x''<sub>1</sub> et ''x''<sub>2</sub> peuvent être des matrices de même dimension, les opérations sont alors faites élément par élément. Ces fonctions disposent en outre de méthodes, notamment de la méthode <code>.accumulate()</code> qui cumule les opérations, par exemple
<syntaxhighlight lang="python">
np.add.accumulate([1, 2, 3, 4, 5])
# array([ 1, 3, 6, 10, 15])
</syntaxhighlight>
Cette méthode existe d'ailleurs pour toutes les fonctions universelles de deux variables.
Les autres opérations de base sont :
* <code>np.negative(x)</code> : –''x'' ;
* <code>np.reciprocal(x)</code> : 1 / ''x'' ;
* <code>np.mod(x1, x2)</code>, <code>np.remainder(x1, x2)</code> : reste de la division euclidienne ''x''<sub>1</sub> ÷ ''x''<sub>2</sub> ;
* <code>np.absolute(x)</code>, <code>np.abs(x)</code> : |''x''| ;
* <code>np.rint(x)</code> : arrondi à l'entier le plus proche ;
* <code>np.floor(x)</code>, <code>np.ceil(x)</code> : arrondi respectivement à l'entier directement inférieur et supérieur ;
* <code>np.trunc(x)</code> : tronque les décimales, partie entière, E(''x'') ;
* <code>np.sign(x)</code> : signe de ''x'' (+1 ou –1) ;
* <code>np.gcd(x1, x2)</code> : PGCD ;
* <code>np.lcm(x1, x2)</code> : PPCM ;
* <code>np.mean([x1, x2, …, xn])</code> : moyenne des ''x<sub>i</sub>'' ;
* <code>np.average([x1, x2, …, xn], weights=[m1, m2, …, mn])</code> : moyenne des ''x<sub>i</sub>'' pondérée par mes masses ''m<sub>i</sub>''.
Notez que les valeurs renvoyées par <code lang="python">np.rint()</code>, <code lang="python">np.floor()</code>, <code lang="python">np.ceil()</code> et <code lang="python">np.trunc()</code> sont des réels à virgule flottante ''({{lang|en|float}})'' bien que les valeurs soient entières. Pour avoir des nombres de types « entier » ''({{lang|en|integer}})'', il faut utiliser la fonction <code lang="python">int()</code> : <code lang="python">int(np.rint())</code>…
== Autres fonctions universelles ==
* <code>np.power(x1, x2)</code> : ''x''<sub>1</sub><sup>''x''<sub>2</sub></sup> ;
* <code>np.square(x)</code> : ''x''<sup>2</sup> ;
* <code>np.sqrt(x)</code> : <math>\sqrt{x}</math> ;
*: si ''x'' est une matrice de réels, alors le résultat doit être réel, les valeurs négatives renvoient un NaN ; si ''x'' contient au moins un nombre complexe, alors le résultat est complexe, par exemple :
<syntaxhighlight lang="python">
np.sqrt(-1) # NaN
np.sqrt(-1 + 0j) # 0 + 1j
np.sqrt(complex(-1)) # 0 + 1j
</syntaxhighlight>
* <code>np.cbrt(x)</code> : <math>\sqrt[3]{x}</math> ;
* <code>np.exp(x)</code> : exp(''x''), ''e<sup>x</sup>'' ;
* <code>np.exp2(x)</code> : 2<sup>''x''</sup> ;
* <code>np.expm1(x)</code> : exp(''x'') – 1 ;
* <code>np.log(x)</code> : ln(''x''), logarithme népérien ;
* <code>np.log2(x)</code> : logarithme en base 2 ;
* <code>np.log10(x)</code> : log(''x''), logarithme en base 10 ;
* <code>np.log1p(x)</code> : ln(1 + ''x'') ;
* <code>np.conj(z)</code> : {{surligner|''z''}}, conjugué complexe ;
* <code>np.heavyside(x1, x2)</code> : fonction marche, vaut 0 si ''x''<sub>1</sub> < 0, 1 si ''x''<sub>1</sub> > 0, et ''x''<sub>2</sub> si ''x''<sub>1</sub> = 0.
== Autres fonctions ==
; Fonctions trigonométriques
* <code lang="python">np.sin(x)</code> : fonction sinus trigonométrique ;
* <code lang="python">np.cos(x)</code> : fonction cosinus trigonométrique ;
* <code lang="python">np.tan(x)</code> : fonction tangente trigonométrique ;
* <code lang="python">np.arcsin(x)</code> : fonction sinus trigonométrique inverse ;
* <code lang="python">np.arccos(x)</code> : fonction cosinus trigonométrique inverse ;
* <code lang="python">np.arctan(x)</code> : fonction tangente trigonométrique inverse ;
* <code lang="python">np.arctan2(x1,x2)</code> : fonction tangente trigonométrique inverse de <math>x1/x2</math> sélectionnant le bon quadrant selon les deux valeurs.
; Fonctions hyperboliques
* <code lang="python">np.sinh(x)</code> : fonction sinus hyperbolique ;
* <code lang="python">np.cosh(x)</code> : fonction cosinus hyperbolique ;
* <code lang="python">np.tanh(x)</code> : fonction tangente hyperbolique ;
* <code lang="python">np.arcsinh(x)</code> : fonction sinus hyperbolique inverse ;
* <code lang="python">np.arccosh(x)</code> : fonction cosinus hyperbolique inverse ;
* <code lang="python">np.arctanh(x)</code> : fonction tangente hyperbolique inverse.
== Précision ==
La précision de la représentation des réels en virgule flottante ''({{lang|en|float}})'' est donnée par :
: <code lang="python">np.finfo(float).eps</code>
Python possède une commande de base pour les arrondis : <code>round(x, n)</code> arrondit le nombre ''x'' à ''n'' chiffres après la virgule. Si ''n'' = 0 ou si ce paramètre est omis, il arrondit à l'entier.
Numpy dispose des fonctions suivantes, qui s'appliquent terme par terme sur des matrices :
* <code>np.round(x, n)</code> : arrondit au plus proche ; comme la fonction de base de Python ;
* <code>np.fix(x, n)</code> : arrondit à l'entier vers zéro ;
* <code>np.ceil(x, n)</code> : arrondit à l'entier supérieur ;
* <code>np.floor(x, n)</code> : arrondit à l'entier inférieur.
Les matrices (<code>np.array([…])</code> ont une méthode <code>round(n)</code> pour arrondir chaque élément à la ''n''{{e}} décimale, par exemple
<syntaxhighlight lang="python">
print(np.round(np.pi, 5)) # 3.14159
print(np.array([np.pi]).round(5)) # [3.14159]
</syntaxhighlight>
== Fonctions booléennes ==
Les fonctions booléennes sont incluses dans le langage de base :
* <code lang="python">x or y</code> : ''ou'' ;
* <code lang="python">x and y</code> : ''et'' ;
* <code lang="python">not x</code> : ''non'' ;
* <code lang="python">all([x, y, z])</code> : ''et'' appliqué à une liste ;
* <code lang="python">any([x, y, z])</code> : ''ou'' appliqué à une liste.
{{note |Les fonctions <code>all()</code> et <code>any()</code> renvoient un seul et unique booléen. Si l'on veut appliquer un opérateur élément par élément à toute une liste, par exemple prendre la négation de chaque élément de la liste, il faut utiliser les fonctions de type <code>numpy.logical_''xxx''</code> de la bibliothèque NumPy :
* <code>np.logical_or(a, b)</code> ;
* <code>np.logical_and(a, b)</code> ;
* <code>np.logical_xor(a, b)</code> ;
* <code>np.logical_nor(a)</code>.}}
== Notes et références ==
{{références}}
* {{lien web
| url = https://docs.scipy.org/doc/numpy/reference/ufuncs.html
| titre = Universal functions (ufunc)
| site = Numpy and Scipy Documentation
| lang = en
| consulté le = 2019-05-18
}}
----
[[../Premiers programmes|Premiers programmes]] < [[../|↑]] > [[../Éléments de programmation|Éléments de programmation]]
{{DEFAULTSORT:Fonctions mathematiques generales}}
[[Catégorie:Python pour le calcul scientifique (livre)]]
tt9gl44cocr6v00o5t6w6d6qx5advhd
768701
768630
2026-06-26T08:00:31Z
Cdang
1202
/* Rappel sur les opérations de base */ comparaisons
768701
wikitext
text/x-wiki
Par défaut, Python ne propose que les quatre opérations (<code>+</code>, <code>-</code>, <code>*</code> et <code>/</code>), la division euclidienne (<code>//</code>, et <code>%</code> pour le reste) et l'élévation à la puissance (<code>**</code>)<ref>Voir ''[[../Découverte de Python et de Jupyter#Commandes élémentaires|Découverte de Python et de Jupyter > Commandes élémentaires]]''.</ref>. Le module NumPy fournit les opérations mathématiques avancées. En particulier, il propose des fonctions « universelles » ''(ufunc)'' qui peuvent travailler directement sur des matrices.
== Rappel sur les opérations de base ==
* Addition (''a'' + ''b'') : <code>a + b</code> ;
* soustraction (''a'' – ''b'') : <code>a - b</code> ;
* multiplication (''a'' × ''b'') : <code>a / b</code> ;
* division (''a'' ÷ ''b'') : <code>a / b</code> ;
* division euclidienne (''a'' ÷ ''b'') : <code>quotient = a // b ; reste = a % b</code><br />ou bien <code>[quotient, reste] = divmod(a, b)</code> ;
* élévation à la puissance (''a''<sup> ''b'' </sup>) : <code>a ** b</code> ou bien <code>pow(a, b)</code> ;
* arrondi à l'entier le plus proche de ''a'' : <code>round(a)</code> ;
* comparaisons (=, <, égt;, ⩽, ⩾) : <code>==</code>, <code><</code>, <code>></code>, <code><=</code>, <code>>=</code> ; notez que l'on peut les associer dans une même expression, par exemple <code>a <= b < c</code>.
{{note|L'opérateur « + » sert également à la concaténation (de chaînes de caractères, de listes, de n-uplets).}}
<syntaxhighlight lang="python">
print([1, 2] + [, 4]) # [1, 2, 3, 4]
</syntaxhighlight>
== Importer le module Numpy ==
Toutes les fonctions évoquées dans ce chapitre supposent l'import du module NumPy sous le nom {{g|np}} :
<syntaxhighlight lang="python">
# Importer NumPy, sous le nom np
import numpy as np
# Ensuite, le code utilisant les fonctions de np
# ...
</syntaxhighlight>
== Vecteurs et matrices ==
Les vecteurs et matrices sont étudiés plus loin. Pour l'instant, indiquons que pour créer un vecteur, on applique la commande <code>np.array()</code> à une liste :
<syntaxhighlight lang="python">
vecteur = np.array([x, y z])
</syntaxhighlight>
pour le vecteur
: <math>\begin{pmatrix}x & y & z \end{pmatrix}</math> ou bien <math>\begin{pmatrix}x \\ y \\ z \end{pmatrix}</math>.
Une matrice est une liste de lignes, chaque ligne étant une liste de valeurs :
<syntaxhighlight lang="python">
matrice = np.array([[a11, a12], [a21, a22]])
# [ligne 1 ] [ligne 2 ]
</syntaxhighlight>
pour la matrice
: <math>\begin{pmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \end{pmatrix}</math>.
Sauf indication contraire, avec des vecteurs ou matrices, les opérateurs s'appliquent élément par élément, à l'exception des opérateurs <code>*</code>, <code>/</code> et <code>**</code>.
<syntaxhighlight lang="python">
print(np.array([[1, 2], [3, 4]]) + np.array([[5, 6], [7, 8]]))
# [[ 6 8]
# [10 12]]
print(np.cos(np.array([[0, np.pi/2], [np.pi/3, np.pi/4]])))
# [[1.0 0.0]
# [0.5 0.707]]
</syntaxhighlight>
== Fonctions universelles de base ==
On retrouve les quatre opérations mais sous la forme de fonctions de deux variables :
* <code>np.add(x1, x2)</code> : ''x''<sub>1</sub> + ''x''<sub>2</sub> ;
* <code>np.subtract(x1, x2)</code> : ''x''<sub>1</sub> – ''x''<sub>2</sub> ;
* <code>np.multiply(x1, x2)</code> : ''x''<sub>1</sub> × ''x''<sub>2</sub> ;
* <code>np.divide(x1, x2)</code>, <code>np.true_divide(x1, x2)</code> : ''x''<sub>1</sub> ÷ ''x''<sub>2</sub>.
Les variables ''x''<sub>1</sub> et ''x''<sub>2</sub> peuvent être des matrices de même dimension, les opérations sont alors faites élément par élément. Ces fonctions disposent en outre de méthodes, notamment de la méthode <code>.accumulate()</code> qui cumule les opérations, par exemple
<syntaxhighlight lang="python">
np.add.accumulate([1, 2, 3, 4, 5])
# array([ 1, 3, 6, 10, 15])
</syntaxhighlight>
Cette méthode existe d'ailleurs pour toutes les fonctions universelles de deux variables.
Les autres opérations de base sont :
* <code>np.negative(x)</code> : –''x'' ;
* <code>np.reciprocal(x)</code> : 1 / ''x'' ;
* <code>np.mod(x1, x2)</code>, <code>np.remainder(x1, x2)</code> : reste de la division euclidienne ''x''<sub>1</sub> ÷ ''x''<sub>2</sub> ;
* <code>np.absolute(x)</code>, <code>np.abs(x)</code> : |''x''| ;
* <code>np.rint(x)</code> : arrondi à l'entier le plus proche ;
* <code>np.floor(x)</code>, <code>np.ceil(x)</code> : arrondi respectivement à l'entier directement inférieur et supérieur ;
* <code>np.trunc(x)</code> : tronque les décimales, partie entière, E(''x'') ;
* <code>np.sign(x)</code> : signe de ''x'' (+1 ou –1) ;
* <code>np.gcd(x1, x2)</code> : PGCD ;
* <code>np.lcm(x1, x2)</code> : PPCM ;
* <code>np.mean([x1, x2, …, xn])</code> : moyenne des ''x<sub>i</sub>'' ;
* <code>np.average([x1, x2, …, xn], weights=[m1, m2, …, mn])</code> : moyenne des ''x<sub>i</sub>'' pondérée par mes masses ''m<sub>i</sub>''.
Notez que les valeurs renvoyées par <code lang="python">np.rint()</code>, <code lang="python">np.floor()</code>, <code lang="python">np.ceil()</code> et <code lang="python">np.trunc()</code> sont des réels à virgule flottante ''({{lang|en|float}})'' bien que les valeurs soient entières. Pour avoir des nombres de types « entier » ''({{lang|en|integer}})'', il faut utiliser la fonction <code lang="python">int()</code> : <code lang="python">int(np.rint())</code>…
== Autres fonctions universelles ==
* <code>np.power(x1, x2)</code> : ''x''<sub>1</sub><sup>''x''<sub>2</sub></sup> ;
* <code>np.square(x)</code> : ''x''<sup>2</sup> ;
* <code>np.sqrt(x)</code> : <math>\sqrt{x}</math> ;
*: si ''x'' est une matrice de réels, alors le résultat doit être réel, les valeurs négatives renvoient un NaN ; si ''x'' contient au moins un nombre complexe, alors le résultat est complexe, par exemple :
<syntaxhighlight lang="python">
np.sqrt(-1) # NaN
np.sqrt(-1 + 0j) # 0 + 1j
np.sqrt(complex(-1)) # 0 + 1j
</syntaxhighlight>
* <code>np.cbrt(x)</code> : <math>\sqrt[3]{x}</math> ;
* <code>np.exp(x)</code> : exp(''x''), ''e<sup>x</sup>'' ;
* <code>np.exp2(x)</code> : 2<sup>''x''</sup> ;
* <code>np.expm1(x)</code> : exp(''x'') – 1 ;
* <code>np.log(x)</code> : ln(''x''), logarithme népérien ;
* <code>np.log2(x)</code> : logarithme en base 2 ;
* <code>np.log10(x)</code> : log(''x''), logarithme en base 10 ;
* <code>np.log1p(x)</code> : ln(1 + ''x'') ;
* <code>np.conj(z)</code> : {{surligner|''z''}}, conjugué complexe ;
* <code>np.heavyside(x1, x2)</code> : fonction marche, vaut 0 si ''x''<sub>1</sub> < 0, 1 si ''x''<sub>1</sub> > 0, et ''x''<sub>2</sub> si ''x''<sub>1</sub> = 0.
== Autres fonctions ==
; Fonctions trigonométriques
* <code lang="python">np.sin(x)</code> : fonction sinus trigonométrique ;
* <code lang="python">np.cos(x)</code> : fonction cosinus trigonométrique ;
* <code lang="python">np.tan(x)</code> : fonction tangente trigonométrique ;
* <code lang="python">np.arcsin(x)</code> : fonction sinus trigonométrique inverse ;
* <code lang="python">np.arccos(x)</code> : fonction cosinus trigonométrique inverse ;
* <code lang="python">np.arctan(x)</code> : fonction tangente trigonométrique inverse ;
* <code lang="python">np.arctan2(x1,x2)</code> : fonction tangente trigonométrique inverse de <math>x1/x2</math> sélectionnant le bon quadrant selon les deux valeurs.
; Fonctions hyperboliques
* <code lang="python">np.sinh(x)</code> : fonction sinus hyperbolique ;
* <code lang="python">np.cosh(x)</code> : fonction cosinus hyperbolique ;
* <code lang="python">np.tanh(x)</code> : fonction tangente hyperbolique ;
* <code lang="python">np.arcsinh(x)</code> : fonction sinus hyperbolique inverse ;
* <code lang="python">np.arccosh(x)</code> : fonction cosinus hyperbolique inverse ;
* <code lang="python">np.arctanh(x)</code> : fonction tangente hyperbolique inverse.
== Précision ==
La précision de la représentation des réels en virgule flottante ''({{lang|en|float}})'' est donnée par :
: <code lang="python">np.finfo(float).eps</code>
Python possède une commande de base pour les arrondis : <code>round(x, n)</code> arrondit le nombre ''x'' à ''n'' chiffres après la virgule. Si ''n'' = 0 ou si ce paramètre est omis, il arrondit à l'entier.
Numpy dispose des fonctions suivantes, qui s'appliquent terme par terme sur des matrices :
* <code>np.round(x, n)</code> : arrondit au plus proche ; comme la fonction de base de Python ;
* <code>np.fix(x, n)</code> : arrondit à l'entier vers zéro ;
* <code>np.ceil(x, n)</code> : arrondit à l'entier supérieur ;
* <code>np.floor(x, n)</code> : arrondit à l'entier inférieur.
Les matrices (<code>np.array([…])</code> ont une méthode <code>round(n)</code> pour arrondir chaque élément à la ''n''{{e}} décimale, par exemple
<syntaxhighlight lang="python">
print(np.round(np.pi, 5)) # 3.14159
print(np.array([np.pi]).round(5)) # [3.14159]
</syntaxhighlight>
== Fonctions booléennes ==
Les fonctions booléennes sont incluses dans le langage de base :
* <code lang="python">x or y</code> : ''ou'' ;
* <code lang="python">x and y</code> : ''et'' ;
* <code lang="python">not x</code> : ''non'' ;
* <code lang="python">all([x, y, z])</code> : ''et'' appliqué à une liste ;
* <code lang="python">any([x, y, z])</code> : ''ou'' appliqué à une liste.
{{note |Les fonctions <code>all()</code> et <code>any()</code> renvoient un seul et unique booléen. Si l'on veut appliquer un opérateur élément par élément à toute une liste, par exemple prendre la négation de chaque élément de la liste, il faut utiliser les fonctions de type <code>numpy.logical_''xxx''</code> de la bibliothèque NumPy :
* <code>np.logical_or(a, b)</code> ;
* <code>np.logical_and(a, b)</code> ;
* <code>np.logical_xor(a, b)</code> ;
* <code>np.logical_nor(a)</code>.}}
== Notes et références ==
{{références}}
* {{lien web
| url = https://docs.scipy.org/doc/numpy/reference/ufuncs.html
| titre = Universal functions (ufunc)
| site = Numpy and Scipy Documentation
| lang = en
| consulté le = 2019-05-18
}}
----
[[../Premiers programmes|Premiers programmes]] < [[../|↑]] > [[../Éléments de programmation|Éléments de programmation]]
{{DEFAULTSORT:Fonctions mathematiques generales}}
[[Catégorie:Python pour le calcul scientifique (livre)]]
p9ih04s0ahhqdr0kxqbe4uxjom4j475
768702
768701
2026-06-26T08:00:52Z
Cdang
1202
/* Rappel sur les opérations de base */ typo
768702
wikitext
text/x-wiki
Par défaut, Python ne propose que les quatre opérations (<code>+</code>, <code>-</code>, <code>*</code> et <code>/</code>), la division euclidienne (<code>//</code>, et <code>%</code> pour le reste) et l'élévation à la puissance (<code>**</code>)<ref>Voir ''[[../Découverte de Python et de Jupyter#Commandes élémentaires|Découverte de Python et de Jupyter > Commandes élémentaires]]''.</ref>. Le module NumPy fournit les opérations mathématiques avancées. En particulier, il propose des fonctions « universelles » ''(ufunc)'' qui peuvent travailler directement sur des matrices.
== Rappel sur les opérations de base ==
* Addition (''a'' + ''b'') : <code>a + b</code> ;
* soustraction (''a'' – ''b'') : <code>a - b</code> ;
* multiplication (''a'' × ''b'') : <code>a / b</code> ;
* division (''a'' ÷ ''b'') : <code>a / b</code> ;
* division euclidienne (''a'' ÷ ''b'') : <code>quotient = a // b ; reste = a % b</code><br />ou bien <code>[quotient, reste] = divmod(a, b)</code> ;
* élévation à la puissance (''a''<sup> ''b'' </sup>) : <code>a ** b</code> ou bien <code>pow(a, b)</code> ;
* arrondi à l'entier le plus proche de ''a'' : <code>round(a)</code> ;
* comparaisons (=, <, >, ⩽, ⩾) : <code>==</code>, <code><</code>, <code>></code>, <code><=</code>, <code>>=</code> ; notez que l'on peut les associer dans une même expression, par exemple <code>a <= b < c</code>.
{{note|L'opérateur « + » sert également à la concaténation (de chaînes de caractères, de listes, de n-uplets).}}
<syntaxhighlight lang="python">
print([1, 2] + [, 4]) # [1, 2, 3, 4]
</syntaxhighlight>
== Importer le module Numpy ==
Toutes les fonctions évoquées dans ce chapitre supposent l'import du module NumPy sous le nom {{g|np}} :
<syntaxhighlight lang="python">
# Importer NumPy, sous le nom np
import numpy as np
# Ensuite, le code utilisant les fonctions de np
# ...
</syntaxhighlight>
== Vecteurs et matrices ==
Les vecteurs et matrices sont étudiés plus loin. Pour l'instant, indiquons que pour créer un vecteur, on applique la commande <code>np.array()</code> à une liste :
<syntaxhighlight lang="python">
vecteur = np.array([x, y z])
</syntaxhighlight>
pour le vecteur
: <math>\begin{pmatrix}x & y & z \end{pmatrix}</math> ou bien <math>\begin{pmatrix}x \\ y \\ z \end{pmatrix}</math>.
Une matrice est une liste de lignes, chaque ligne étant une liste de valeurs :
<syntaxhighlight lang="python">
matrice = np.array([[a11, a12], [a21, a22]])
# [ligne 1 ] [ligne 2 ]
</syntaxhighlight>
pour la matrice
: <math>\begin{pmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \end{pmatrix}</math>.
Sauf indication contraire, avec des vecteurs ou matrices, les opérateurs s'appliquent élément par élément, à l'exception des opérateurs <code>*</code>, <code>/</code> et <code>**</code>.
<syntaxhighlight lang="python">
print(np.array([[1, 2], [3, 4]]) + np.array([[5, 6], [7, 8]]))
# [[ 6 8]
# [10 12]]
print(np.cos(np.array([[0, np.pi/2], [np.pi/3, np.pi/4]])))
# [[1.0 0.0]
# [0.5 0.707]]
</syntaxhighlight>
== Fonctions universelles de base ==
On retrouve les quatre opérations mais sous la forme de fonctions de deux variables :
* <code>np.add(x1, x2)</code> : ''x''<sub>1</sub> + ''x''<sub>2</sub> ;
* <code>np.subtract(x1, x2)</code> : ''x''<sub>1</sub> – ''x''<sub>2</sub> ;
* <code>np.multiply(x1, x2)</code> : ''x''<sub>1</sub> × ''x''<sub>2</sub> ;
* <code>np.divide(x1, x2)</code>, <code>np.true_divide(x1, x2)</code> : ''x''<sub>1</sub> ÷ ''x''<sub>2</sub>.
Les variables ''x''<sub>1</sub> et ''x''<sub>2</sub> peuvent être des matrices de même dimension, les opérations sont alors faites élément par élément. Ces fonctions disposent en outre de méthodes, notamment de la méthode <code>.accumulate()</code> qui cumule les opérations, par exemple
<syntaxhighlight lang="python">
np.add.accumulate([1, 2, 3, 4, 5])
# array([ 1, 3, 6, 10, 15])
</syntaxhighlight>
Cette méthode existe d'ailleurs pour toutes les fonctions universelles de deux variables.
Les autres opérations de base sont :
* <code>np.negative(x)</code> : –''x'' ;
* <code>np.reciprocal(x)</code> : 1 / ''x'' ;
* <code>np.mod(x1, x2)</code>, <code>np.remainder(x1, x2)</code> : reste de la division euclidienne ''x''<sub>1</sub> ÷ ''x''<sub>2</sub> ;
* <code>np.absolute(x)</code>, <code>np.abs(x)</code> : |''x''| ;
* <code>np.rint(x)</code> : arrondi à l'entier le plus proche ;
* <code>np.floor(x)</code>, <code>np.ceil(x)</code> : arrondi respectivement à l'entier directement inférieur et supérieur ;
* <code>np.trunc(x)</code> : tronque les décimales, partie entière, E(''x'') ;
* <code>np.sign(x)</code> : signe de ''x'' (+1 ou –1) ;
* <code>np.gcd(x1, x2)</code> : PGCD ;
* <code>np.lcm(x1, x2)</code> : PPCM ;
* <code>np.mean([x1, x2, …, xn])</code> : moyenne des ''x<sub>i</sub>'' ;
* <code>np.average([x1, x2, …, xn], weights=[m1, m2, …, mn])</code> : moyenne des ''x<sub>i</sub>'' pondérée par mes masses ''m<sub>i</sub>''.
Notez que les valeurs renvoyées par <code lang="python">np.rint()</code>, <code lang="python">np.floor()</code>, <code lang="python">np.ceil()</code> et <code lang="python">np.trunc()</code> sont des réels à virgule flottante ''({{lang|en|float}})'' bien que les valeurs soient entières. Pour avoir des nombres de types « entier » ''({{lang|en|integer}})'', il faut utiliser la fonction <code lang="python">int()</code> : <code lang="python">int(np.rint())</code>…
== Autres fonctions universelles ==
* <code>np.power(x1, x2)</code> : ''x''<sub>1</sub><sup>''x''<sub>2</sub></sup> ;
* <code>np.square(x)</code> : ''x''<sup>2</sup> ;
* <code>np.sqrt(x)</code> : <math>\sqrt{x}</math> ;
*: si ''x'' est une matrice de réels, alors le résultat doit être réel, les valeurs négatives renvoient un NaN ; si ''x'' contient au moins un nombre complexe, alors le résultat est complexe, par exemple :
<syntaxhighlight lang="python">
np.sqrt(-1) # NaN
np.sqrt(-1 + 0j) # 0 + 1j
np.sqrt(complex(-1)) # 0 + 1j
</syntaxhighlight>
* <code>np.cbrt(x)</code> : <math>\sqrt[3]{x}</math> ;
* <code>np.exp(x)</code> : exp(''x''), ''e<sup>x</sup>'' ;
* <code>np.exp2(x)</code> : 2<sup>''x''</sup> ;
* <code>np.expm1(x)</code> : exp(''x'') – 1 ;
* <code>np.log(x)</code> : ln(''x''), logarithme népérien ;
* <code>np.log2(x)</code> : logarithme en base 2 ;
* <code>np.log10(x)</code> : log(''x''), logarithme en base 10 ;
* <code>np.log1p(x)</code> : ln(1 + ''x'') ;
* <code>np.conj(z)</code> : {{surligner|''z''}}, conjugué complexe ;
* <code>np.heavyside(x1, x2)</code> : fonction marche, vaut 0 si ''x''<sub>1</sub> < 0, 1 si ''x''<sub>1</sub> > 0, et ''x''<sub>2</sub> si ''x''<sub>1</sub> = 0.
== Autres fonctions ==
; Fonctions trigonométriques
* <code lang="python">np.sin(x)</code> : fonction sinus trigonométrique ;
* <code lang="python">np.cos(x)</code> : fonction cosinus trigonométrique ;
* <code lang="python">np.tan(x)</code> : fonction tangente trigonométrique ;
* <code lang="python">np.arcsin(x)</code> : fonction sinus trigonométrique inverse ;
* <code lang="python">np.arccos(x)</code> : fonction cosinus trigonométrique inverse ;
* <code lang="python">np.arctan(x)</code> : fonction tangente trigonométrique inverse ;
* <code lang="python">np.arctan2(x1,x2)</code> : fonction tangente trigonométrique inverse de <math>x1/x2</math> sélectionnant le bon quadrant selon les deux valeurs.
; Fonctions hyperboliques
* <code lang="python">np.sinh(x)</code> : fonction sinus hyperbolique ;
* <code lang="python">np.cosh(x)</code> : fonction cosinus hyperbolique ;
* <code lang="python">np.tanh(x)</code> : fonction tangente hyperbolique ;
* <code lang="python">np.arcsinh(x)</code> : fonction sinus hyperbolique inverse ;
* <code lang="python">np.arccosh(x)</code> : fonction cosinus hyperbolique inverse ;
* <code lang="python">np.arctanh(x)</code> : fonction tangente hyperbolique inverse.
== Précision ==
La précision de la représentation des réels en virgule flottante ''({{lang|en|float}})'' est donnée par :
: <code lang="python">np.finfo(float).eps</code>
Python possède une commande de base pour les arrondis : <code>round(x, n)</code> arrondit le nombre ''x'' à ''n'' chiffres après la virgule. Si ''n'' = 0 ou si ce paramètre est omis, il arrondit à l'entier.
Numpy dispose des fonctions suivantes, qui s'appliquent terme par terme sur des matrices :
* <code>np.round(x, n)</code> : arrondit au plus proche ; comme la fonction de base de Python ;
* <code>np.fix(x, n)</code> : arrondit à l'entier vers zéro ;
* <code>np.ceil(x, n)</code> : arrondit à l'entier supérieur ;
* <code>np.floor(x, n)</code> : arrondit à l'entier inférieur.
Les matrices (<code>np.array([…])</code> ont une méthode <code>round(n)</code> pour arrondir chaque élément à la ''n''{{e}} décimale, par exemple
<syntaxhighlight lang="python">
print(np.round(np.pi, 5)) # 3.14159
print(np.array([np.pi]).round(5)) # [3.14159]
</syntaxhighlight>
== Fonctions booléennes ==
Les fonctions booléennes sont incluses dans le langage de base :
* <code lang="python">x or y</code> : ''ou'' ;
* <code lang="python">x and y</code> : ''et'' ;
* <code lang="python">not x</code> : ''non'' ;
* <code lang="python">all([x, y, z])</code> : ''et'' appliqué à une liste ;
* <code lang="python">any([x, y, z])</code> : ''ou'' appliqué à une liste.
{{note |Les fonctions <code>all()</code> et <code>any()</code> renvoient un seul et unique booléen. Si l'on veut appliquer un opérateur élément par élément à toute une liste, par exemple prendre la négation de chaque élément de la liste, il faut utiliser les fonctions de type <code>numpy.logical_''xxx''</code> de la bibliothèque NumPy :
* <code>np.logical_or(a, b)</code> ;
* <code>np.logical_and(a, b)</code> ;
* <code>np.logical_xor(a, b)</code> ;
* <code>np.logical_nor(a)</code>.}}
== Notes et références ==
{{références}}
* {{lien web
| url = https://docs.scipy.org/doc/numpy/reference/ufuncs.html
| titre = Universal functions (ufunc)
| site = Numpy and Scipy Documentation
| lang = en
| consulté le = 2019-05-18
}}
----
[[../Premiers programmes|Premiers programmes]] < [[../|↑]] > [[../Éléments de programmation|Éléments de programmation]]
{{DEFAULTSORT:Fonctions mathematiques generales}}
[[Catégorie:Python pour le calcul scientifique (livre)]]
5fy5u7p5cwl8yxq2z9dak2vpl29ddqx
768703
768702
2026-06-26T08:02:14Z
Cdang
1202
/* Rappel sur les opérations de base */ typo
768703
wikitext
text/x-wiki
Par défaut, Python ne propose que les quatre opérations (<code>+</code>, <code>-</code>, <code>*</code> et <code>/</code>), la division euclidienne (<code>//</code>, et <code>%</code> pour le reste) et l'élévation à la puissance (<code>**</code>)<ref>Voir ''[[../Découverte de Python et de Jupyter#Commandes élémentaires|Découverte de Python et de Jupyter > Commandes élémentaires]]''.</ref>. Le module NumPy fournit les opérations mathématiques avancées. En particulier, il propose des fonctions « universelles » ''(ufunc)'' qui peuvent travailler directement sur des matrices.
== Rappel sur les opérations de base ==
* Addition (''a'' + ''b'') : <code>a + b</code> ;
* soustraction (''a'' – ''b'') : <code>a - b</code> ;
* multiplication (''a'' × ''b'') : <code>a / b</code> ;
* division (''a'' ÷ ''b'') : <code>a / b</code> ;
* division euclidienne (''a'' ÷ ''b'') : <code>quotient = a // b ; reste = a % b</code><br />ou bien <code>[quotient, reste] = divmod(a, b)</code> ;
* élévation à la puissance (''a''<sup> ''b'' </sup>) : <code>a ** b</code> ou bien <code>pow(a, b)</code> ;
* arrondi à l'entier le plus proche de ''a'' : <code>round(a)</code> ;
* comparaisons (=, <, >, ⩽, ⩾) : <code>==</code>, <code><</code>, <code>></code>, <code><=</code>, <code>>=</code> ; notez que l'on peut les associer dans une même expression, par exemple <code>a <= b < c</code>.
{{note|L'opérateur « + » sert également à la concaténation (de chaînes de caractères, de listes, de n-uplets).}}
<syntaxhighlight lang="python">
print([1, 2] + [3, 4]) # [1, 2, 3, 4]
</syntaxhighlight>
== Importer le module Numpy ==
Toutes les fonctions évoquées dans ce chapitre supposent l'import du module NumPy sous le nom {{g|np}} :
<syntaxhighlight lang="python">
# Importer NumPy, sous le nom np
import numpy as np
# Ensuite, le code utilisant les fonctions de np
# ...
</syntaxhighlight>
== Vecteurs et matrices ==
Les vecteurs et matrices sont étudiés plus loin. Pour l'instant, indiquons que pour créer un vecteur, on applique la commande <code>np.array()</code> à une liste :
<syntaxhighlight lang="python">
vecteur = np.array([x, y z])
</syntaxhighlight>
pour le vecteur
: <math>\begin{pmatrix}x & y & z \end{pmatrix}</math> ou bien <math>\begin{pmatrix}x \\ y \\ z \end{pmatrix}</math>.
Une matrice est une liste de lignes, chaque ligne étant une liste de valeurs :
<syntaxhighlight lang="python">
matrice = np.array([[a11, a12], [a21, a22]])
# [ligne 1 ] [ligne 2 ]
</syntaxhighlight>
pour la matrice
: <math>\begin{pmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \end{pmatrix}</math>.
Sauf indication contraire, avec des vecteurs ou matrices, les opérateurs s'appliquent élément par élément, à l'exception des opérateurs <code>*</code>, <code>/</code> et <code>**</code>.
<syntaxhighlight lang="python">
print(np.array([[1, 2], [3, 4]]) + np.array([[5, 6], [7, 8]]))
# [[ 6 8]
# [10 12]]
print(np.cos(np.array([[0, np.pi/2], [np.pi/3, np.pi/4]])))
# [[1.0 0.0]
# [0.5 0.707]]
</syntaxhighlight>
== Fonctions universelles de base ==
On retrouve les quatre opérations mais sous la forme de fonctions de deux variables :
* <code>np.add(x1, x2)</code> : ''x''<sub>1</sub> + ''x''<sub>2</sub> ;
* <code>np.subtract(x1, x2)</code> : ''x''<sub>1</sub> – ''x''<sub>2</sub> ;
* <code>np.multiply(x1, x2)</code> : ''x''<sub>1</sub> × ''x''<sub>2</sub> ;
* <code>np.divide(x1, x2)</code>, <code>np.true_divide(x1, x2)</code> : ''x''<sub>1</sub> ÷ ''x''<sub>2</sub>.
Les variables ''x''<sub>1</sub> et ''x''<sub>2</sub> peuvent être des matrices de même dimension, les opérations sont alors faites élément par élément. Ces fonctions disposent en outre de méthodes, notamment de la méthode <code>.accumulate()</code> qui cumule les opérations, par exemple
<syntaxhighlight lang="python">
np.add.accumulate([1, 2, 3, 4, 5])
# array([ 1, 3, 6, 10, 15])
</syntaxhighlight>
Cette méthode existe d'ailleurs pour toutes les fonctions universelles de deux variables.
Les autres opérations de base sont :
* <code>np.negative(x)</code> : –''x'' ;
* <code>np.reciprocal(x)</code> : 1 / ''x'' ;
* <code>np.mod(x1, x2)</code>, <code>np.remainder(x1, x2)</code> : reste de la division euclidienne ''x''<sub>1</sub> ÷ ''x''<sub>2</sub> ;
* <code>np.absolute(x)</code>, <code>np.abs(x)</code> : |''x''| ;
* <code>np.rint(x)</code> : arrondi à l'entier le plus proche ;
* <code>np.floor(x)</code>, <code>np.ceil(x)</code> : arrondi respectivement à l'entier directement inférieur et supérieur ;
* <code>np.trunc(x)</code> : tronque les décimales, partie entière, E(''x'') ;
* <code>np.sign(x)</code> : signe de ''x'' (+1 ou –1) ;
* <code>np.gcd(x1, x2)</code> : PGCD ;
* <code>np.lcm(x1, x2)</code> : PPCM ;
* <code>np.mean([x1, x2, …, xn])</code> : moyenne des ''x<sub>i</sub>'' ;
* <code>np.average([x1, x2, …, xn], weights=[m1, m2, …, mn])</code> : moyenne des ''x<sub>i</sub>'' pondérée par mes masses ''m<sub>i</sub>''.
Notez que les valeurs renvoyées par <code lang="python">np.rint()</code>, <code lang="python">np.floor()</code>, <code lang="python">np.ceil()</code> et <code lang="python">np.trunc()</code> sont des réels à virgule flottante ''({{lang|en|float}})'' bien que les valeurs soient entières. Pour avoir des nombres de types « entier » ''({{lang|en|integer}})'', il faut utiliser la fonction <code lang="python">int()</code> : <code lang="python">int(np.rint())</code>…
== Autres fonctions universelles ==
* <code>np.power(x1, x2)</code> : ''x''<sub>1</sub><sup>''x''<sub>2</sub></sup> ;
* <code>np.square(x)</code> : ''x''<sup>2</sup> ;
* <code>np.sqrt(x)</code> : <math>\sqrt{x}</math> ;
*: si ''x'' est une matrice de réels, alors le résultat doit être réel, les valeurs négatives renvoient un NaN ; si ''x'' contient au moins un nombre complexe, alors le résultat est complexe, par exemple :
<syntaxhighlight lang="python">
np.sqrt(-1) # NaN
np.sqrt(-1 + 0j) # 0 + 1j
np.sqrt(complex(-1)) # 0 + 1j
</syntaxhighlight>
* <code>np.cbrt(x)</code> : <math>\sqrt[3]{x}</math> ;
* <code>np.exp(x)</code> : exp(''x''), ''e<sup>x</sup>'' ;
* <code>np.exp2(x)</code> : 2<sup>''x''</sup> ;
* <code>np.expm1(x)</code> : exp(''x'') – 1 ;
* <code>np.log(x)</code> : ln(''x''), logarithme népérien ;
* <code>np.log2(x)</code> : logarithme en base 2 ;
* <code>np.log10(x)</code> : log(''x''), logarithme en base 10 ;
* <code>np.log1p(x)</code> : ln(1 + ''x'') ;
* <code>np.conj(z)</code> : {{surligner|''z''}}, conjugué complexe ;
* <code>np.heavyside(x1, x2)</code> : fonction marche, vaut 0 si ''x''<sub>1</sub> < 0, 1 si ''x''<sub>1</sub> > 0, et ''x''<sub>2</sub> si ''x''<sub>1</sub> = 0.
== Autres fonctions ==
; Fonctions trigonométriques
* <code lang="python">np.sin(x)</code> : fonction sinus trigonométrique ;
* <code lang="python">np.cos(x)</code> : fonction cosinus trigonométrique ;
* <code lang="python">np.tan(x)</code> : fonction tangente trigonométrique ;
* <code lang="python">np.arcsin(x)</code> : fonction sinus trigonométrique inverse ;
* <code lang="python">np.arccos(x)</code> : fonction cosinus trigonométrique inverse ;
* <code lang="python">np.arctan(x)</code> : fonction tangente trigonométrique inverse ;
* <code lang="python">np.arctan2(x1,x2)</code> : fonction tangente trigonométrique inverse de <math>x1/x2</math> sélectionnant le bon quadrant selon les deux valeurs.
; Fonctions hyperboliques
* <code lang="python">np.sinh(x)</code> : fonction sinus hyperbolique ;
* <code lang="python">np.cosh(x)</code> : fonction cosinus hyperbolique ;
* <code lang="python">np.tanh(x)</code> : fonction tangente hyperbolique ;
* <code lang="python">np.arcsinh(x)</code> : fonction sinus hyperbolique inverse ;
* <code lang="python">np.arccosh(x)</code> : fonction cosinus hyperbolique inverse ;
* <code lang="python">np.arctanh(x)</code> : fonction tangente hyperbolique inverse.
== Précision ==
La précision de la représentation des réels en virgule flottante ''({{lang|en|float}})'' est donnée par :
: <code lang="python">np.finfo(float).eps</code>
Python possède une commande de base pour les arrondis : <code>round(x, n)</code> arrondit le nombre ''x'' à ''n'' chiffres après la virgule. Si ''n'' = 0 ou si ce paramètre est omis, il arrondit à l'entier.
Numpy dispose des fonctions suivantes, qui s'appliquent terme par terme sur des matrices :
* <code>np.round(x, n)</code> : arrondit au plus proche ; comme la fonction de base de Python ;
* <code>np.fix(x, n)</code> : arrondit à l'entier vers zéro ;
* <code>np.ceil(x, n)</code> : arrondit à l'entier supérieur ;
* <code>np.floor(x, n)</code> : arrondit à l'entier inférieur.
Les matrices (<code>np.array([…])</code> ont une méthode <code>round(n)</code> pour arrondir chaque élément à la ''n''{{e}} décimale, par exemple
<syntaxhighlight lang="python">
print(np.round(np.pi, 5)) # 3.14159
print(np.array([np.pi]).round(5)) # [3.14159]
</syntaxhighlight>
== Fonctions booléennes ==
Les fonctions booléennes sont incluses dans le langage de base :
* <code lang="python">x or y</code> : ''ou'' ;
* <code lang="python">x and y</code> : ''et'' ;
* <code lang="python">not x</code> : ''non'' ;
* <code lang="python">all([x, y, z])</code> : ''et'' appliqué à une liste ;
* <code lang="python">any([x, y, z])</code> : ''ou'' appliqué à une liste.
{{note |Les fonctions <code>all()</code> et <code>any()</code> renvoient un seul et unique booléen. Si l'on veut appliquer un opérateur élément par élément à toute une liste, par exemple prendre la négation de chaque élément de la liste, il faut utiliser les fonctions de type <code>numpy.logical_''xxx''</code> de la bibliothèque NumPy :
* <code>np.logical_or(a, b)</code> ;
* <code>np.logical_and(a, b)</code> ;
* <code>np.logical_xor(a, b)</code> ;
* <code>np.logical_nor(a)</code>.}}
== Notes et références ==
{{références}}
* {{lien web
| url = https://docs.scipy.org/doc/numpy/reference/ufuncs.html
| titre = Universal functions (ufunc)
| site = Numpy and Scipy Documentation
| lang = en
| consulté le = 2019-05-18
}}
----
[[../Premiers programmes|Premiers programmes]] < [[../|↑]] > [[../Éléments de programmation|Éléments de programmation]]
{{DEFAULTSORT:Fonctions mathematiques generales}}
[[Catégorie:Python pour le calcul scientifique (livre)]]
pgwbh46slzmp3itik8eu9ccc924iecx
Fonctionnement d'un ordinateur/Les circuits de décalage et de rotation
0
75477
768622
768130
2026-06-25T15:22:25Z
Mewtow
31375
/* Le barrel shifter de l'Intel 386 */
768622
wikitext
text/x-wiki
Dans ce chapitre, nous allons voir des opérations appelées les décalages et les rotations. Nous allons voir ce que sont ces opérations, puis les nombreux circuits qui permettent d'implémenter ces opérations. Mais expliquons d'abord les différentes opérations de décalage et de rotation.
==Les opérations de décalage==
Les ''décalages'' décalent un nombre de un ou plusieurs rangs vers la gauche, ou la droite. Il existe plusieurs opérations de décalage différentes et on peut les classer en plusieurs types. Dans les grandes lignes, on distingue les rotations, les décalages logiques et les décalages arithmétiques. Elles se distinguent sur plusieurs points, les principaux étant les suivants :
* ce qu'on fait des bits qui sortent du nombre lors du décalage ;
* comment on remplit les vides qui apparaissent lors du décalage ;
* la manière dont est géré le signe du nombre décalé.
[[File:Décalages, gestion des bits entrants et sortants.png|vignette|Décalages, gestion des bits entrants et sortants]]
Pour comprendre les deux premiers points, prenons l'exemple ci-contre. L'exemple montre le décalage de deux rangs vers la droite, d'un opérande de 8 bits valant 01011101. On obtient 010111 : les deux bits de poids forts sont vides et les deux bits de poids faible (01) sortent du nombre. Et cela vaut pour tout décalage : d'un côté le décalage fait sortir des bits du nombre, de l'autre certains bits sont inconnus ce qui laisse des vides dans le nombre. Si on décale de n rangs, alors cela laissera n vides et fera sortir n bits. Ces deux points, la gestion des vides et des bits sortants, sont assez liés.
===Le différents types de décalages===
Au-delà de la distinction assez intuitive entre les décalages vers la gauche et vers la droite, parlons de ce qu'on fait des bits qui sortent du nombre lors du décalage. Que fait-on de ces bits ?
La première solution est de les faire rentrer de l'autre côté, de les remettre au début du nombre décalé. L'opération en question est alors appelée une '''rotation'''. Il existe des rotations à droite et à gauche.
{|
|MSB : bit de poids fort
(Most Significant Bit)
LSB : bit de poids faible
(Least Significant Bit)
|
|[[File:Rotate left.svg|vignette|Rotation à gauche.]]
|[[File:Rotate right.svg|vignette|Rotation à droite.]]
|}
L'autre solution est d'oublier les bits sortants. L’opération est alors appelée un '''décalage''', qui peut être soit un décalage logique, soit un décalage arithmétique. Le fait que l'on oublie les bits sortants fait que les vides ne sont pas remplis et qu'il faut trouver de quoi les combler. Et c'est là qu'on peut faire la distinction entre décalages logiques et arithmétiques.
Avec un '''décalage logique''', les vides sont remplis par des zéros, aussi bien pour un décalage à gauche et un décalage à droite.
{|
|[[File:Rotate left logically.svg|vignette|Décalage logique à gauche.]]
|[[File:Rotate right logically.svg|vignette|Décalage logique à droite.]]
|}
[[File:Shift Arithmetic Right.svg|vignette|Décalage arithmétique à droite.]]
Avec un '''décalage arithmétique''', la situation est différente pour un décalage à gauche et à droite. Le principe des décalages arithmétique est qu'ils conservent le bit de signe de l'opérande décalé (qui est supposé être signé), contrairement aux autres décalages. Pour un décalage à droite, les vides dans les vides de poids forts sont remplis par le bit de signe. Ce remplissage est une sorte d'extension de signe, ce qui fait que la conservation du signe est automatique.
[[File:Shift Left and Shift Arithmetic Left.svg|vignette|Décalage arithmétique à gauche qui ne conserve pas le bit de signe.]]
Pour un décalage à gauche, les vides sont remplis par des zéros, comme pour un décalage logique. Mais pour ce qui est de la conservation du bit de signe, c'est plus compliqué. On a deux écoles : la première ne conserve pas le bit de signe, la seconde le fait. Dans le premier cas, le décalage est identique à un décalage logique à gauche. Dans le second cas, le bit de signe n'est pas concerné par le décalage et il se produit une forme particulière de débordement d'entier.
L'utilité principale des opérations de décalage est qu'elles permettent de faire simplement des multiplications ou divisions par une puissance de 2. Un décalage logique/arithmétique correspond à une multiplication ou division entière par 2^n : multiplication pour les décalages à gauche, division pour les décalages à droite. Les décalages logiques fonctionnent seulement pour les entiers non signés, alors que les décalages arithmétiques fonctionnent sur les entiers signés. Le fait est qu'un décalage logique ne préserve pas le bit de signe.
[[File:Modulo et quotient d'une division par une puissance de deux en binaire.png|centre|vignette|upright=2.5|Modulo et quotient d'une division par une puissance de deux en binaire]]
===Les arrondis lors des décalages===
Les décalages à droite entraînent l'apparition d{{'}}''arrondis''. Lorsqu'on effectue un décalage à droite, les bits qui sortent du résultat sont perdus. L’équivalent en décimal est que les chiffres après la virgule sont perdus, ce qui arrondit le résultat. Mais cet arrondi dépend de la représentation des nombres utilisé. Pour comprendre pourquoi, il faut faire un rapide rappel sur les types d'arrondis en décimal.
En décimal, on peut arrondir de deux manières : soit on arrondit à l'entier au-dessus, soit on arrondi à l'entier au-dessous. Par exemple, prenons la division 29/4, qui a pour résultat 7.25. Cela donne 7 dans le premier cas et 8 dans le second. Pour un résultat négatif, c'est la même chose, mais le fait que le signe soit inversé change la donne. Par exemple, prenons le résultat de -29 / 4, soit -7.25. On peut l'arrondir soit à -7, soit à -8. En combinant les deux cas négatifs avec les deux cas positifs, on se trouve face à quatre possibilités :
* l'arrondi vers la plus basse valeur absolue (vers zéro), qui donne respectivement 7 et -7 dans l'exemple précédent.
* l'arrondi vers la plus basse valeur (vers moins l'infini), qui donne -8 et 7 dans l'exemple précédent ;
* l'arrondi vers la plus haute valeur (vers plus l'infini), qui donne -7 et 8 dans l'exemple précédent ;
* l'arrondi vers la plus haute valeur absolue (vers l'infini), qui donne 8 et -8 dans l'exemple précédent.
En binaire, c'est la même chose. Par exemple, 11100,1010 peut s'arrondir en 11100 ou en 11101, suivant qu'on arrondisse vers le bas ou vers le haut, et la même chose est possible pour les nombres négatifs. Vu que les bits sortants sont simplement éliminés, on pourrait croire que cela correspond à un arrondi vers zéro (vers la valeur inférieure). C'est bien le cas pour les décalages logiques, peu importe la représentation, l'arrondi se fait vers zéro (vu que tous les nombres sont traités comme positifs). Mais pour les décalages arithmétiques, tout dépend de la représentation binaire utilisée. L'arrondi se fait bien vers zéro en complément à 1, mais pas en complément à deux, où l'arrondi se fait à la valeur inférieure, vers moins l'infini.
Précisons que ces arrondis n'ont lieu que si le résultat du décalage n'est pas exact. Pour un décalage d'un rang, à savoir une division par deux, seuls les nombres impairs donnent un arrondi, pas les nombres pairs. De manière générale, pour un décalage de n rangs, les nombres divisibles par 2^n ne donnent pas d'arrondi, alors que les autres si.
===Les débordements d'entiers lors des décalages===
Les décalages peuvent aussi causer des ''débordements d'entier''. Pour rappel un débordement d'entier est une situation où le résultat d'un calcul devient trop gros pour être codé. Pour donner un exemple, prenons une situation équivalente mais en décimal. Par exemple supposons que l'ordinateur sur lequel vous travailler manipule des données codées sur 5 chiffres décimaux, pas plus. Si on prend le nombre 4512, le décalage à gauche d'un cran donne 45120, qui tient sur 5 chiffres : on n'a pas de débordement. Mais si je prends le nombre 97426, un décalage à gauche d'un cran donne 974260, ce qui ne tient pas dans 5 chiffres : on a un débordement d’entier. Celui-ci se traduit par le fait qu'un chiffre non-nul sorte du nombre. La même chose a lieu en binaire, avec les décalages à gauche : si au moins un bit non-nul sorte à gauche, c'est un débordement d'entier.
La manière habituelle de gérer les débordements d'entiers est simplement de ne rien faire, mais de prévenir qu'un débordement a eu lieu. Pour cela, le circuit qui effectue le décalage a une sortie qui indique qu'un débordement a eu lieu lors du décalage. Cette sortie fournit un simple bit qui vaut 1 en cas de débordement et 0 sinon (ou l'inverse). Une autre solution est de corriger le débordement, mais elle est utilisée uniquement pour les opérations arithmétiques, pas pour les décalages.
Toujours est-il que déterminer l’occurrence d'un débordement n'est pas compliqué. Pour les décalages logiques, il suffit de prendre les bits sortants et de vérifier qu'un au moins d'entre eux vaut 1. Une simple porte OU sur les bits sortants fait l'affaire. Pour les décalages arithmétiques, il faut aussi tenir compte de la présence du bit de signe. Si le nombre décalé est positif, seuls des zéros doivent sortir, la présence d'un 1 indiquant un débordement d'entier. Pour un nombre négatif, c'est l'inverse : seuls des 1 doivent sortir (du fait des règles d'extension de signe), alors que l’occurrence d'un zéro trahit un débordement d'entier. Pour résumer le tout, les bits sortants sont censés être égaux au bit de signe, un débordement a eu lieu dans le cas contraire. L’occurrence d'un débordement se détermine en décomposant le décalage en une succession de décalages de 1 bit. Si un seul de ces décalages de 1 rang altère le bit de signe (change sa valeur), alors on a un débordement.
Il est possible de déterminer l’occurrence d'un débordement en analysant l'opérande, sans même avoir à faire le décalage. Pour un décalage vers la gauche de <math>n</math> rangs, on sait que les bits sortants sont les <math>n</math> bits de poids fort de l'opérande. En clair, on peut déterminer si un débordement a lieu en sélectionnant seulement les <math>n</math> bits de poids fort de l'opérande. Pour cela, on peut simplement prendre l'opérande et lui appliquer un masque adéquat. Par exemple, prenons le cas d'un débordement pour un décalage logique, qui a lieu si au moins un bit sortant est à 1. Il suffit de prendre l'opérande, conserver les <math>n</math> rangs bits de poids fort et mettre les autres à zéro, puis faire un ET entre les bits du résultat. La même logique prévaut pour les décalages arithmétiques, même s'il faut faire quelques adaptations.
[[File:Calcul du bit de débordement pour un décalage à gauche de trois rangs.png|centre|vignette|upright=2|Calcul du bit de débordement pour un décalage à gauche de trois rangs.]]
Toujours est-il que le calcul des débordements peut se faire en parallèle du décalage, ce qui est utile. Précisons que le masque se calcule dans un circuit à part, qui ressemble beaucoup à un encodeur. Le masque calculé peut être utilisé sur certains circuits de décalages, pour transformer des rotations en décalage logiques, par exemple. Mais nous verrons cela plus tard.
==Les décaleurs et rotateurs élémentaires==
[[File:Décaleur - interface.png|vignette|Décaleur - interface]]
Pour commencer, nous allons voir deux types de circuits : les '''décaleurs''' qui effectuent un décalage (logique ou arithmétique, peu importe) et les '''rotateurs''' qui effectuent une rotation. Les deux circuits sont conceptuellement séparés, même s’ils se ressemblent. Faire la distinction sera utile dans la suite du cours. Leur interface est la même pour tous les décaleurs et rotateurs élémentaires. On doit fournir l'opérande à décaler et le nombre de rangs qu'on veut décaler en entrée, et on récupère l'opérande décalé en sortie.
Nous allons d'abord voir comment créer un décaleur. Pour cela, on peut faire une remarque simple : décaler de 6 rangs, c'est équivalent à décaler de 4 rangs et redécaler le tout de 2 rangs. Même chose pour 7 rangs : cela consiste à décaler de 4 rangs, redécaler de 2 rangs et enfin redécaler d'un rang. En suivant l'idée jusqu'au bout, on peut créer un décaleur à partir de décaleurs plus simples, reliés en cascade, qu'on active ou désactive suivant le nombre de rangs. Les décaleurs élémentaires décalent par 1, 2, 4, 8, etc ; bref : par une puissance de 2. La raison à cela est que le nombre de rangs par lequel on va devoir décaler est un nombre codé en binaire, qui s'écrit donc sous la forme d'une somme de puissances de deux. Le énième bit du nombre de rang servira à actionner le décaleur par 2^n.
[[File:Décaleur logique - principe.png|centre|vignette|upright=2|Décaleur logique - principe]]
La même logique s'applique pour les rotateurs, la seule différence étant qu'il faut remplacer les décaleurs par 1, 2, 4, 8, etc ; par des rotateurs par 1, 2, 4, 8, etc. Reste à savoir comment créer ces décaleurs qu'on peut activer ou désactiver à la demande. Surtout que le circuit n'est pas le même selon que l'on parle d'un décalage logique, d'un décalage arithmétique ou d'une rotation. Néanmoins, tous les circuits de décalage/rotation sont fabriqués avec des multiplexeurs à deux entrées et une sortie.
===Le circuit décaleur logique===
Commençons par étudier le cas du décalage logique par 4 rangs à droite. La sortie vaudra soit le nombre tel qu'il est passé en entrée (le décaleur est inactif), soit le nombre décalé de 4 rangs. Ainsi, si je prends un nombre A, composé des bits a7, a6, a5, a4, a3, a2, a1, a0 ; (cités dans l'ordre), le résultat sera :
* soit le nombre composé des chiffres a7, a6, a5, a4, a3, a2, a1, a0 (on n'effectue pas de décalage) ;
* soit le nombre composé des chiffres 0, 0, 0, 0, a7, a6, a5, a4 (on effectue un décalage par 4).
Chaque bit de sortie peut prendre deux valeurs, qui valent soit zéro, soit un bit du nombre d'entrée. On peut donc utiliser un multiplexeur pour choisir quel bit envoyer sur la sortie. Par exemple, pour le choix du bit de poids fort du résultat, celui-ci vaut soit a7, soit 0 : il suffit d’utiliser un multiplexeur prenant le bit a7 sur son entrée 1, et un 0 sur son entrée 0.
[[File:Décaleur par 4.png|centre|vignette|upright=2|Exemple d'un décaleur par 4.]]
Le tout peut être adapté pour créer des décaleurs par 1, par 2, par 8, etc. Il suffit de faire la même chose pour tous les autres bits, et le tour est joué. En utilisant des décaleurs basiques par 4, 2 et 1 bit, on obtient le circuit suivant :
[[File:Décaleur logique 8 bits.png|centre|vignette|upright=2|Décaleur logique 8 bits.]]
===Le circuit décaleur arithmétique===
Les décalages arithmétiques sont basés sur le même principe, à une différence près : on n'envoie pas un zéro dans les bits de poids fort, mais le bit de signe (le bit de poids fort du nombre d'entrée). Un décaleur arithmétique ressemble beaucoup à un décaleur logique, la seule différence étant que c'est le bit de poids fort qui est relié aux entrées des multiplexeurs, là où c'était le zéro avec le décaleur logique. Par exemple, reprenons un nombre A, composé des bits a7, a6, a5, a4, a3, a2, a1, a0 ; (cités dans l'ordre). La sortie d'un décaleur arithmétique par 4 sera :
* soit le nombre composé des chiffres a7, a6, a5, a4, a3, a2, a1, a0 (on n'effectue pas de décalage) ;
* soit le nombre composé des chiffres a7, a7, a7, a7, a7, a6, a5, a4 (on effectue un décalage arithmétique par 4).
[[File:Décaleur arithmétique par 4.png|centre|vignette|upright=2|Exemple d'un décaleur arithmétique par 4]]
En combinant des décaleurs basiques par 4, 2 et 1 bits, on obtient le circuit suivant :
[[File:Décaleur arithmétique 8 bits.png|centre|vignette|upright=2|Décaleur arithmétique 8 bits]]
===Le circuit rotateur===
Les rotations sont elles aussi basées sur le même principe, sauf que ce sont les bits de poids faible qu'on injecte dans les bits de poids forts, au lieu d'un zéro ou du bit de signe. Le circuit est donc le même, sauf que les connexions ne sont pas identiques. Là où il y avait un zéro sur les entrées des multiplexeurs, on doit envoyer le bon bit de poids faible. Par exemple, reprenons un nombre A, composé des bits a7, a6, a5, a4, a3, a2, a1, a0 ; (cités dans l'ordre). La sortie d'un rotateur arithmétique par 4 sera :
* soit le nombre composé des chiffres a7, a6, a5, a4, a3, a2, a1, a0 (on n'effectue pas de décalage) ;
* soit le nombre composé des chiffres a3, a2, a1, a0, a7, a6, a5, a4 (on effectue un décalage arithmétique par 4).
==Les ''barell shifters'' unidirectionnels==
[[File:Barrel shifter - interface.png|vignette|Barrel shifter - interface]]
Dans ce qui précède, on a appris à créer un circuit qui fait des décalages logiques, un autre pour les décalages arithmétiques et un autre pour les rotations. Il nous reste à voir les '''décaleurs-rotateurs''', aussi appelés des '''''barrel shifters''''', qui sont capables de faire à la fois des décalages et des rotations. Certains décaleur-rotateurs sont capables de faire des rotations et des décalages logiques, d'autres savent aussi réaliser les décalages arithmétiques en plus. Un tel circuit a la même interface qu'un décaleur, sauf qu'on rajoute une entrée qui précise quelle opération faire. Cette entrée indique s'il faut faire un décalage logique, un décalage arithmétique ou une rotation.
Précisons dès maintenant qu'il faut faire la différence entre un ''barrel shifter'' unidirectionnel et un ''barrel shifter'' bidirectionnel. La différence entre les deux tient dans le sens possible des décalages. Le ''barrel shifter'' unidirectionnel ne peut faire que des décalages à gauche ou que des décalages à droite, mais pas les deux. À l'inverse, un ''barrel shifter'' bidirectionnel peut faire des décalages à droite et à gauche, suivant ce qu'on lui demande. Dans cette section, nous allons nous concentrer sur les ''barrel shifters'' unidirectionnels, qui font des décalages/rotations vers la droite. Les explications seront valides aussi pour des décalages/rotations à gauche, avec quelques petites modifications triviales.
Il existe trois grandes méthodes pour fabriquer un décaleur-rotateur.
* La manière la plus naïve est de prendre un décaleur logique, un décaleur arithmétique et un rotateur, et de prendre le résultat adéquat suivant l’opération voulue. Le choix du bon résultat est effectué par une couche de multiplexeur adaptée. Mais cette solution est inutilement gourmande en multiplexeurs. Après tout, les trois circuits se ressemblent et partagent une même structure.
* Une autre solution, bien plus économe en multiplexeurs, élimine ces redondances en fusionnant les trois circuits en un seul. Elle part d'un circuit qui effectue des décalages logiques, auquel on ajoute des multiplexeurs pour le rendre capable de faire aussi les décalages arithmétiques et les rotations.
* La dernière méthode part d'un rotateur et on lui ajoute de quoi faire des décalages logiques.
===Le décaleur-rotateur à base de multiplexeurs===
Avec la seconde méthode, on part d'un circuit qui effectue des décalages logiques, auquel on ajoute des multiplexeurs pour le rendre capable de faire aussi les décalages arithmétiques et les rotations. Ces nouveaux multiplexeurs ne font que choisir les bits à envoyer sur les entrées des décaleurs. Par exemple, prenons un décalage/rotation par 4 crans. La seule différence entre décalage logique, arithmétique et rotation est ce qu'on met sur les 4 bits de poids fort : un 0 pour un décalage logique, le bit de poids fort pour un décalage arithmétique et les 4 bits de poids faible pour une rotation. Pour choisir entre ces trois valeurs, il suffit de rajouter des multiplexeurs.
Nous allons d'abord ajouter des multiplexeurs pour prendre en charge les rotations, un peu de la même manière qu'on modifie un décaleur logique pour lui faire faire aussi des décalages arithmétiques. Pour cela, prenons un décaleur par 4 et étudions les 4 bits de poids fort. Suivant le type de décalage, on doit envoyer soit un zéro, soit le bit de poids faible adéquat sur certaines entrées. Ce choix peut être réalisé par un multiplexeur, tant qu'il est commandé correctement. En clair, il suffit d'ajouter un ou plusieurs multiplexeurs pour chaque décaleur élémentaire par 1, 2, 4, etc. Ces multiplexeurs choisissent quoi envoyer sur l'entrée de l'ancienne couche : soit un 0 (décalage logique), soit le bit de poids faible (rotation). Notons qu'on doit utiliser un multiplexeur par entrée, contrairement au décaleur complet. La raison est qu'un décalage arithmétique envoie toujours le même bit dans les entrées de poids fort, alors qu'une rotation envoie un bit différent sur chaque entrée de poids fort, ce qui demande un multiplexeur par entrée.
[[File:Décaleur-rotateur par 4.png|centre|vignette|upright=2|Décaleur-rotateur par 4.]]
Il est possible d'étendre le décaleur logique pour lui permettre de faire des décalages arithmétiques. Pour cela, même recette que dans le cas précédent. Encore une fois, suivant le type de décalage, on doit envoyer soit un zéro, soit le bit de poids fort sur certaines entrées. Il est possible d'utiliser un seul multiplexeur dans ce cas précis, car on envoie le même bit sur les entrées de poids fort.
[[File:Exemple avec un décaleur par 4.png|centre|vignette|upright=2|Exemple avec un décaleur par 4.]]
En combinant des décaleurs basiques par 4, 2 et 1 bits, on obtient un circuit qui fait tous les types de décalages. Pas étonnant que ce circuit soit nommé un '''décaleur complet'''. Notons qu'on peut se contenter d'un seul mutiplexeur pour tout le ''barrel shifter'', en utilisant le câblage astucieusement. Après tout, le choix entre 0 ou bit de poids fort est le même pour toutes les entrées concernées. Autant ne le faire qu'une seule fois et connecter toutes les entrées concernées au multiplexeur.
[[File:Décaleur complet 8 bits.png|centre|vignette|upright=2|Décaleur complet 8 bits]]
En utilisant les deux modifications en même temps, on se retrouve avec un ''barrel-shifter'' complet, capable de faire des décalages et rotations sur 4 bits.
[[File:Circuit de rotation partiel.png|centre|vignette|upright=2|Circuit de rotation partiel.]]
===Les ''mask barrel shifters''===
Les ''mask barrel shifters'' sont des décaleurs-rotateurs basés autour d'un rotateur, qui est modifié afin de supporter les décalages logiques/arithmétiques. L'idée est de faire une rotation et de corriger le résultat si c'est un décalage qui est demandé. La correction à effectuer dépend du type de décalage demandé, suivant qu'il soit logique ou arithmétique.
Pour un décalage logique, il suffit de mettre les n bits de poids fort à zéro pour un décalage de n bits vers la droite (inversement, les n bits de poids faible pour un décalage vers la gauche). Et pour mettre des bits de poids fort à zéro sous une certaine condition, on doit utiliser un masque qui est calculé par un circuit dédié. Le circuit de calcul du masque est un encodeur modifié, qu'on peut concevoir avec les techniques des chapitres précédents.
Le circuit qui combine le masque avec le résultat de la rotation est composé d'une couche de portes ET et d'une couche de multiplexeurs. La couche de portes ET applique le masque sur le résultat du rotateur. Les multiplexeurs choisissent entre le résultat du rotateur et le résultat avec masque appliqué. Les multiplexeurs sont commandés par un bit de commande qui indique s'il faut faire un décalage ou une rotation.
[[File:Décaleur-rotateur basé sur un masque.png|centre|vignette|upright=1.5|Décaleur-rotateur basé sur un masque.]]
==Les ''barrel shifters'' bidirectionnels (à double sens de décalage/rotation)==
Le circuit précédent est capable d'effectuer des décalages et rotations, mais seulement vers la droite. On peut évidemment concevoir un circuit similaire capable de faire des décalages/rotations vers la gauche, mais il est intéressant d'essayer de créer un circuit capable de faire les deux. Un tel circuit est appelé un '''''barrel shifter'' bidirectionnel'''. Notons qu'on doit obligatoirement fournir un bit qui indique dans quelle direction faire le décalage. Précédemment, nous avons vu qu'il existe deux méthodes pour créer un ''barrel shifter''. La première se base sur un décaleur auquel on ajoute de quoi faire les rotations, alors que l'autre se base sur l'application d'un masque en sortie d'un rotateur. Dans ce qui va suivre, nous allons voir comment ces deux types de circuits peuvent être rendus bidirectionnels.
[[File:Barrel shifter bidirectionnel - interface.png|centre|vignette|upright=2|Barrel shifter bidirectionnel - interface]]
===Les ''barrel shifters'' bidirectionnels basé sur des multiplexeurs===
Commençons par voir comment rendre bidirectionnel un ''barrel shifter'' basé sur des multiplexeurs. Pour rappel, ces derniers sont basés sur un décaleur qu'on rend capable de faire des rotations en ajoutant des multiplexeurs.
Une première solution est d'utiliser des '''''barrel shifters'' bidirectionnels série''', série signifiant que les deux sens sont calculés en série, l'un après l'autre. Ils sont composés de décaleurs qui sont capables de faire des décalages/rotations vers la gauche et vers la droite. De tels décaleurs peuvent se concevoir de diverses façons, mais la plus simple se base sur le principe qui veut qu'un décaleur est composé de décaleurs de 1, 2, 4, 8 bits, etc. Chaque décaleur est en double : une version qui décale vers la gauche, et une autre qui décale vers la droite. Lors d'un décalage vers la droite, les décaleurs élémentaire à gauche sont désactivés alors que les décaleurs vers la droite sont actifs (et réciproquement lors d'un décalage à gauche). Le bit qui indique la direction du décalage est envoyé à chaque décaleur et lui indique s'il doit décaler ou non.
[[File:Décaleur bidirectionnel.png|centre|vignette|upright=2|Décaleur bidirectionnel]]
Une autre solution, bien plus simple, est de prendre un décaleur/rotateur vers la gauche et un autre vers la droite, et de prendre la sortie adéquate en fonction de l'opération demandée. Le choix du résultat se fait encore une fois avec une couche de multiplexeurs. Le résultat est ce qu'on appelle un '''''barrel shifter'' bidirectionnel parallèle''', parallèle signifiant que les deux sens sont calculés en parallèle, en même temps. Notons que cette solution ressemble beaucoup à la précédente. À vrai dire, si on prend la première solution et qu'on regroupe ensemble les décaleur/rotateurs allant dans la même direction, on retombe sur un circuit presque identique à un ''barrel shifter'' bidirectionnel parallèle.
Les deux techniques précédentes utilisent beaucoup de portes logiques et il est possible de faire bien plus efficace. L'idée est simplement d'inverser l'ordre des bits avant de faire le décalage ou la rotation, puis de remettre le résultat dans l'ordre. Par exemple, pour faire un décalage à gauche, on inverse les bits du nombre à décaler, on fait un décalage à droite, puis on remet les bits dans l'ordre originel, et voilà ! Pour cela, il suffit de prendre un décaleur/rotateur à droite, et d'ajouter deux circuits qui inversent l'ordre des bits : un avant le décaleur/rotateur, un après. Ce circuit d'inversion est une simple couche de multiplexeurs. Le résultat est ce qu'on appelle un '''''barrel shifter'' bidirectionnel à inversion de bits'''.
[[File:Barrel shifter à inversion de bits.png|centre|vignette|upright=1.5|Barrel shifter à inversion de bits.]]
===Le décaleur-rotateur bidirectionnel basé sur des masques===
Dans cette section, nous allons voir concevoir un rotateur bidirectionnel avec des masques. Pour cela, il faut juste créer un rotateur bidirectionnel et utiliser des masques pour obtenir des décalages.
Pour créer le rotateur bidirectionnel, nous allons devoir étudier ce qui se passe quand on enchaine deux rotations successives. N'allons pas par quatre chemins : l'enchainement de deux rotations successives donne un résultat qui aurait pu être obtenu en ne faisant qu'une seule rotation. Par exemple, faire une rotation à droite par 5 rangs suivie d'une rotation à droite de 8 rangs est équivalent à faire une rotation à droite de 5+8 rangs, soit 13 rangs. Le résultat issu de la succession de deux rotations est identique à celui d'une ''rotation équivalente''. Et on peut calculer le nombre de rangs de la rotation équivalente à partir des rangs des deux rotations initiales. Pour cela, il suffit d'additionner les rangs en question.
La logique est la même quand on enchaine des rotations à droite et à gauche. Il suffit de compter les rangs d'une rotation en les comptant positifs pour une rotation à droite et négatifs pour une rotation à gauche. Par exemple, une rotation de -5 rangs sera une rotation à gauche de 5 rangs, alors qu'une rotation de 10 rangs sera une rotation à droite de 10 rangs. On pourrait faire l'inverse, mais prenons cette convention pour l'explication qui suit. Toujours est-il qu'avec cette convention, l'addition des rangs donne le bon résultat pour la rotation équivalente. Par exemple, si je fais une rotation à droite de 15 rangs et une rotation à gauche de 6 rangs, le résultat sera une rotation de 15-6 rangs : c'est équivalent à une rotation à droite de 9 rangs.
Faisons dès maintenant remarquer quelque chose d'important. Prenons un nombre de n bits. Avec un peu de logique et quelques expériences, on remarque facilement qu'une rotation par <math>n</math> ne fait rien, dans le sens où les bits reviennent à leur place initiale. Une rotation par <math>n</math> est donc égale à pas de rotation du tout, ce qui est équivalent à faire une rotation par zéro rangs.
Pour le moment, ce détail nous permet de gérer le cas où l'addition de deux rangs donne un résultat supérieur à <math>n</math>. Par exemple, prenons une rotation par 56 rangs pour un nombre de 9 bits. La division nous dit que 56 = 9*6 + 2. En clair, faire un décalage par 56 rangs est équivalent à faire 6 rotations totales par 9, suivie d'une rotation par 2 rangs. Les rotations par 9 ne comptant pas, cela revient en fait à faire une rotation par 2 rangs. Le même raisonnement fonctionne dans le cas général, et revient à faire ce qu'on appelle une '''addition modulo n'''. C'est à dire qu'une fois le résultat de l'addition connu, on le divise par <math>n</math> et l'on garde le reste de la division. Avec cette méthode, le nombre de rangs de la rotation équivalente est compris entre 0 et <math>n-1</math>.
: ''Les additions modulo n seront notées comme suit : <math>(a+b)\mod n</math>.''
Armé de ces explications, on peut maintenant expliquer comment fonctionne le rotateur bidirectionnel. L'idée derrière ce circuit est de remplacer une rotation à droitepar une rotation à gauche équivalente (ou inversement, mais nous allons supposer que le rotateur fait des rotations vers la gauche). Dans ce qui suit, nous utiliserons la notation suivante : <math>r_\text{équivalent}</math> est le nombre de rangs de la rotation équivalente, <math>n</math> la taille du nombre à décaler et <math>r</math> le nombre de rangs du décalage initial. En soi, ce n'est pas compliqué de trouver une rotation équivalente : une rotation à droite de <math>r</math> rangs est équivalente à une rotation de <math>r + n</math> rangs, à une rotation de <math>r + 2 \times n</math> rangs, et de manière générale à toute rotation de <math>r + k \times n</math> rangs. La raison est que les rotations par n ne comptent pas, elles sont éliminées par la division par <math>n</math>. Pour résumer, on a :
: <math>r_\text{équivalent} = (r \pm k \times n)\mod n</math>
Ls propriétés des calculs modulo n font que cela marche aussi quand on retranche n. Les bizarreries de l'arithmétique modulaire font que, quand on fait les additions modulo n, on peut remplacer tout nombre positif r par <math>r \pm k \times n</math> sans changer les résultats. Mais tous les cas possibles ne nous intéressent pas. En effet, on sait que le nombre de rangs de la rotation équivalente est compris entre 0 et <math>n-1</math>. Le résultat que l'on recherche doit donc être compris entre 0 et <math>n-1</math>. Et seul un cas respecte cette contrainte : celui où l'on retranche n une seule fois. On a alors :
: <math>r_\text{équivalent} = r - n</math>
L'équation nous dit qu'il est possible de remplacer une rotation à droite par une rotation à gauche équivalente. Par exemple, sur 8 bits et pour une rotation à droite de 6 bits, on a <math>r_\text{équivalent} = 6 - 8 = -2</math>. En clair, la rotation équivalente est ici une rotation à gauche de 2 crans. Vous pouvez essayer avec d'autres exemples, vous trouverez la même chose. Par exemple, sur 16 bits, une rotation à gauche de 3 rangs est équivalente à une rotation à droite de 13 rangs.
Le calcul ci-dessus peut être simplifié en utilisant quelques astuces. Sur la plupart des ordinateurs, n est égal à 8, 16, 32, 64, ou toute autre nombre de la forme <math>2^n</math>. Les cas où n vaut 3, 7, 14 ou autres sont tellement rares que l'on peut les considérer comme anecdotiques. De plus, <math>r</math> est compris entre 0 et <math>n-1</math>. On peut donc coder le rang sur un nombre bien précis de bits, tel que n est la valeur haute de débordement (en clair, n-1 est la plus grande valeur codable, n entraine un débordement d'entier). Grâce à cela, on peut coder le nombre de rangs en complément à un ou en complément à deux. Rappelons que ces deux représentations des nombres utilisent l'arithmétique modulaire, c'est à dire que l'addition et la soustraction se font modulo n, et que leur principe est de représenter tout n négatif par un n positif équivalent. Ainsi, tout <math>r_\text{équivalent}</math> négatif est codé par un <math>r</math> positif équivalent. Et dans ces représentations, on a obligatoirement <math>r - n = - r</math>. En appliquant cette formule dans l'équation précédente, on a :
: <math>r_\text{équivalent} = - r</math>
Reprenons l'exemple d'une rotation à gauche de 2 crans pour un nombre de 8 bits, ce qui est équivalent à une rotation de 6 crans à droite: on a bien 6 = -2 en complément à deux. Reste à faire le calcul ci-dessus par le circuit de rotation.
En complément à un, le calcul de l'opposé d'un nombre consiste simplement à inverser les bits de <math>r_\text{initial}</math>. En conséquence, le circuit est plus simple en complément à un. Le calcul du nombre de rangs demande juste un inverseur commandable, qu'on sait fabriquer depuis quelques chapitres.
[[File:Rotateur bidirectionnel en complèment à un.png|centre|vignette|upright=2|Rotateur bidirectionnel en complément à un.]]
En complément à deux, le calcul est le suivant :
: <math>r_\text{équivalent} = \overline{r_\text{initial}} + 1</math>
On pourrait utiliser un circuit pour faire l'addition, mais il y a une autre manière plus simple de faire. L'idée est simplement de prendre le circuit en complément à un et d'y ajouter de quoi corriger le résultat final. En clair, on fait le calcul comme en complément à un, mais la rotation effectuée ne sera pas équivalente, du fait du +1 dans le calcul. Ce +1 indique simplement qu'il faut décaler le résultat obtenu d'un cran supplémentaire. Pour cela, on ajoute un rotateur d'un cran à la fin du circuit.
[[File:Rotateur bidirectionnel en complément à deux.png|centre|vignette|upright=2|Rotateur bidirectionnel en complément à deux.]]
On peut transformer ce circuit en décaleur-rotateur en appliquant la méthode vue plus haut, à savoir en appliquant un masque en sortie du rotateur. Le circuit obtenu est le suivant :
[[File:Décaleur rotateur bidirectionnel basé sur un masque.png|centre|vignette|upright=2|Décaleur rotateur bidirectionnel basé sur un masque.]]
==Le ''barrel shifter'' de l'Intel 386==
Le ''barrel shifter'' de l'Intel 386 est différent des ''barrel shifter'' vus précédemment. Il gère nativement décalages, rotations, et quelques autres opérations. Il a pour particularité de faire toutes ses opérations à partir de décalages à droite. Et pour que cela fonctionne, il manipule ses opérandes d'une manière assez inédite.
Pour comprendre pourquoi, précisons que l'Intel 386 est le tout premier processeur 32 bits d'Intel. Intuitivement, on se dirait que son ''barrel shifter'' prend un opérande de 32 bits, et fournit une sortie de 32 bits. Mais la réalité est qu'il prend un opérande de 64 bits, répartie dans deux registres de 32 bits chacun. Le ''barrel shifter'' est de 64 bits. La sortie du circuit correspond aux 32 bits de poids faible du résultat du décalage.
{|class="wikitable"
|-
! Registre 32 bits !! Registre 32 bits
|-
| colspan="2" | ''Barrel Shifter'' hybride 64 - 32 bits
|-
! colspan="2" | Sortie 32 bits
|}
Ce décaleur hybride 64-32 bits était composé de deux sous-circuits placés en série. Le premier décale le résultat par paquets de 4 rangs, à savoir de 0, 4, 8, 12, 16, 20, 24 ou 28 rangs. Le second décale le résultat du premier de 0, 1, 2 ou 3 rangs. Faire ainsi économise des transistors comparé à un ''barrel shifter'' usuel, du moins pour un ''barrel shifter'' hybride 64-32 bits. Et malgré cela, le ''barrel shifter'' faisait environ 2000 transistors, ce qui était énorme pour l'époque. Pour comparer, le processeur 6502 de Motorola tout entier faisait le double.
Pour un décalage à droite logique, le décalage se fait normalement. Les 32 bits de poids fort sont remplis par des zéros, les 32 bits de poids faible sont remplis avec l'opérande à décaler. Les décalages arithmétiques se font d'une manière similaire, la seule différence étant que les 32 bits de poids fort sont remplis avec le bit de signe.
Pour un décalage à gauche, la situation est inversée. L'opérande est placé dans les 32 bits de poids fort, alors que les zéros sont dans les 32 bits de poids fort. Le ''barrel shifter'' fait un décalage à droite, qui émule le décalage à gauche. Pour un décalage à gauche de N rangs, il est émulé par un décalage à droite de 32 − N rangs.
Pour les rotations, les 32 bits de poids fort sont remplis avec l'opérande, idem avec les bits de poids faible. Les rotations à droite se font avec un simple décalage à droite, les rotations à gauche se font avec le même décalage mais le nombre de rangs est altéré de la même manière qu'avec les décalages à droite.
{|class="wikitable"
|-
!
! 32 bits de poids fort !! 32 bits de poids faible
! Nombre de rangs du décalage
|-
! Décalage à droite logique (N rangs)
| 0 || Opérande
| N
|-
! Décalage à droite arithmétique (N rangs)
| Bit de signe x32 || Opérande
| N
|-
! Décalage à gauche (N rangs)
| Opérande || 0
| 32 - N
|-
! Rotation à droite (N rangs)
| Opérande || Opérande
| N
|-
! Rotation à gauche (N rangs)
| Opérande || Opérande
| 32 − N
|}
L'avantage d'un tel circuit est qu'il facilite l'implémentation de certaines opérations qu'on n'a pas encore abordées, comme des rotations avec retenue, ou l'opération ''Bit Test''. Le processeur gérait aussi nativement des décalages sur 64 bits, deux instructions étaient prévues pour. Ce n'était pas très utile pour un processeur 32 bits, mais l'implémentation était aisée, alors pourquoi pas ?
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les registres à décalage et les LSFR
| prevText=Les registres à décalage et les LSFR
| next=Les circuits pour l'addition et la soustraction
| nextText=Les circuits pour l'addition et la soustraction
}}
</noinclude>
3nrs7jsay19402t189mkfz60r9mlp21
Fonctionnement d'un ordinateur/Les compteurs et timers
0
78073
768598
768143
2026-06-25T14:27:06Z
Mewtow
31375
/* Les compteurs modulo */
768598
wikitext
text/x-wiki
Les '''compteurs/décompteurs''' sont des circuits électroniques qui mémorisent un nombre et l'incrémentent à la demande. En clair, ce sont des registres améliorés afin de supporter l'incrémentation et la décrémentation. Pour donner un exemple d'utilisation, imaginez un circuit qui compte le nombre de voitures dans un parking dans la journée. Pour cela, vous allez prendre deux circuits qui détectent respectivement l'entrée ou la sortie d'une voiture, et un compteur. Le compteur est initialisé à 0 quand le parking est vide, puis est incrémenté à chaque entrée de voiture, décrémenté à chaque sortie. Les exemples de ce type sont suffisamment nombreux pour qu'on dédie un chapitre aux compteurs.
[[File:Binary counter.gif|vignette|Illustration du fonctionnement d'un compteur modulaire binaire de 4 bits, avec un pas de compteur de 1 (le contenu est augmenté de 1 à chaque mise à jour).]]
Un compteur mémorise un nombre qui est incrémenté ou décrémenté au besoin. Le nombre mémorisé sera appelé le '''décompte''' dans ce qui suit. Il est mémorisé dans un registre à l'intérieur du compteur. Au passage, le nombre de bits <math>n</math> du compteur est appelé la '''taille du compteur''', par analogie avec les registres. Il faut cependant faire la différence entre les ''compteurs'' d'un côté et les ''décompteurs'' de l'autre. Les compteurs incrémentent le décompte, les décompteurs le décrémentent, les compteurs-décompteurs peuvent faire les deux.
==L'interface d'un compteur/décompteur==
Les compteurs et décompteurs sont des circuits synchrones et ont donc une entrée d'horloge. Les compteurs les plus simples incrémentent leur contenu à chaque cycle d'horloge, mais les plus fréquents n'incrémentent le décompte que sur demande. Pour cela, ils disposent d'une entrée '''''Count Enable''''', similaire à l'entrée ''Enable'' des registres, séparée de l'entrée d'horloge. Le décompte est incrémenté/décrémenté seulement si l'entrée Enable est à 1, lors d'un front adéquat sur le signal d'horloge.
Les compteurs ont aussi une entrée '''''Reset''''' qui permet de les remettre à zéro. Il y a parfois une entrée qui permet d'initialiser le compteur à une valeur par défaut, non-nulle. Par exemple, on peut initialiser le décompte à la valeur 5, ou une autre. Pour cela, le compteur dispose de deux entrées : une entrée sur laquelle envoyer le décompte initial, une entrée pour autoriser la réinitialisation. Les entrées en question sont appelées '''''Preload Data''''' et '''''Preload Enable'''''. La seconde entrée est parfois distincte de l'entrée de réinitialisation, pour permettre de réinitialiser le compteur soit à zéro, soit à la valeur voulue.
Il peut être utile de prévenir quand un débordement d'entier a lieu, à savoir quand le compteur n'a pas assez de bits pour encoder le décompte. Le compteur est alors remis à zéro, dans la plupart des cas. Mais il faut prévenir que le compteur a débordé, ce qui est utile pour fabriquer des circuits diviseurs de fréquence et des ''timers''. Pour cela, on ajoute une '''sortie de débordement''' au compteur, qui est mise à 1 quand le compteur déborde.
Sur les compteurs/décompteurs, il y a une entrée '''''Count Direction''''' qui décide s'il faut compter ou décompter. Typiquement, elle est à 1 s'il faut compter et 0 s'il faut décompter.
[[File:Digital counter signals.jpg|centre|vignette|upright=2|Interface d'un compteur-décompteur.]]
Dans les schémas qui vont suivre, les entrées Enable ne sont pas représentées. Il est sous-entendu qu'il y a une entrée ''Enable'' pour tous les compteurs qui vont suivre. Il existe deux méthodes pour créer de tels compteurs : la première donne ce qu'on appelle des compteurs asynchrones, et l'autre des compteurs synchrones.
==L'intérieur d'un compteur==
A une exception bien précise qu'on abordera plus bas, les compteurs sont composés d'un registre, qui mémorise le décompte, couplé à un circuit '''incrémenteur'''. Nous avions déjà abordé l'incrémenteur dans un chapitre précédent, aussi je ne vais pas ré-expliquer comment il est conçu. Tout ce qu'il faut retenir est qu'il y en a plusieurs types, le plus simple étant celui à propagation de retenue.
[[File:Fonctionnement d'un compteur (décompteur), schématique.jpg|centre|vignette|upright=2|Fonctionnement d'un compteur (décompteur), schématique. Le pas du compteur précise de combien on incrémente le compteur par cycle d'horloge.]]
L'incrémenteur le plus simple, à propagation de retenue, est fabriqué en enchainant des ''demi-additionneurs'' les uns à la suite des autres. Pour rappel, un demi-additionneur additionne deux bits. Ici, il additionne un bit de l'opérande, la retenue des colonnes précédentes. Pour le bit de poids faible, la retenue est forcé à 1. Si on combine un incrémenteur à propagation de retenue avec un registre, on obtient ce compteur :
[[File:Compteur synchrone à incrémenteur.jpg|centre|vignette|upright=2|Compteur synchrone avec des bascules D.]]
Il est possible d'optimiser le circuit avec les optimisations vues dans le chapitre sur les incrémenteurs. La première est le ''carry skip'' qui fait l'incrémentation non pas bit par bit, mais par paquets de deux bits. La seconde est l'anticipation de retenue. Et ces deux optimisations se marient bien avec ce qui va suivre.
===Les compteurs en cascade===
Il est possible de concevoir des compteurs à partir de compteurs plus petits, mis en cascade. Par exemple, en créant un compteur 16 bits à partir de compteurs 4 bits, enchainés l'un à la suite de l'autre.
Les compteurs mis en cascade ont les mêmes entrées et sorties que les compteurs normaux, avec cependant un détail très important : ils ont une entrée pour la retenue entrante, et une sortie pour la retenue sortante. L'entrée pour la retenue entrante précise ce qui doit être additionné au bit de poids faible. Si elle vaut zéro, l'incrémenteur n'incrémente pas l'opérande. Si elle vaut 1, le compteur est incrémenté. L'entrée de retenue fait office d'entrée ''Count Enable'', qui active ou désactive l'incrémentation. Le compteur a aussi une sortie de débordement, qui indique que le compteur déborde. Il se trouve que cette sortie fournit la retenue pour le compteur suivant. La retenue est calculée en faisant un ET logique entre tous les bits du décompte.
[[File:Cascadable binary up-counter.jpg|centre|vignette|upright=2|Cascadable binary up-counter]]
Les compteurs sont mis en cascade de la manière suivante : leur sortie de débordement est connectée sur l'entrée ''Enable'' du compteur suivant, celle qui déclenche l'incrémentation du compteur. La sortie de débordement est notée RCO dans les schéma qui suivent, nous verrons pourquoi dans le prochaine paragraphe.
[[File:Cascaded binary counters.jpg|centre|vignette|upright=2.5|Cascaded binary counters]]
===Les compteurs modulo===
La plupart des compteurs comptent de 0 à <math>2^n - 1</math>, avec <math>n</math> la taille du compteur. Mais d'autres compteurs ne comptent pas jusque-là. Par exemple, certains compteurs ne comptent que jusqu'à 10, 150, etc. Ils sont appelés des '''compteurs ''modulo'''''. Prenons un compteur modulo 6, par exemple : il compte de 0 à 5, et est remis immédiatement à zéro quand il atteint 6. Il compte donc comme suit : 0, 1, 2, 3, 4, 5, 0, 1, 2, ...
Les compteurs ''modulo'' sont construits à partir d'un compteur normal, couplé à un circuit comparateur qui remet à zéro le registre quand il atteint la valeur maximale. Par exemple, on peut imaginer un compteur modulo 6 est construit à partir d'un compteur 4 bits qui compte de 0 à 15 (donc un compteur modulo 16), mais qui est remis à zéro quand il atteint 6. Le circuit comparateur vérifie si la valeur maximale 6 est atteinte et met à 1 l'entrée ''Reset'' si c'est le cas. Le comparateur est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Td4bfig4.png|centre|vignette|upright=2|Compteur modulo N.]]
Pour une minorité de compteurs ''modulo'', la valeur maximale est configurable. Pour cela, le compteur est associé à un ''registre de configuration'' qui mémorise la valeur maximale souhaitée. A chaque cycle d'horloge, la valeur dans le compteur est comparée au registre de configuration. Si elles sont identiques, le compteur est remis à zéro. Le compteur est associé au registre de configuration et à un comparateur qui vérifie que les deux sont égaux. Pour le moment, nous ne savons pas faire de circuits comparateurs, ce qui fait qu'on ne peut pas expliquer ce circuit plus en détail.
[[File:4 Bit Counter Prog 1.svg|centre|vignette|upright=2|Compteur 4 bits à valeur maximale programmable.]]
Les compteurs ont tous une sortie de débordement, qui indique quand le compteur déborde. Pour les compteurs modulo, la sortie n'est autre que la sortie du comparateur. Pour les compteurs non-modulo, la sortie de débordement est une sortie du circuit combinatoire qui incrémente le compteur.
===Les mal-nommés ''compteurs asynchrones''===
Les '''compteurs asynchrones''' n'utilisent pas de circuit incrémenteur. A la place, ils sont fabriqués à partir d'un registre à décalage. Ils utilisent moins de portes logiques que les compteurs synchrones. Et ils ne sont pas forcément plus difficiles à comprendre. Précisons cependant qu'ils sont très mal nommés, à savoir que ce sont bien des circuits synchrones.
Pour comprendre comment fonctionne un compteur asynchrone, il faut regarder la séquence des premiers entiers :
* 000 ;
* 001 ;
* 010 ;
* 011 ;
* 100 ;
* 101 ;
* 110 ;
* 111.
Il faut remarquer que le bit de poids faible s'inverse à chaque cycle d'horloge. Pour les colonnes suivantes, le bit s'inverse quand le bit de la colonne précédente passe de 1 à 0, lors d'un front descendant sur la colonne précédente. Maintenant que l'on sait cela, on peut créer un compteur avec des bascules T (elles inversent leur contenu à chaque cycle d'horloge). La première colonne inverse son contenu à chaque cycle, elle correspond donc à une bascule T reliée directement à l'horloge. Les autres colonnes utilisent des bascules T activées sur front descendant.
: Attention, la bascule la plus à gauche stocke le bit de poids faible, pas celui de poids fort. Cela sera pareil dans tous les schémas qui suivront.
[[File:AsyncCounter Alternativ 2.svg|centre|vignette|upright=2|Compteur asynchrone de 4 bits.]]
Le compteur précédent ne peut pas être réinitialisé, ce qui pose des problèmes pour implémenter des compteurs modulo. Pour cela, il faut que les bascules du compteur aient une entrée de réinitialisation ''Reset'', qui les force à se remettre à zéro. Il suffit alors de connecter ensemble les entrées ''Reset'' des bascules à l'entrée ''Reset'' du compteur.
[[File:AsyncCounter mod 8 with RST .svg|centre|vignette|upright=2|Compteur réinitialisable.]]
Implémenter un compteur modulo demande d'ajouter un comparateur qui détecte quand la valeur maximale est atteinte, afin de commander l'entrée de réinitialisation. Un tel circuit est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Modulo-5-Zähler.svg|centre|vignette|upright=2|Compteur modulo 10.]]
Il est aussi possible d'utiliser des bascules D à la place des bascules T simplifiées. En effet, une bascule T simplifiée est identique à une bascule D dont on boucle la sortie /Q sur l'entrée de données. Cette implémentation permet d'ailleurs de réinitialiser le compteur à une valeur non-nulle. Pour cela, l'entrée de chaque bascule D est précédée d'un multiplexeur, qui choisit entre le bit calculé par le compteur et celui présenté sur l'entrée de ré-initialisation. Quand l'entrée ''Reset'' est activée, les multiplexeurs connectent les bascules aux bits sur l'entrée de ré-initialisation. Dans le cas contraire, le compteur fonctionne normalement, les multiplexeurs connectant l'entrée de chaque bascule à sa sortie.
[[File:Compteur asynchrone, avec initialisation.jpg|centre|vignette|upright=2|Compteur asynchrone, avec initialisation.]]
Pour finir, la sortie de débordement n'est autre que la sortie de la dernière bascule, celle qui contient le bit de poids fort.
==Les ''timers'' : compter des durées, compter des cycles d'horloge==
Les '''''timers''''', aussi appelés ''Programmable interval timer'', sont des circuits capables de compter des durées, exprimées en cycles d'horloge. Leur fonctionnement est assez simple : ils émettent un signal quand un certain nombre de cycles est écoulé, ce nombre de cycles étant configurable. On peut ainsi générer un signal qui surviendra après 50 cycles d'horloge, ou après 100 cycles d'horloge, etc. Le signal en question est disponible sur une sortie de 1 bit, et correspond tout simplement au fait que cette sortie est mise à 1, pendant un cycle d'horloge.
Les ''timers'' sont composés d'un compteur/décompteur cadencé par un signal d'horloge. Le compteur initialisé à 0, puis est incrémenté à chaque signal d'horloge, jusqu’à atteinte d'une valeur limite où il génère un signal. Pour un décompteur, c'est la même chose, sauf que le décompteur est initialisé à sa valeur limite et est décrémenté à chaque cycle, et envoie un signal quand il atteint 0. Les ''timers'' basés sur des décompteurs sont nettement plus simples que les autres, ce qui fait qu'ils sont plus utilisés Pour que les ''timers'' soient configurables, on doit pouvoir préciser combien de cycles il faut (dé-)compter avant d'émettre un signal. On peut ainsi préciser s'il faut émettre le signal après 32 cycles d'horloge, après les 50 cycles, tous les 129 cycles, etc. Le nombre de cycles en question est envoyé sur une entrée d’initialisation du compteur.
Les ''timers'' matériels peuvent compter de deux manières différentes, appelées '''mode une fois''' et '''mode périodique'''.
* En mode une fois, le ''timer'' s'arrête une fois qu'il a atteint la limite configurée. On doit le réinitialiser manuellement, par l'intermédiaire du logiciel, pour l'utiliser une nouvelle fois. Cela permet de compter une certaine durée, exprimée en nombre de cycles d'horloge.
* En mode périodique, le ''timer'' se réinitialise automatiquement avec la valeur de départ, ce qui fait qu'il reboucle à l'infini.
Un ordinateur est rempli de ''timers'' divers. Dans ce qui va suivre, nous allons voir les principaux ''timers'', qui sont actuellement intégrés dans les PC modernes. Ils se trouvent sur la carte mère ou dans le processeur, tout dépend du ''timer''.
===Le ''watchdog timer''===
Le '''''watchdog timer''''' est un ''timer'' spécifique dont le but est de redémarrer automatiquement l'ordinateur si jamais celui-ci ne répond plus ou plante. Beaucoup de PC s'en passent, mais ce ''timer'' est très fréquent dans les architectures embarquées. Le ''watchdog timer'' est un compteur/décompteur qui doit être réinitialisé régulièrement. S'il n'est pas réinitialisé, le ''watchdog timer'' déborde (revient à 0 ou atteint 0) et envoie un signal qui redémarre le système. Le système est conçu pour réinitialiser le ''watchdog timer'' régulièrement, ce qui signifie que le système n'est pas censé redémarrer. Si jamais le système dysfonctionne gravement, le système ne pourra pas réinitialiser le ''watchdog timer'' et le système est redémarré automatiquement ou mis en arrêt.
[[File:SimpleWatchdogTimer.gif|centre|vignette|upright=2|Le ''Watchdog Timer'' et l'ordinateur.]]
===Le ''Time Stamp Counter'' des processeurs x86===
Tous les processeurs des PC actuels sont des processeurs dits x86. Nous ne pouvons pas expliquer ce que cela signifie pour le moment, retenez juste ce terme. Sachez que tous les processeurs x86 contiennent un compteur de 64 bits, appelé le '''''Time Stamp Counter''''', qui mémorise le nombre de cycles d'horloge qu'a effectué le processeur depuis son démarrage. Les programmes peuvent accéder à ce registre assez facilement, ce qui est utile pour faire des mesures ou comparer les performances de deux applications. Il permet de compter combien de cycles d'horloge met un morceau de code à s’exécuter, combien de cycles prend une instruction à s’exécuter, etc. Les processeurs non-x86 ont un registre équivalent, que ce soit les processeurs ARM ou d'autres.
Malheureusement, ce compteur est tombé en désuétude pour tout un tas de raisons. La principale est que les processeurs actuels font varier leur fréquence suivant les besoins. Ils augmentent leur fréquence quand on leur demande de faire beaucoup de calculs, et se mettent en mode basse(fréquence pour économiser de l'énergie si on ne leur demande pas grand chose. Avec une fréquence variable, le ''Time Stamp Counter'' perd complétement en fiabilité. Intel a tenté de corriger ce défaut en incrémentant ce registre à une fréquence constante, différente de celle du processeur, ce qui est encore le cas sur les processeurs Intel actuels. Le comportement est un peu différent sur les processeurs AMD, qui cadencent ce ''timer'' à la fréquence du processeur mais utilisent des mécanismes de synchronisation assez complexes pour corriger l'effet de la fréquence variable.
===L'horloge temps réel===
L''''horloge temps réel''' est un ''timer'' qui génère une fréquence de 1024 Hz, soit près d'un Kilohertz. Dans ce qui suit, nous la noterons RTC, ce qui est l'acronyme du terme anglais ''Real Time Clock''. La RTC prend en entrée un signal d'horloge de 32KHz, généré par un oscillateur à Quartz, et fournit en sortie un signal de fréquence 32 fois plus faible, c'est à dire de 1 KHz. Pour cela, elle est réglée en mode répétitif et son décompteur interne est initialisé à 32. La RTC génère donc un signal toutes les millisecondes, qui est envoyé au processeur. On peut, en théorie, changer la fréquence de la RTC, mais c'est rarement une bonne idée.
En théorie, la RTC permet de compter des durées assez courtes, comme le ''ping'' (le temps de latence d'un réseau, pour simplifier), le temps de rafraichissement de l'écran, ou bien d'autres choses. Mais dans les faits, l'horloge temps réel sa fréquence n'aide pas : 1024 Hz est proche de 1000, mais pas assez pour faire des mesures à la milliseconde près, chose qui est nécessaire pour mesurer le ''ping'' ou d'autres choses utiles. A la place, l'ordinateur l'utilise pour que l'ordinateur soit toujours à l'heure. L'ordinateur sait quelle heure il est avec une précision de l'ordre de la seconde (vous pouvez regarder le bureau de Windows dans le coin inférieur droite de votre écran pour vous en convaincre).
===Le ''Programmable Interval Timer'' : l'Intel 8253===
L'Intel 8253 était un ''timer'' programmable autrefois soudé sur les cartes mères des premiers PC. Il fût suivi par l'Intel 8254, qui en était une légère amélioration. Il était cadencé par une horloge maitre, générée par un oscillateur à Quartz, dont la fréquence est de 32 768 Hertz, soit 2^15 cycles d'horloge par seconde. S'il n'est plus présent dans un boitier sur la carte mère, on trouve toujours un circuit semblable au 8253 à l'intérieur du ''chipset'' de la carte mère, voire à l'intérieur du processeur, pour des raisons de compatibilité. L'intérieur de l'Intel 8253 est illustré ci-dessous. Nous allons expliquer l'ensemble de ce schéma, rassurez-vous, mais les explications seront plus simples à comprendre si vous survolez ce schéma en premier lieu.
[[File:Intel 8253 block diagram.svg|centre|vignette|upright=2|Intel 8253, intérieur.]]
L'Intel 8253 contient trois compteurs de 16 bits, numérotés 0, 1 et 2. Pour chaque compteur, l'entrée CLOCK est celle de l'horloge de 32 MHz, l'entrée GATE active ou désactive le compteur, la sortie fournit le signal voulu et/ou la fréquence de sortie. Les trois compteurs étaient utilisés pour dériver plusieurs fréquences allant de 18,2 Hz à environ 500 KHz. Par exemple, il était utilisé par défaut pour le rafraichissement de la mémoire (D)RAM, mais il était souvent reprogrammé pour servir à générer des fréquences spécifiques par le BIOS ou la carte graphique.
[[File:Intel 8253 and 8254.svg|vignette|Intel 8253 and 8254]]
L'Intel 8253 lui-même possède plusieurs entrées et sorties. En premier lieu, on voit un port de 8 bits connecté aux trois compteurs, qui permet à l'Intel 8253 de communiquer avec le reste de l'ordinateur. La communication se fait dans les deux sens : soit de l'ordinateur vers les compteurs, soit des compteurs vers l'ordinateur. Dans le sens ordinateur -> compteurs, cela permet à l'ordinateur de programmer les compteurs, de les initialiser. Dans l'autre sens, cela permet de récupérer le contenu des compteurs, même si ce n'est pas très utilisé. Il y a aussi 5 entrées de configuration :
* Deux bits A0 et A1 pour sélectionner le compteur voulu avec son numéro.
* Un bit RD à mettre à 0 pour que l'ordinateur récupère le compteur sélectionné sur le port de 8 bits.
* Un bit WR à mettre à 0 pour que l'ordinateur modifie le compteur sélectionné, en envoyant le nombre pour l'initialisation sur le port de 8 bits.
* Un bit CS qui active ou désactive l'Intel 8253 et permet de l'allumer ou de l’éteindre.
L'Intel 8253 intégre un registre de 8 bits, le ''Control Word register'' qui mémorise la configuration de l'Intel 8253. Pour programmer les trois compteurs, il faut écrire un mot de 8 bits dans ce ''Control Word register''. Pour écrire dans le ''Control Word register'', il faut mettre le bit CS à 0 (on active l'Intel 8253), mettre le bit RDà 1 , le bit WR à 0 le bit WR (on indique qu'on fait une écriture), sélectionner le ''Control Word register'' en mettant les deux bits A0 et A1 à 1, puis envoyer la configuration du ''Control Word register'' sur le port de 8 bits.
===Le ''High Precision Event Timer'' (HPET)===
De nos jours, l'horloge temps réel et l'Intel 8253/8254 tendent à être remplacé par un autre ''timer'', le ''High Precision Event Timer'' (HPET). Il s'agit d'un compteur de 64 bits, dont la fréquence est d'au moins 10 MHz. Il s'agit bien d'un compteur et non d'un décompteur. Il gère nativement plusieurs valeurs limites à laquelle générer un signal, qui sont configurables. Pour cela, il est couplé à plusieurs comparateurs, chacun associé à un registre pour mémoriser la valeur limite. Il doit y avoir au moins trois comparateurs/registres, mais le nombre peut monter jusqu’à 256.
[[File:High Precision Event Timer.png|centre|vignette|upright=2|High Precision Event Timer]]
Il faut noter que les systèmes d'exploitation conçus avant le HPET ne peuvent pas l'utiliser, pour des raisons de compatibilité matérielle. C'est le cas de Windows XP avant le Service Pack 3. C'est la raison pour laquelle les cartes mères émulent RTC et PIT dans leurs circuits. D'ailleurs, pour économiser des circuits, les cartes mères modernes émulent le PIT et la RTC avec le HPET : le premier comparateur fournisse la fréquence de 1024 Hz de la RTC, 3 autres comparateurs remplacent l'Intel 8253.
Le HPET gère de nombreux modes de fonctionnement : ses comparateurs peuvent être configuré en mode une fois ou périodique, on peut lui demander d'émuler la RTC et le PIT, etc. Chaque comparateur doit pouvoir fonctionner en mode une fois, et au moins un comparateur doit pouvoir fonctionner en mode périodique. Aussi, il contient aussi 3 registres de configuration. Notons qu'il est aussi possible de lire ou écrire dans le compteur de 64 bits, mais ce n'est pas recommandé.
==Les compteurs utilisant des représentations binaires non-classiques==
Les compteurs classiques encodent leur décompte en binaire normal sur <math>n</math> bits, mais il faut savoir que d'autres compteurs utilisent le BCD, d'autre le code Gray, etc. Nous allons voir dans ce qui suit deux types de compteurs : ceux qui comptent en code Gray, ceux qui comptent en représentation ''one-hot''.
===Les compteurs en code Gray===
Pour rappel, le code Gray permet de coder des nombres d'une manière un peu différente du binaire normal. Son avantage principal est que lorsqu'on incrémente ou décrémente un nombre, seul un bit change ! Pour comparer, en binaire normal, lorsqu'on passe incrémente un nombre, il peut y avoir plusieurs bits qui changent. La moyenne est d'environ deux bits par incrémentation. Les compteurs en code Gray ont beaucoup d'avantages, qui sont tous liés à cette propriété.
Le premier l'absence d'état transitoires douteux. Le problème est que les bits modifiés par une incrémentation ne le sont pas en même temps. Les bits de poids faibles sont modifiés avant les autres. Évidemment, à la fin du calcul, on obtient le résultat final, correct. Mais pendant le temps de calcul, le compteur peut se retrouver dans un état transitoire, où seuls les bits de poids faibles ont été modifiés. C'est un problème si le contenu de ce compteur est relié à des circuits rapides, qui peuvent voir cet état transitoire, mais ne le doivent pas sous peine de dysfonctionner. L'usage de compteurs en code Gray permet d'éviter ce problème : vu que seul un bit est modifié lors d'une incrémentation/décrémentation, les états transitoires n'existent tout simplement pas.
Un exemple typique, évoqué dans les chapitres précédents, est l'échange d'informations entre deux domaines d'horloge. Pour rappel, il arrive que deux portions d'un circuit imprimé aillent à des fréquences différences : on dit que le circuit à plusieurs domaines d'horloge. S'il faut échanger des informations entre ces deux domaines d'horloge, divers problèmes surviennent. Un domaine d'horloge sera plus rapide que l'autre, et pourra voir les états transitoires invisible de l'autre circuit. Et par voir, on veut dire qu'il les prendra pour des états valides, ce qui fera dysfonctionner le circuit. Pour éviter cela, diverses techniques de croisement de domaines d'horloge existent. Et les compteurs Gray en font partie : si un domaine d'horloge utilise la valeur d'un compteur de l'autre, mieux vaut que ce compteur soit un compteur Gray. Et cette situation est assez fréquente !
Un autre avantage mineurs est que la consommation d'énergie de ces compteurs est bien plus réduite qu'avec un compteur normal. Rappelons que pour fonctionner, les circuits électroniques consomment un peu d'électricité. Et la majeure partie de cette consommation sert à faire passer un bit de 0 à 1 ou de 1 à 0. Ce qui fait que quand un compteur est incrémenté ou décrémenté, cela consomme un peu d'énergie électrique.
La moyenne pour un compteur binaire normal est de 2 bits changés par incrémentation/décrémentation, contre un seul pour un compteur Gray, on devine que ces derniers consomment deux fois moins d'énergie par incrémentation. Et cet avantage a des effets en cascade sur les circuits qui suivent ce compteur. Si l'entrée de ces circuits ne change que d'un seul bit, alors leur état changera moins que si c'était deux bits. Les circuits qui suivent vont donc moins consommer.
Un autre avantage en matière de consommation énergétique est lié auxs transitions d'état douteuses. Les circuits connectés au compteur vont voir ces transitions d'état douteuses et modifier leur état interne en réaction. Bien sur, l'état final correct sera atteint une fois que le compteur sera stabilisé, ce qui effacera ces états transitoires intermédiaires. Mais chaque état intermédiaire transitoire correspond à un changement d'état, donc à une consommation d'énergie. En supprimant ces états transitoires, on réduit fortement la consommation d'énergie du circuit. Cela vaut pour le compteur Gray lui-même, mais aussi sur tous les circuits qui ont ce compteur comme entrée !
===Les compteurs ''one-hot''===
Les '''compteurs ''one-hot''''' sont appelés ainsi, car ils permettent de compter dans une représentation des nombres appelée la représentation ''one-hot''. Pour rappel, dans une telle représentation, un seul bit est à 1 pendant que les autres sont à 0. Les entiers sont codés de la manière suivante : le nombre N est encodé en mettant le énième bit à 1, avec la condition que l'on commence à compteur à partir de zéro. Il est important de remarquer que dans cette représentation, le zéro est n'est PAS codé en mettant tous les bits à 0, la valeur 0000...0000 est une valeur interdite. À la place, le zéro est codé en mettant le bit de poids faible à 1. Pour N bits, on peut encoder seulement N valeurs, dont le zéro.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! ''One-hot''
|-
| 0 || 000 || 00000001
|-
| 1 || 001 || 00000010
|-
| 2 || 010 || 00000100
|-
| 3 || 011 || 00001000
|-
| 4 || 100 || 00010000
|-
| 5 || 101 || 00100000
|-
| 6 || 110 || 01000000
|-
| 7 || 111 || 10000000
|}
Un compteur en représentation ''one-hot'' contient un nombre codé de cette manière, qui est incrémenté ou décrémenté si besoin. Pour donner un exemple, la séquence d'un compteur en anneau de 4 bits est :
* 0001 (0) ;
* 0010 (1) ;
* 0100 (2) ;
* 1000 (3) .
Un compteur ''one-hot'' basique est composé d'un registre à décalage dont on boucle la sortie sur son entrée. En faisant cela, on garantit que le registre revient à zéro lors d'un débordement, zéro étant codé avec un 1 dans le bit de poids faible. Au passage, si vous ne mettez que des 0 dans un compteur en anneau, il restera bloqué pour toujours : décaler une suite de 0 donnera la même suite de 0. Initialiser un compteur ''one-hot'' demande donc quelques subtilités qu'on détaillera plus bas.
[[File:Compteur en anneau de 4 bits.jpg|centre|vignette|upright=2|Compteur en anneau de 4 bits]]
Un compteur ''one-hot'' de N bits peut prendre N valeurs différentes, qui ont toutes un seul bit à 1. Pour 5 bits, la séquence est illustrée ci-dessous.
[[File:Lauflicht 5 LED.gif|centre|vignette|Compteur en anneau de 5 bits.]]
Faire des comparaisons avec ce type de compteur est très simple : le compteur contient la valeur N si le énième bit est à 1. Pas besoin d'utiliser de circuit comparateur, juste de lire un bit. Par contre, ce compteur n'est pas très économe en bascules. Imaginons que l'on veut un compteur qui compte jusqu'à une valeur N arbitraire : un compteur binaire normal utilisera environ <math>\log_2{(N)}</math> bascules, alors qu'un compteur ''one-hot'' demande N bascules. Mais si N est assez petit, l'économie de bascules est assez faible, alors que l'économie de circuits comparateurs/incrémenteurs l'est beaucoup plus.
Il y a peu d'applications qui utilisent des compteurs en anneau. Ils étaient autrefois utilisés dans les tous premiers ordinateurs, notamment ceux qui géraient une représentation des nombres spécifique appelée la '''''Bi-quinary coded decimal'''''. De nos jours, de tels compteurs sont utilisés dans les séquenceurs de processeurs, mais aussi dans les séquenceurs de certains périphériques, ou dans les circuits séquentiels simples qui se résument à des machines à états. Ils sont alors utilisés car très rapides, parfaitement adaptés au stockage de petites valeur, et surtout : ils n'ont pas besoin de circuit comparateur pour connaitre la valeur stockée dedans. Nous n'allons pas rentrer dans le détail de leurs utilisations, car nous en reparlerons dans la suite du cours.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les registres et mémoires adressables
| prevText=Les registres et mémoires adressables
| next=Les registres à décalage à rétroaction linéaire
| nextText=Les registres à décalage à rétroaction linéaire
}}
</noinclude>
b3rck3nodpa0hz7kudesusc3wu8lwbi
768599
768598
2026-06-25T14:29:41Z
Mewtow
31375
/* Les compteurs en cascade */
768599
wikitext
text/x-wiki
Les '''compteurs/décompteurs''' sont des circuits électroniques qui mémorisent un nombre et l'incrémentent à la demande. En clair, ce sont des registres améliorés afin de supporter l'incrémentation et la décrémentation. Pour donner un exemple d'utilisation, imaginez un circuit qui compte le nombre de voitures dans un parking dans la journée. Pour cela, vous allez prendre deux circuits qui détectent respectivement l'entrée ou la sortie d'une voiture, et un compteur. Le compteur est initialisé à 0 quand le parking est vide, puis est incrémenté à chaque entrée de voiture, décrémenté à chaque sortie. Les exemples de ce type sont suffisamment nombreux pour qu'on dédie un chapitre aux compteurs.
[[File:Binary counter.gif|vignette|Illustration du fonctionnement d'un compteur modulaire binaire de 4 bits, avec un pas de compteur de 1 (le contenu est augmenté de 1 à chaque mise à jour).]]
Un compteur mémorise un nombre qui est incrémenté ou décrémenté au besoin. Le nombre mémorisé sera appelé le '''décompte''' dans ce qui suit. Il est mémorisé dans un registre à l'intérieur du compteur. Au passage, le nombre de bits <math>n</math> du compteur est appelé la '''taille du compteur''', par analogie avec les registres. Il faut cependant faire la différence entre les ''compteurs'' d'un côté et les ''décompteurs'' de l'autre. Les compteurs incrémentent le décompte, les décompteurs le décrémentent, les compteurs-décompteurs peuvent faire les deux.
==L'interface d'un compteur/décompteur==
Les compteurs et décompteurs sont des circuits synchrones et ont donc une entrée d'horloge. Les compteurs les plus simples incrémentent leur contenu à chaque cycle d'horloge, mais les plus fréquents n'incrémentent le décompte que sur demande. Pour cela, ils disposent d'une entrée '''''Count Enable''''', similaire à l'entrée ''Enable'' des registres, séparée de l'entrée d'horloge. Le décompte est incrémenté/décrémenté seulement si l'entrée Enable est à 1, lors d'un front adéquat sur le signal d'horloge.
Les compteurs ont aussi une entrée '''''Reset''''' qui permet de les remettre à zéro. Il y a parfois une entrée qui permet d'initialiser le compteur à une valeur par défaut, non-nulle. Par exemple, on peut initialiser le décompte à la valeur 5, ou une autre. Pour cela, le compteur dispose de deux entrées : une entrée sur laquelle envoyer le décompte initial, une entrée pour autoriser la réinitialisation. Les entrées en question sont appelées '''''Preload Data''''' et '''''Preload Enable'''''. La seconde entrée est parfois distincte de l'entrée de réinitialisation, pour permettre de réinitialiser le compteur soit à zéro, soit à la valeur voulue.
Il peut être utile de prévenir quand un débordement d'entier a lieu, à savoir quand le compteur n'a pas assez de bits pour encoder le décompte. Le compteur est alors remis à zéro, dans la plupart des cas. Mais il faut prévenir que le compteur a débordé, ce qui est utile pour fabriquer des circuits diviseurs de fréquence et des ''timers''. Pour cela, on ajoute une '''sortie de débordement''' au compteur, qui est mise à 1 quand le compteur déborde.
Sur les compteurs/décompteurs, il y a une entrée '''''Count Direction''''' qui décide s'il faut compter ou décompter. Typiquement, elle est à 1 s'il faut compter et 0 s'il faut décompter.
[[File:Digital counter signals.jpg|centre|vignette|upright=2|Interface d'un compteur-décompteur.]]
Dans les schémas qui vont suivre, les entrées Enable ne sont pas représentées. Il est sous-entendu qu'il y a une entrée ''Enable'' pour tous les compteurs qui vont suivre. Il existe deux méthodes pour créer de tels compteurs : la première donne ce qu'on appelle des compteurs asynchrones, et l'autre des compteurs synchrones.
==L'intérieur d'un compteur==
A une exception bien précise qu'on abordera plus bas, les compteurs sont composés d'un registre, qui mémorise le décompte, couplé à un circuit '''incrémenteur'''. Nous avions déjà abordé l'incrémenteur dans un chapitre précédent, aussi je ne vais pas ré-expliquer comment il est conçu. Tout ce qu'il faut retenir est qu'il y en a plusieurs types, le plus simple étant celui à propagation de retenue.
[[File:Fonctionnement d'un compteur (décompteur), schématique.jpg|centre|vignette|upright=2|Fonctionnement d'un compteur (décompteur), schématique. Le pas du compteur précise de combien on incrémente le compteur par cycle d'horloge.]]
L'incrémenteur le plus simple, à propagation de retenue, est fabriqué en enchainant des ''demi-additionneurs'' les uns à la suite des autres. Pour rappel, un demi-additionneur additionne deux bits. Ici, il additionne un bit de l'opérande, la retenue des colonnes précédentes. Pour le bit de poids faible, la retenue est forcé à 1. Si on combine un incrémenteur à propagation de retenue avec un registre, on obtient ce compteur :
[[File:Compteur synchrone à incrémenteur.jpg|centre|vignette|upright=2|Compteur synchrone avec des bascules D.]]
Il est possible d'optimiser le circuit avec les optimisations vues dans le chapitre sur les incrémenteurs. La première est le ''carry skip'' qui fait l'incrémentation non pas bit par bit, mais par paquets de deux bits. La seconde est l'anticipation de retenue. Et ces deux optimisations se marient bien avec ce qui va suivre.
===Les compteurs en cascade===
Il est possible de concevoir des compteurs à partir de compteurs plus petits, mis en cascade. Par exemple, en créant un compteur 16 bits à partir de compteurs 4 bits, enchainés l'un à la suite de l'autre.
Les compteurs mis en cascade ont les mêmes entrées et sorties que les compteurs normaux, avec cependant un détail très important : ils ont une entrée pour la retenue entrante, et une sortie pour la retenue sortante. L'entrée pour la retenue entrante précise ce qui doit être additionné au bit de poids faible. Si elle vaut zéro, l'incrémenteur n'incrémente pas l'opérande. Si elle vaut 1, le compteur est incrémenté. L'entrée de retenue fait office d'entrée ''Count Enable'', qui active ou désactive l'incrémentation.
Le compteur a aussi une sortie de débordement, qui indique que le compteur déborde. Il se trouve que cette sortie fournit la retenue pour le compteur suivant. La retenue peut être calculée en utilisant des optimisations comme l'anticipation de retenue, ce qui veut dire qu'elle est calculée sans propager les retenues, directement à partir des bits de l'opérande. Concrètement, la retenue est calculée en faisant un ET logique entre tous les bits du décompte.
[[File:Cascadable binary up-counter.jpg|centre|vignette|upright=2|Cascadable binary up-counter]]
Les compteurs sont mis en cascade de la manière suivante : leur sortie de débordement est connectée sur l'entrée ''Enable'' du compteur suivant, celle qui déclenche l'incrémentation du compteur. La sortie de débordement est notée RCO dans les schéma qui suivent, nous verrons pourquoi dans le prochaine paragraphe.
[[File:Cascaded binary counters.jpg|centre|vignette|upright=2.5|Cascaded binary counters]]
===Les compteurs modulo===
La plupart des compteurs comptent de 0 à <math>2^n - 1</math>, avec <math>n</math> la taille du compteur. Mais d'autres compteurs ne comptent pas jusque-là. Par exemple, certains compteurs ne comptent que jusqu'à 10, 150, etc. Ils sont appelés des '''compteurs ''modulo'''''. Prenons un compteur modulo 6, par exemple : il compte de 0 à 5, et est remis immédiatement à zéro quand il atteint 6. Il compte donc comme suit : 0, 1, 2, 3, 4, 5, 0, 1, 2, ...
Les compteurs ''modulo'' sont construits à partir d'un compteur normal, couplé à un circuit comparateur qui remet à zéro le registre quand il atteint la valeur maximale. Par exemple, on peut imaginer un compteur modulo 6 est construit à partir d'un compteur 4 bits qui compte de 0 à 15 (donc un compteur modulo 16), mais qui est remis à zéro quand il atteint 6. Le circuit comparateur vérifie si la valeur maximale 6 est atteinte et met à 1 l'entrée ''Reset'' si c'est le cas. Le comparateur est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Td4bfig4.png|centre|vignette|upright=2|Compteur modulo N.]]
Pour une minorité de compteurs ''modulo'', la valeur maximale est configurable. Pour cela, le compteur est associé à un ''registre de configuration'' qui mémorise la valeur maximale souhaitée. A chaque cycle d'horloge, la valeur dans le compteur est comparée au registre de configuration. Si elles sont identiques, le compteur est remis à zéro. Le compteur est associé au registre de configuration et à un comparateur qui vérifie que les deux sont égaux. Pour le moment, nous ne savons pas faire de circuits comparateurs, ce qui fait qu'on ne peut pas expliquer ce circuit plus en détail.
[[File:4 Bit Counter Prog 1.svg|centre|vignette|upright=2|Compteur 4 bits à valeur maximale programmable.]]
Les compteurs ont tous une sortie de débordement, qui indique quand le compteur déborde. Pour les compteurs modulo, la sortie n'est autre que la sortie du comparateur. Pour les compteurs non-modulo, la sortie de débordement est une sortie du circuit combinatoire qui incrémente le compteur.
===Les mal-nommés ''compteurs asynchrones''===
Les '''compteurs asynchrones''' n'utilisent pas de circuit incrémenteur. A la place, ils sont fabriqués à partir d'un registre à décalage. Ils utilisent moins de portes logiques que les compteurs synchrones. Et ils ne sont pas forcément plus difficiles à comprendre. Précisons cependant qu'ils sont très mal nommés, à savoir que ce sont bien des circuits synchrones.
Pour comprendre comment fonctionne un compteur asynchrone, il faut regarder la séquence des premiers entiers :
* 000 ;
* 001 ;
* 010 ;
* 011 ;
* 100 ;
* 101 ;
* 110 ;
* 111.
Il faut remarquer que le bit de poids faible s'inverse à chaque cycle d'horloge. Pour les colonnes suivantes, le bit s'inverse quand le bit de la colonne précédente passe de 1 à 0, lors d'un front descendant sur la colonne précédente. Maintenant que l'on sait cela, on peut créer un compteur avec des bascules T (elles inversent leur contenu à chaque cycle d'horloge). La première colonne inverse son contenu à chaque cycle, elle correspond donc à une bascule T reliée directement à l'horloge. Les autres colonnes utilisent des bascules T activées sur front descendant.
: Attention, la bascule la plus à gauche stocke le bit de poids faible, pas celui de poids fort. Cela sera pareil dans tous les schémas qui suivront.
[[File:AsyncCounter Alternativ 2.svg|centre|vignette|upright=2|Compteur asynchrone de 4 bits.]]
Le compteur précédent ne peut pas être réinitialisé, ce qui pose des problèmes pour implémenter des compteurs modulo. Pour cela, il faut que les bascules du compteur aient une entrée de réinitialisation ''Reset'', qui les force à se remettre à zéro. Il suffit alors de connecter ensemble les entrées ''Reset'' des bascules à l'entrée ''Reset'' du compteur.
[[File:AsyncCounter mod 8 with RST .svg|centre|vignette|upright=2|Compteur réinitialisable.]]
Implémenter un compteur modulo demande d'ajouter un comparateur qui détecte quand la valeur maximale est atteinte, afin de commander l'entrée de réinitialisation. Un tel circuit est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Modulo-5-Zähler.svg|centre|vignette|upright=2|Compteur modulo 10.]]
Il est aussi possible d'utiliser des bascules D à la place des bascules T simplifiées. En effet, une bascule T simplifiée est identique à une bascule D dont on boucle la sortie /Q sur l'entrée de données. Cette implémentation permet d'ailleurs de réinitialiser le compteur à une valeur non-nulle. Pour cela, l'entrée de chaque bascule D est précédée d'un multiplexeur, qui choisit entre le bit calculé par le compteur et celui présenté sur l'entrée de ré-initialisation. Quand l'entrée ''Reset'' est activée, les multiplexeurs connectent les bascules aux bits sur l'entrée de ré-initialisation. Dans le cas contraire, le compteur fonctionne normalement, les multiplexeurs connectant l'entrée de chaque bascule à sa sortie.
[[File:Compteur asynchrone, avec initialisation.jpg|centre|vignette|upright=2|Compteur asynchrone, avec initialisation.]]
Pour finir, la sortie de débordement n'est autre que la sortie de la dernière bascule, celle qui contient le bit de poids fort.
==Les ''timers'' : compter des durées, compter des cycles d'horloge==
Les '''''timers''''', aussi appelés ''Programmable interval timer'', sont des circuits capables de compter des durées, exprimées en cycles d'horloge. Leur fonctionnement est assez simple : ils émettent un signal quand un certain nombre de cycles est écoulé, ce nombre de cycles étant configurable. On peut ainsi générer un signal qui surviendra après 50 cycles d'horloge, ou après 100 cycles d'horloge, etc. Le signal en question est disponible sur une sortie de 1 bit, et correspond tout simplement au fait que cette sortie est mise à 1, pendant un cycle d'horloge.
Les ''timers'' sont composés d'un compteur/décompteur cadencé par un signal d'horloge. Le compteur initialisé à 0, puis est incrémenté à chaque signal d'horloge, jusqu’à atteinte d'une valeur limite où il génère un signal. Pour un décompteur, c'est la même chose, sauf que le décompteur est initialisé à sa valeur limite et est décrémenté à chaque cycle, et envoie un signal quand il atteint 0. Les ''timers'' basés sur des décompteurs sont nettement plus simples que les autres, ce qui fait qu'ils sont plus utilisés Pour que les ''timers'' soient configurables, on doit pouvoir préciser combien de cycles il faut (dé-)compter avant d'émettre un signal. On peut ainsi préciser s'il faut émettre le signal après 32 cycles d'horloge, après les 50 cycles, tous les 129 cycles, etc. Le nombre de cycles en question est envoyé sur une entrée d’initialisation du compteur.
Les ''timers'' matériels peuvent compter de deux manières différentes, appelées '''mode une fois''' et '''mode périodique'''.
* En mode une fois, le ''timer'' s'arrête une fois qu'il a atteint la limite configurée. On doit le réinitialiser manuellement, par l'intermédiaire du logiciel, pour l'utiliser une nouvelle fois. Cela permet de compter une certaine durée, exprimée en nombre de cycles d'horloge.
* En mode périodique, le ''timer'' se réinitialise automatiquement avec la valeur de départ, ce qui fait qu'il reboucle à l'infini.
Un ordinateur est rempli de ''timers'' divers. Dans ce qui va suivre, nous allons voir les principaux ''timers'', qui sont actuellement intégrés dans les PC modernes. Ils se trouvent sur la carte mère ou dans le processeur, tout dépend du ''timer''.
===Le ''watchdog timer''===
Le '''''watchdog timer''''' est un ''timer'' spécifique dont le but est de redémarrer automatiquement l'ordinateur si jamais celui-ci ne répond plus ou plante. Beaucoup de PC s'en passent, mais ce ''timer'' est très fréquent dans les architectures embarquées. Le ''watchdog timer'' est un compteur/décompteur qui doit être réinitialisé régulièrement. S'il n'est pas réinitialisé, le ''watchdog timer'' déborde (revient à 0 ou atteint 0) et envoie un signal qui redémarre le système. Le système est conçu pour réinitialiser le ''watchdog timer'' régulièrement, ce qui signifie que le système n'est pas censé redémarrer. Si jamais le système dysfonctionne gravement, le système ne pourra pas réinitialiser le ''watchdog timer'' et le système est redémarré automatiquement ou mis en arrêt.
[[File:SimpleWatchdogTimer.gif|centre|vignette|upright=2|Le ''Watchdog Timer'' et l'ordinateur.]]
===Le ''Time Stamp Counter'' des processeurs x86===
Tous les processeurs des PC actuels sont des processeurs dits x86. Nous ne pouvons pas expliquer ce que cela signifie pour le moment, retenez juste ce terme. Sachez que tous les processeurs x86 contiennent un compteur de 64 bits, appelé le '''''Time Stamp Counter''''', qui mémorise le nombre de cycles d'horloge qu'a effectué le processeur depuis son démarrage. Les programmes peuvent accéder à ce registre assez facilement, ce qui est utile pour faire des mesures ou comparer les performances de deux applications. Il permet de compter combien de cycles d'horloge met un morceau de code à s’exécuter, combien de cycles prend une instruction à s’exécuter, etc. Les processeurs non-x86 ont un registre équivalent, que ce soit les processeurs ARM ou d'autres.
Malheureusement, ce compteur est tombé en désuétude pour tout un tas de raisons. La principale est que les processeurs actuels font varier leur fréquence suivant les besoins. Ils augmentent leur fréquence quand on leur demande de faire beaucoup de calculs, et se mettent en mode basse(fréquence pour économiser de l'énergie si on ne leur demande pas grand chose. Avec une fréquence variable, le ''Time Stamp Counter'' perd complétement en fiabilité. Intel a tenté de corriger ce défaut en incrémentant ce registre à une fréquence constante, différente de celle du processeur, ce qui est encore le cas sur les processeurs Intel actuels. Le comportement est un peu différent sur les processeurs AMD, qui cadencent ce ''timer'' à la fréquence du processeur mais utilisent des mécanismes de synchronisation assez complexes pour corriger l'effet de la fréquence variable.
===L'horloge temps réel===
L''''horloge temps réel''' est un ''timer'' qui génère une fréquence de 1024 Hz, soit près d'un Kilohertz. Dans ce qui suit, nous la noterons RTC, ce qui est l'acronyme du terme anglais ''Real Time Clock''. La RTC prend en entrée un signal d'horloge de 32KHz, généré par un oscillateur à Quartz, et fournit en sortie un signal de fréquence 32 fois plus faible, c'est à dire de 1 KHz. Pour cela, elle est réglée en mode répétitif et son décompteur interne est initialisé à 32. La RTC génère donc un signal toutes les millisecondes, qui est envoyé au processeur. On peut, en théorie, changer la fréquence de la RTC, mais c'est rarement une bonne idée.
En théorie, la RTC permet de compter des durées assez courtes, comme le ''ping'' (le temps de latence d'un réseau, pour simplifier), le temps de rafraichissement de l'écran, ou bien d'autres choses. Mais dans les faits, l'horloge temps réel sa fréquence n'aide pas : 1024 Hz est proche de 1000, mais pas assez pour faire des mesures à la milliseconde près, chose qui est nécessaire pour mesurer le ''ping'' ou d'autres choses utiles. A la place, l'ordinateur l'utilise pour que l'ordinateur soit toujours à l'heure. L'ordinateur sait quelle heure il est avec une précision de l'ordre de la seconde (vous pouvez regarder le bureau de Windows dans le coin inférieur droite de votre écran pour vous en convaincre).
===Le ''Programmable Interval Timer'' : l'Intel 8253===
L'Intel 8253 était un ''timer'' programmable autrefois soudé sur les cartes mères des premiers PC. Il fût suivi par l'Intel 8254, qui en était une légère amélioration. Il était cadencé par une horloge maitre, générée par un oscillateur à Quartz, dont la fréquence est de 32 768 Hertz, soit 2^15 cycles d'horloge par seconde. S'il n'est plus présent dans un boitier sur la carte mère, on trouve toujours un circuit semblable au 8253 à l'intérieur du ''chipset'' de la carte mère, voire à l'intérieur du processeur, pour des raisons de compatibilité. L'intérieur de l'Intel 8253 est illustré ci-dessous. Nous allons expliquer l'ensemble de ce schéma, rassurez-vous, mais les explications seront plus simples à comprendre si vous survolez ce schéma en premier lieu.
[[File:Intel 8253 block diagram.svg|centre|vignette|upright=2|Intel 8253, intérieur.]]
L'Intel 8253 contient trois compteurs de 16 bits, numérotés 0, 1 et 2. Pour chaque compteur, l'entrée CLOCK est celle de l'horloge de 32 MHz, l'entrée GATE active ou désactive le compteur, la sortie fournit le signal voulu et/ou la fréquence de sortie. Les trois compteurs étaient utilisés pour dériver plusieurs fréquences allant de 18,2 Hz à environ 500 KHz. Par exemple, il était utilisé par défaut pour le rafraichissement de la mémoire (D)RAM, mais il était souvent reprogrammé pour servir à générer des fréquences spécifiques par le BIOS ou la carte graphique.
[[File:Intel 8253 and 8254.svg|vignette|Intel 8253 and 8254]]
L'Intel 8253 lui-même possède plusieurs entrées et sorties. En premier lieu, on voit un port de 8 bits connecté aux trois compteurs, qui permet à l'Intel 8253 de communiquer avec le reste de l'ordinateur. La communication se fait dans les deux sens : soit de l'ordinateur vers les compteurs, soit des compteurs vers l'ordinateur. Dans le sens ordinateur -> compteurs, cela permet à l'ordinateur de programmer les compteurs, de les initialiser. Dans l'autre sens, cela permet de récupérer le contenu des compteurs, même si ce n'est pas très utilisé. Il y a aussi 5 entrées de configuration :
* Deux bits A0 et A1 pour sélectionner le compteur voulu avec son numéro.
* Un bit RD à mettre à 0 pour que l'ordinateur récupère le compteur sélectionné sur le port de 8 bits.
* Un bit WR à mettre à 0 pour que l'ordinateur modifie le compteur sélectionné, en envoyant le nombre pour l'initialisation sur le port de 8 bits.
* Un bit CS qui active ou désactive l'Intel 8253 et permet de l'allumer ou de l’éteindre.
L'Intel 8253 intégre un registre de 8 bits, le ''Control Word register'' qui mémorise la configuration de l'Intel 8253. Pour programmer les trois compteurs, il faut écrire un mot de 8 bits dans ce ''Control Word register''. Pour écrire dans le ''Control Word register'', il faut mettre le bit CS à 0 (on active l'Intel 8253), mettre le bit RDà 1 , le bit WR à 0 le bit WR (on indique qu'on fait une écriture), sélectionner le ''Control Word register'' en mettant les deux bits A0 et A1 à 1, puis envoyer la configuration du ''Control Word register'' sur le port de 8 bits.
===Le ''High Precision Event Timer'' (HPET)===
De nos jours, l'horloge temps réel et l'Intel 8253/8254 tendent à être remplacé par un autre ''timer'', le ''High Precision Event Timer'' (HPET). Il s'agit d'un compteur de 64 bits, dont la fréquence est d'au moins 10 MHz. Il s'agit bien d'un compteur et non d'un décompteur. Il gère nativement plusieurs valeurs limites à laquelle générer un signal, qui sont configurables. Pour cela, il est couplé à plusieurs comparateurs, chacun associé à un registre pour mémoriser la valeur limite. Il doit y avoir au moins trois comparateurs/registres, mais le nombre peut monter jusqu’à 256.
[[File:High Precision Event Timer.png|centre|vignette|upright=2|High Precision Event Timer]]
Il faut noter que les systèmes d'exploitation conçus avant le HPET ne peuvent pas l'utiliser, pour des raisons de compatibilité matérielle. C'est le cas de Windows XP avant le Service Pack 3. C'est la raison pour laquelle les cartes mères émulent RTC et PIT dans leurs circuits. D'ailleurs, pour économiser des circuits, les cartes mères modernes émulent le PIT et la RTC avec le HPET : le premier comparateur fournisse la fréquence de 1024 Hz de la RTC, 3 autres comparateurs remplacent l'Intel 8253.
Le HPET gère de nombreux modes de fonctionnement : ses comparateurs peuvent être configuré en mode une fois ou périodique, on peut lui demander d'émuler la RTC et le PIT, etc. Chaque comparateur doit pouvoir fonctionner en mode une fois, et au moins un comparateur doit pouvoir fonctionner en mode périodique. Aussi, il contient aussi 3 registres de configuration. Notons qu'il est aussi possible de lire ou écrire dans le compteur de 64 bits, mais ce n'est pas recommandé.
==Les compteurs utilisant des représentations binaires non-classiques==
Les compteurs classiques encodent leur décompte en binaire normal sur <math>n</math> bits, mais il faut savoir que d'autres compteurs utilisent le BCD, d'autre le code Gray, etc. Nous allons voir dans ce qui suit deux types de compteurs : ceux qui comptent en code Gray, ceux qui comptent en représentation ''one-hot''.
===Les compteurs en code Gray===
Pour rappel, le code Gray permet de coder des nombres d'une manière un peu différente du binaire normal. Son avantage principal est que lorsqu'on incrémente ou décrémente un nombre, seul un bit change ! Pour comparer, en binaire normal, lorsqu'on passe incrémente un nombre, il peut y avoir plusieurs bits qui changent. La moyenne est d'environ deux bits par incrémentation. Les compteurs en code Gray ont beaucoup d'avantages, qui sont tous liés à cette propriété.
Le premier l'absence d'état transitoires douteux. Le problème est que les bits modifiés par une incrémentation ne le sont pas en même temps. Les bits de poids faibles sont modifiés avant les autres. Évidemment, à la fin du calcul, on obtient le résultat final, correct. Mais pendant le temps de calcul, le compteur peut se retrouver dans un état transitoire, où seuls les bits de poids faibles ont été modifiés. C'est un problème si le contenu de ce compteur est relié à des circuits rapides, qui peuvent voir cet état transitoire, mais ne le doivent pas sous peine de dysfonctionner. L'usage de compteurs en code Gray permet d'éviter ce problème : vu que seul un bit est modifié lors d'une incrémentation/décrémentation, les états transitoires n'existent tout simplement pas.
Un exemple typique, évoqué dans les chapitres précédents, est l'échange d'informations entre deux domaines d'horloge. Pour rappel, il arrive que deux portions d'un circuit imprimé aillent à des fréquences différences : on dit que le circuit à plusieurs domaines d'horloge. S'il faut échanger des informations entre ces deux domaines d'horloge, divers problèmes surviennent. Un domaine d'horloge sera plus rapide que l'autre, et pourra voir les états transitoires invisible de l'autre circuit. Et par voir, on veut dire qu'il les prendra pour des états valides, ce qui fera dysfonctionner le circuit. Pour éviter cela, diverses techniques de croisement de domaines d'horloge existent. Et les compteurs Gray en font partie : si un domaine d'horloge utilise la valeur d'un compteur de l'autre, mieux vaut que ce compteur soit un compteur Gray. Et cette situation est assez fréquente !
Un autre avantage mineurs est que la consommation d'énergie de ces compteurs est bien plus réduite qu'avec un compteur normal. Rappelons que pour fonctionner, les circuits électroniques consomment un peu d'électricité. Et la majeure partie de cette consommation sert à faire passer un bit de 0 à 1 ou de 1 à 0. Ce qui fait que quand un compteur est incrémenté ou décrémenté, cela consomme un peu d'énergie électrique.
La moyenne pour un compteur binaire normal est de 2 bits changés par incrémentation/décrémentation, contre un seul pour un compteur Gray, on devine que ces derniers consomment deux fois moins d'énergie par incrémentation. Et cet avantage a des effets en cascade sur les circuits qui suivent ce compteur. Si l'entrée de ces circuits ne change que d'un seul bit, alors leur état changera moins que si c'était deux bits. Les circuits qui suivent vont donc moins consommer.
Un autre avantage en matière de consommation énergétique est lié auxs transitions d'état douteuses. Les circuits connectés au compteur vont voir ces transitions d'état douteuses et modifier leur état interne en réaction. Bien sur, l'état final correct sera atteint une fois que le compteur sera stabilisé, ce qui effacera ces états transitoires intermédiaires. Mais chaque état intermédiaire transitoire correspond à un changement d'état, donc à une consommation d'énergie. En supprimant ces états transitoires, on réduit fortement la consommation d'énergie du circuit. Cela vaut pour le compteur Gray lui-même, mais aussi sur tous les circuits qui ont ce compteur comme entrée !
===Les compteurs ''one-hot''===
Les '''compteurs ''one-hot''''' sont appelés ainsi, car ils permettent de compter dans une représentation des nombres appelée la représentation ''one-hot''. Pour rappel, dans une telle représentation, un seul bit est à 1 pendant que les autres sont à 0. Les entiers sont codés de la manière suivante : le nombre N est encodé en mettant le énième bit à 1, avec la condition que l'on commence à compteur à partir de zéro. Il est important de remarquer que dans cette représentation, le zéro est n'est PAS codé en mettant tous les bits à 0, la valeur 0000...0000 est une valeur interdite. À la place, le zéro est codé en mettant le bit de poids faible à 1. Pour N bits, on peut encoder seulement N valeurs, dont le zéro.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! ''One-hot''
|-
| 0 || 000 || 00000001
|-
| 1 || 001 || 00000010
|-
| 2 || 010 || 00000100
|-
| 3 || 011 || 00001000
|-
| 4 || 100 || 00010000
|-
| 5 || 101 || 00100000
|-
| 6 || 110 || 01000000
|-
| 7 || 111 || 10000000
|}
Un compteur en représentation ''one-hot'' contient un nombre codé de cette manière, qui est incrémenté ou décrémenté si besoin. Pour donner un exemple, la séquence d'un compteur en anneau de 4 bits est :
* 0001 (0) ;
* 0010 (1) ;
* 0100 (2) ;
* 1000 (3) .
Un compteur ''one-hot'' basique est composé d'un registre à décalage dont on boucle la sortie sur son entrée. En faisant cela, on garantit que le registre revient à zéro lors d'un débordement, zéro étant codé avec un 1 dans le bit de poids faible. Au passage, si vous ne mettez que des 0 dans un compteur en anneau, il restera bloqué pour toujours : décaler une suite de 0 donnera la même suite de 0. Initialiser un compteur ''one-hot'' demande donc quelques subtilités qu'on détaillera plus bas.
[[File:Compteur en anneau de 4 bits.jpg|centre|vignette|upright=2|Compteur en anneau de 4 bits]]
Un compteur ''one-hot'' de N bits peut prendre N valeurs différentes, qui ont toutes un seul bit à 1. Pour 5 bits, la séquence est illustrée ci-dessous.
[[File:Lauflicht 5 LED.gif|centre|vignette|Compteur en anneau de 5 bits.]]
Faire des comparaisons avec ce type de compteur est très simple : le compteur contient la valeur N si le énième bit est à 1. Pas besoin d'utiliser de circuit comparateur, juste de lire un bit. Par contre, ce compteur n'est pas très économe en bascules. Imaginons que l'on veut un compteur qui compte jusqu'à une valeur N arbitraire : un compteur binaire normal utilisera environ <math>\log_2{(N)}</math> bascules, alors qu'un compteur ''one-hot'' demande N bascules. Mais si N est assez petit, l'économie de bascules est assez faible, alors que l'économie de circuits comparateurs/incrémenteurs l'est beaucoup plus.
Il y a peu d'applications qui utilisent des compteurs en anneau. Ils étaient autrefois utilisés dans les tous premiers ordinateurs, notamment ceux qui géraient une représentation des nombres spécifique appelée la '''''Bi-quinary coded decimal'''''. De nos jours, de tels compteurs sont utilisés dans les séquenceurs de processeurs, mais aussi dans les séquenceurs de certains périphériques, ou dans les circuits séquentiels simples qui se résument à des machines à états. Ils sont alors utilisés car très rapides, parfaitement adaptés au stockage de petites valeur, et surtout : ils n'ont pas besoin de circuit comparateur pour connaitre la valeur stockée dedans. Nous n'allons pas rentrer dans le détail de leurs utilisations, car nous en reparlerons dans la suite du cours.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les registres et mémoires adressables
| prevText=Les registres et mémoires adressables
| next=Les registres à décalage à rétroaction linéaire
| nextText=Les registres à décalage à rétroaction linéaire
}}
</noinclude>
0locrfapuskjcjl8hioekzpfmqt9ro8
768600
768599
2026-06-25T14:42:28Z
Mewtow
31375
/* L'intérieur d'un compteur */
768600
wikitext
text/x-wiki
Les '''compteurs/décompteurs''' sont des circuits électroniques qui mémorisent un nombre et l'incrémentent à la demande. En clair, ce sont des registres améliorés afin de supporter l'incrémentation et la décrémentation. Pour donner un exemple d'utilisation, imaginez un circuit qui compte le nombre de voitures dans un parking dans la journée. Pour cela, vous allez prendre deux circuits qui détectent respectivement l'entrée ou la sortie d'une voiture, et un compteur. Le compteur est initialisé à 0 quand le parking est vide, puis est incrémenté à chaque entrée de voiture, décrémenté à chaque sortie. Les exemples de ce type sont suffisamment nombreux pour qu'on dédie un chapitre aux compteurs.
[[File:Binary counter.gif|vignette|Illustration du fonctionnement d'un compteur modulaire binaire de 4 bits, avec un pas de compteur de 1 (le contenu est augmenté de 1 à chaque mise à jour).]]
Un compteur mémorise un nombre qui est incrémenté ou décrémenté au besoin. Le nombre mémorisé sera appelé le '''décompte''' dans ce qui suit. Il est mémorisé dans un registre à l'intérieur du compteur. Au passage, le nombre de bits <math>n</math> du compteur est appelé la '''taille du compteur''', par analogie avec les registres. Il faut cependant faire la différence entre les ''compteurs'' d'un côté et les ''décompteurs'' de l'autre. Les compteurs incrémentent le décompte, les décompteurs le décrémentent, les compteurs-décompteurs peuvent faire les deux.
==L'interface d'un compteur/décompteur==
Les compteurs et décompteurs sont des circuits synchrones et ont donc une entrée d'horloge. Les compteurs les plus simples incrémentent leur contenu à chaque cycle d'horloge, mais les plus fréquents n'incrémentent le décompte que sur demande. Pour cela, ils disposent d'une entrée '''''Count Enable''''', similaire à l'entrée ''Enable'' des registres, séparée de l'entrée d'horloge. Le décompte est incrémenté/décrémenté seulement si l'entrée Enable est à 1, lors d'un front adéquat sur le signal d'horloge.
Les compteurs ont aussi une entrée '''''Reset''''' qui permet de les remettre à zéro. Il y a parfois une entrée qui permet d'initialiser le compteur à une valeur par défaut, non-nulle. Par exemple, on peut initialiser le décompte à la valeur 5, ou une autre. Pour cela, le compteur dispose de deux entrées : une entrée sur laquelle envoyer le décompte initial, une entrée pour autoriser la réinitialisation. Les entrées en question sont appelées '''''Preload Data''''' et '''''Preload Enable'''''. La seconde entrée est parfois distincte de l'entrée de réinitialisation, pour permettre de réinitialiser le compteur soit à zéro, soit à la valeur voulue.
Il peut être utile de prévenir quand un débordement d'entier a lieu, à savoir quand le compteur n'a pas assez de bits pour encoder le décompte. Le compteur est alors remis à zéro, dans la plupart des cas. Mais il faut prévenir que le compteur a débordé, ce qui est utile pour fabriquer des circuits diviseurs de fréquence et des ''timers''. Pour cela, on ajoute une '''sortie de débordement''' au compteur, qui est mise à 1 quand le compteur déborde.
Sur les compteurs/décompteurs, il y a une entrée '''''Count Direction''''' qui décide s'il faut compter ou décompter. Typiquement, elle est à 1 s'il faut compter et 0 s'il faut décompter.
[[File:Digital counter signals.jpg|centre|vignette|upright=2|Interface d'un compteur-décompteur.]]
Dans les schémas qui vont suivre, les entrées Enable ne sont pas représentées. Il est sous-entendu qu'il y a une entrée ''Enable'' pour tous les compteurs qui vont suivre. Il existe deux méthodes pour créer de tels compteurs : la première donne ce qu'on appelle des compteurs asynchrones, et l'autre des compteurs synchrones.
==L'intérieur d'un compteur==
A une exception bien précise qu'on abordera plus bas, les compteurs sont composés d'un registre, qui mémorise le décompte, couplé à un circuit '''incrémenteur'''. Nous avions déjà abordé l'incrémenteur dans un chapitre précédent, aussi je ne vais pas ré-expliquer comment il est conçu. Tout ce qu'il faut retenir est qu'il y en a plusieurs types, le plus simple étant celui à propagation de retenue.
[[File:Fonctionnement d'un compteur (décompteur), schématique.jpg|centre|vignette|upright=2|Fonctionnement d'un compteur (décompteur), schématique. Le pas du compteur précise de combien on incrémente le compteur par cycle d'horloge.]]
L'incrémenteur le plus simple, à propagation de retenue, est fabriqué en enchainant des ''demi-additionneurs'' les uns à la suite des autres. Pour rappel, un demi-additionneur additionne deux bits. Ici, il additionne un bit de l'opérande, la retenue des colonnes précédentes. Pour le bit de poids faible, la retenue est forcé à 1. Si on combine un incrémenteur à propagation de retenue avec un registre, on obtient ce compteur :
[[File:Compteur synchrone à incrémenteur.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules D.]]
Une simplification permet de faire disparaitre les portes XOR. Les portes XOR servent ici d'inverseur commandable, commandé par la retenue entrante. Elles inversent le contenu de la bascule quand la retenue entrante vaut 1, elles laissent la bascule inchangée si la retenue vaut 0. Or, nous avons déjà une bascule qui inverse son contenu sous certaines condition : la bascule T ! Il est donc possible de fusionner chaque bascule D avec la porte XOR associée, pour donner une bascule T. Le circuit final est celui-ci :
[[File:Counter sync.png|centre|vignette|upright=2|Compteur synchrone utilisant des bascules T.]]
Il est possible d'optimiser le circuit avec les optimisations vues dans le chapitre sur les incrémenteurs. La première est le ''carry skip'' qui fait l'incrémentation non pas bit par bit, mais par paquets de deux bits. La seconde est l'anticipation de retenue. Et ces deux optimisations se marient bien avec ce qui va suivre.
===Les compteurs en cascade===
Il est possible de concevoir des compteurs à partir de compteurs plus petits, mis en cascade. Par exemple, en créant un compteur 16 bits à partir de compteurs 4 bits, enchainés l'un à la suite de l'autre.
Les compteurs mis en cascade ont les mêmes entrées et sorties que les compteurs normaux, avec cependant un détail très important : ils ont une entrée pour la retenue entrante, et une sortie pour la retenue sortante. L'entrée pour la retenue entrante précise ce qui doit être additionné au bit de poids faible. Si elle vaut zéro, l'incrémenteur n'incrémente pas l'opérande. Si elle vaut 1, le compteur est incrémenté. L'entrée de retenue fait office d'entrée ''Count Enable'', qui active ou désactive l'incrémentation.
Le compteur a aussi une sortie de débordement, qui indique que le compteur déborde. Il se trouve que cette sortie fournit la retenue pour le compteur suivant. La retenue peut être calculée en utilisant des optimisations comme l'anticipation de retenue, ce qui veut dire qu'elle est calculée sans propager les retenues, directement à partir des bits de l'opérande. Concrètement, la retenue est calculée en faisant un ET logique entre tous les bits du décompte.
[[File:Cascadable binary up-counter.jpg|centre|vignette|upright=2|Cascadable binary up-counter]]
Les compteurs sont mis en cascade de la manière suivante : leur sortie de débordement est connectée sur l'entrée ''Enable'' du compteur suivant, celle qui déclenche l'incrémentation du compteur. La sortie de débordement est notée RCO dans les schéma qui suivent, nous verrons pourquoi dans le prochaine paragraphe.
[[File:Cascaded binary counters.jpg|centre|vignette|upright=2.5|Cascaded binary counters]]
===Les compteurs modulo===
La plupart des compteurs comptent de 0 à <math>2^n - 1</math>, avec <math>n</math> la taille du compteur. Mais d'autres compteurs ne comptent pas jusque-là. Par exemple, certains compteurs ne comptent que jusqu'à 10, 150, etc. Ils sont appelés des '''compteurs ''modulo'''''. Prenons un compteur modulo 6, par exemple : il compte de 0 à 5, et est remis immédiatement à zéro quand il atteint 6. Il compte donc comme suit : 0, 1, 2, 3, 4, 5, 0, 1, 2, ...
Les compteurs ''modulo'' sont construits à partir d'un compteur normal, couplé à un circuit comparateur qui remet à zéro le registre quand il atteint la valeur maximale. Par exemple, on peut imaginer un compteur modulo 6 est construit à partir d'un compteur 4 bits qui compte de 0 à 15 (donc un compteur modulo 16), mais qui est remis à zéro quand il atteint 6. Le circuit comparateur vérifie si la valeur maximale 6 est atteinte et met à 1 l'entrée ''Reset'' si c'est le cas. Le comparateur est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Td4bfig4.png|centre|vignette|upright=2|Compteur modulo N.]]
Pour une minorité de compteurs ''modulo'', la valeur maximale est configurable. Pour cela, le compteur est associé à un ''registre de configuration'' qui mémorise la valeur maximale souhaitée. A chaque cycle d'horloge, la valeur dans le compteur est comparée au registre de configuration. Si elles sont identiques, le compteur est remis à zéro. Le compteur est associé au registre de configuration et à un comparateur qui vérifie que les deux sont égaux. Pour le moment, nous ne savons pas faire de circuits comparateurs, ce qui fait qu'on ne peut pas expliquer ce circuit plus en détail.
[[File:4 Bit Counter Prog 1.svg|centre|vignette|upright=2|Compteur 4 bits à valeur maximale programmable.]]
Les compteurs ont tous une sortie de débordement, qui indique quand le compteur déborde. Pour les compteurs modulo, la sortie n'est autre que la sortie du comparateur. Pour les compteurs non-modulo, la sortie de débordement est une sortie du circuit combinatoire qui incrémente le compteur.
===Les mal-nommés ''compteurs asynchrones''===
Les '''compteurs asynchrones''' n'utilisent pas de circuit incrémenteur. A la place, ils sont fabriqués à partir d'un registre à décalage. Ils utilisent moins de portes logiques que les compteurs synchrones. Et ils ne sont pas forcément plus difficiles à comprendre. Précisons cependant qu'ils sont très mal nommés, à savoir que ce sont bien des circuits synchrones.
Pour comprendre comment fonctionne un compteur asynchrone, il faut regarder la séquence des premiers entiers :
* 000 ;
* 001 ;
* 010 ;
* 011 ;
* 100 ;
* 101 ;
* 110 ;
* 111.
Il faut remarquer que le bit de poids faible s'inverse à chaque cycle d'horloge. Pour les colonnes suivantes, le bit s'inverse quand le bit de la colonne précédente passe de 1 à 0, lors d'un front descendant sur la colonne précédente. Maintenant que l'on sait cela, on peut créer un compteur avec des bascules T (elles inversent leur contenu à chaque cycle d'horloge). La première colonne inverse son contenu à chaque cycle, elle correspond donc à une bascule T reliée directement à l'horloge. Les autres colonnes utilisent des bascules T activées sur front descendant.
: Attention, la bascule la plus à gauche stocke le bit de poids faible, pas celui de poids fort. Cela sera pareil dans tous les schémas qui suivront.
[[File:AsyncCounter Alternativ 2.svg|centre|vignette|upright=2|Compteur asynchrone de 4 bits.]]
Le compteur précédent ne peut pas être réinitialisé, ce qui pose des problèmes pour implémenter des compteurs modulo. Pour cela, il faut que les bascules du compteur aient une entrée de réinitialisation ''Reset'', qui les force à se remettre à zéro. Il suffit alors de connecter ensemble les entrées ''Reset'' des bascules à l'entrée ''Reset'' du compteur.
[[File:AsyncCounter mod 8 with RST .svg|centre|vignette|upright=2|Compteur réinitialisable.]]
Implémenter un compteur modulo demande d'ajouter un comparateur qui détecte quand la valeur maximale est atteinte, afin de commander l'entrée de réinitialisation. Un tel circuit est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Modulo-5-Zähler.svg|centre|vignette|upright=2|Compteur modulo 10.]]
Il est aussi possible d'utiliser des bascules D à la place des bascules T simplifiées. En effet, une bascule T simplifiée est identique à une bascule D dont on boucle la sortie /Q sur l'entrée de données. Cette implémentation permet d'ailleurs de réinitialiser le compteur à une valeur non-nulle. Pour cela, l'entrée de chaque bascule D est précédée d'un multiplexeur, qui choisit entre le bit calculé par le compteur et celui présenté sur l'entrée de ré-initialisation. Quand l'entrée ''Reset'' est activée, les multiplexeurs connectent les bascules aux bits sur l'entrée de ré-initialisation. Dans le cas contraire, le compteur fonctionne normalement, les multiplexeurs connectant l'entrée de chaque bascule à sa sortie.
[[File:Compteur asynchrone, avec initialisation.jpg|centre|vignette|upright=2|Compteur asynchrone, avec initialisation.]]
Pour finir, la sortie de débordement n'est autre que la sortie de la dernière bascule, celle qui contient le bit de poids fort.
==Les ''timers'' : compter des durées, compter des cycles d'horloge==
Les '''''timers''''', aussi appelés ''Programmable interval timer'', sont des circuits capables de compter des durées, exprimées en cycles d'horloge. Leur fonctionnement est assez simple : ils émettent un signal quand un certain nombre de cycles est écoulé, ce nombre de cycles étant configurable. On peut ainsi générer un signal qui surviendra après 50 cycles d'horloge, ou après 100 cycles d'horloge, etc. Le signal en question est disponible sur une sortie de 1 bit, et correspond tout simplement au fait que cette sortie est mise à 1, pendant un cycle d'horloge.
Les ''timers'' sont composés d'un compteur/décompteur cadencé par un signal d'horloge. Le compteur initialisé à 0, puis est incrémenté à chaque signal d'horloge, jusqu’à atteinte d'une valeur limite où il génère un signal. Pour un décompteur, c'est la même chose, sauf que le décompteur est initialisé à sa valeur limite et est décrémenté à chaque cycle, et envoie un signal quand il atteint 0. Les ''timers'' basés sur des décompteurs sont nettement plus simples que les autres, ce qui fait qu'ils sont plus utilisés Pour que les ''timers'' soient configurables, on doit pouvoir préciser combien de cycles il faut (dé-)compter avant d'émettre un signal. On peut ainsi préciser s'il faut émettre le signal après 32 cycles d'horloge, après les 50 cycles, tous les 129 cycles, etc. Le nombre de cycles en question est envoyé sur une entrée d’initialisation du compteur.
Les ''timers'' matériels peuvent compter de deux manières différentes, appelées '''mode une fois''' et '''mode périodique'''.
* En mode une fois, le ''timer'' s'arrête une fois qu'il a atteint la limite configurée. On doit le réinitialiser manuellement, par l'intermédiaire du logiciel, pour l'utiliser une nouvelle fois. Cela permet de compter une certaine durée, exprimée en nombre de cycles d'horloge.
* En mode périodique, le ''timer'' se réinitialise automatiquement avec la valeur de départ, ce qui fait qu'il reboucle à l'infini.
Un ordinateur est rempli de ''timers'' divers. Dans ce qui va suivre, nous allons voir les principaux ''timers'', qui sont actuellement intégrés dans les PC modernes. Ils se trouvent sur la carte mère ou dans le processeur, tout dépend du ''timer''.
===Le ''watchdog timer''===
Le '''''watchdog timer''''' est un ''timer'' spécifique dont le but est de redémarrer automatiquement l'ordinateur si jamais celui-ci ne répond plus ou plante. Beaucoup de PC s'en passent, mais ce ''timer'' est très fréquent dans les architectures embarquées. Le ''watchdog timer'' est un compteur/décompteur qui doit être réinitialisé régulièrement. S'il n'est pas réinitialisé, le ''watchdog timer'' déborde (revient à 0 ou atteint 0) et envoie un signal qui redémarre le système. Le système est conçu pour réinitialiser le ''watchdog timer'' régulièrement, ce qui signifie que le système n'est pas censé redémarrer. Si jamais le système dysfonctionne gravement, le système ne pourra pas réinitialiser le ''watchdog timer'' et le système est redémarré automatiquement ou mis en arrêt.
[[File:SimpleWatchdogTimer.gif|centre|vignette|upright=2|Le ''Watchdog Timer'' et l'ordinateur.]]
===Le ''Time Stamp Counter'' des processeurs x86===
Tous les processeurs des PC actuels sont des processeurs dits x86. Nous ne pouvons pas expliquer ce que cela signifie pour le moment, retenez juste ce terme. Sachez que tous les processeurs x86 contiennent un compteur de 64 bits, appelé le '''''Time Stamp Counter''''', qui mémorise le nombre de cycles d'horloge qu'a effectué le processeur depuis son démarrage. Les programmes peuvent accéder à ce registre assez facilement, ce qui est utile pour faire des mesures ou comparer les performances de deux applications. Il permet de compter combien de cycles d'horloge met un morceau de code à s’exécuter, combien de cycles prend une instruction à s’exécuter, etc. Les processeurs non-x86 ont un registre équivalent, que ce soit les processeurs ARM ou d'autres.
Malheureusement, ce compteur est tombé en désuétude pour tout un tas de raisons. La principale est que les processeurs actuels font varier leur fréquence suivant les besoins. Ils augmentent leur fréquence quand on leur demande de faire beaucoup de calculs, et se mettent en mode basse(fréquence pour économiser de l'énergie si on ne leur demande pas grand chose. Avec une fréquence variable, le ''Time Stamp Counter'' perd complétement en fiabilité. Intel a tenté de corriger ce défaut en incrémentant ce registre à une fréquence constante, différente de celle du processeur, ce qui est encore le cas sur les processeurs Intel actuels. Le comportement est un peu différent sur les processeurs AMD, qui cadencent ce ''timer'' à la fréquence du processeur mais utilisent des mécanismes de synchronisation assez complexes pour corriger l'effet de la fréquence variable.
===L'horloge temps réel===
L''''horloge temps réel''' est un ''timer'' qui génère une fréquence de 1024 Hz, soit près d'un Kilohertz. Dans ce qui suit, nous la noterons RTC, ce qui est l'acronyme du terme anglais ''Real Time Clock''. La RTC prend en entrée un signal d'horloge de 32KHz, généré par un oscillateur à Quartz, et fournit en sortie un signal de fréquence 32 fois plus faible, c'est à dire de 1 KHz. Pour cela, elle est réglée en mode répétitif et son décompteur interne est initialisé à 32. La RTC génère donc un signal toutes les millisecondes, qui est envoyé au processeur. On peut, en théorie, changer la fréquence de la RTC, mais c'est rarement une bonne idée.
En théorie, la RTC permet de compter des durées assez courtes, comme le ''ping'' (le temps de latence d'un réseau, pour simplifier), le temps de rafraichissement de l'écran, ou bien d'autres choses. Mais dans les faits, l'horloge temps réel sa fréquence n'aide pas : 1024 Hz est proche de 1000, mais pas assez pour faire des mesures à la milliseconde près, chose qui est nécessaire pour mesurer le ''ping'' ou d'autres choses utiles. A la place, l'ordinateur l'utilise pour que l'ordinateur soit toujours à l'heure. L'ordinateur sait quelle heure il est avec une précision de l'ordre de la seconde (vous pouvez regarder le bureau de Windows dans le coin inférieur droite de votre écran pour vous en convaincre).
===Le ''Programmable Interval Timer'' : l'Intel 8253===
L'Intel 8253 était un ''timer'' programmable autrefois soudé sur les cartes mères des premiers PC. Il fût suivi par l'Intel 8254, qui en était une légère amélioration. Il était cadencé par une horloge maitre, générée par un oscillateur à Quartz, dont la fréquence est de 32 768 Hertz, soit 2^15 cycles d'horloge par seconde. S'il n'est plus présent dans un boitier sur la carte mère, on trouve toujours un circuit semblable au 8253 à l'intérieur du ''chipset'' de la carte mère, voire à l'intérieur du processeur, pour des raisons de compatibilité. L'intérieur de l'Intel 8253 est illustré ci-dessous. Nous allons expliquer l'ensemble de ce schéma, rassurez-vous, mais les explications seront plus simples à comprendre si vous survolez ce schéma en premier lieu.
[[File:Intel 8253 block diagram.svg|centre|vignette|upright=2|Intel 8253, intérieur.]]
L'Intel 8253 contient trois compteurs de 16 bits, numérotés 0, 1 et 2. Pour chaque compteur, l'entrée CLOCK est celle de l'horloge de 32 MHz, l'entrée GATE active ou désactive le compteur, la sortie fournit le signal voulu et/ou la fréquence de sortie. Les trois compteurs étaient utilisés pour dériver plusieurs fréquences allant de 18,2 Hz à environ 500 KHz. Par exemple, il était utilisé par défaut pour le rafraichissement de la mémoire (D)RAM, mais il était souvent reprogrammé pour servir à générer des fréquences spécifiques par le BIOS ou la carte graphique.
[[File:Intel 8253 and 8254.svg|vignette|Intel 8253 and 8254]]
L'Intel 8253 lui-même possède plusieurs entrées et sorties. En premier lieu, on voit un port de 8 bits connecté aux trois compteurs, qui permet à l'Intel 8253 de communiquer avec le reste de l'ordinateur. La communication se fait dans les deux sens : soit de l'ordinateur vers les compteurs, soit des compteurs vers l'ordinateur. Dans le sens ordinateur -> compteurs, cela permet à l'ordinateur de programmer les compteurs, de les initialiser. Dans l'autre sens, cela permet de récupérer le contenu des compteurs, même si ce n'est pas très utilisé. Il y a aussi 5 entrées de configuration :
* Deux bits A0 et A1 pour sélectionner le compteur voulu avec son numéro.
* Un bit RD à mettre à 0 pour que l'ordinateur récupère le compteur sélectionné sur le port de 8 bits.
* Un bit WR à mettre à 0 pour que l'ordinateur modifie le compteur sélectionné, en envoyant le nombre pour l'initialisation sur le port de 8 bits.
* Un bit CS qui active ou désactive l'Intel 8253 et permet de l'allumer ou de l’éteindre.
L'Intel 8253 intégre un registre de 8 bits, le ''Control Word register'' qui mémorise la configuration de l'Intel 8253. Pour programmer les trois compteurs, il faut écrire un mot de 8 bits dans ce ''Control Word register''. Pour écrire dans le ''Control Word register'', il faut mettre le bit CS à 0 (on active l'Intel 8253), mettre le bit RDà 1 , le bit WR à 0 le bit WR (on indique qu'on fait une écriture), sélectionner le ''Control Word register'' en mettant les deux bits A0 et A1 à 1, puis envoyer la configuration du ''Control Word register'' sur le port de 8 bits.
===Le ''High Precision Event Timer'' (HPET)===
De nos jours, l'horloge temps réel et l'Intel 8253/8254 tendent à être remplacé par un autre ''timer'', le ''High Precision Event Timer'' (HPET). Il s'agit d'un compteur de 64 bits, dont la fréquence est d'au moins 10 MHz. Il s'agit bien d'un compteur et non d'un décompteur. Il gère nativement plusieurs valeurs limites à laquelle générer un signal, qui sont configurables. Pour cela, il est couplé à plusieurs comparateurs, chacun associé à un registre pour mémoriser la valeur limite. Il doit y avoir au moins trois comparateurs/registres, mais le nombre peut monter jusqu’à 256.
[[File:High Precision Event Timer.png|centre|vignette|upright=2|High Precision Event Timer]]
Il faut noter que les systèmes d'exploitation conçus avant le HPET ne peuvent pas l'utiliser, pour des raisons de compatibilité matérielle. C'est le cas de Windows XP avant le Service Pack 3. C'est la raison pour laquelle les cartes mères émulent RTC et PIT dans leurs circuits. D'ailleurs, pour économiser des circuits, les cartes mères modernes émulent le PIT et la RTC avec le HPET : le premier comparateur fournisse la fréquence de 1024 Hz de la RTC, 3 autres comparateurs remplacent l'Intel 8253.
Le HPET gère de nombreux modes de fonctionnement : ses comparateurs peuvent être configuré en mode une fois ou périodique, on peut lui demander d'émuler la RTC et le PIT, etc. Chaque comparateur doit pouvoir fonctionner en mode une fois, et au moins un comparateur doit pouvoir fonctionner en mode périodique. Aussi, il contient aussi 3 registres de configuration. Notons qu'il est aussi possible de lire ou écrire dans le compteur de 64 bits, mais ce n'est pas recommandé.
==Les compteurs utilisant des représentations binaires non-classiques==
Les compteurs classiques encodent leur décompte en binaire normal sur <math>n</math> bits, mais il faut savoir que d'autres compteurs utilisent le BCD, d'autre le code Gray, etc. Nous allons voir dans ce qui suit deux types de compteurs : ceux qui comptent en code Gray, ceux qui comptent en représentation ''one-hot''.
===Les compteurs en code Gray===
Pour rappel, le code Gray permet de coder des nombres d'une manière un peu différente du binaire normal. Son avantage principal est que lorsqu'on incrémente ou décrémente un nombre, seul un bit change ! Pour comparer, en binaire normal, lorsqu'on passe incrémente un nombre, il peut y avoir plusieurs bits qui changent. La moyenne est d'environ deux bits par incrémentation. Les compteurs en code Gray ont beaucoup d'avantages, qui sont tous liés à cette propriété.
Le premier l'absence d'état transitoires douteux. Le problème est que les bits modifiés par une incrémentation ne le sont pas en même temps. Les bits de poids faibles sont modifiés avant les autres. Évidemment, à la fin du calcul, on obtient le résultat final, correct. Mais pendant le temps de calcul, le compteur peut se retrouver dans un état transitoire, où seuls les bits de poids faibles ont été modifiés. C'est un problème si le contenu de ce compteur est relié à des circuits rapides, qui peuvent voir cet état transitoire, mais ne le doivent pas sous peine de dysfonctionner. L'usage de compteurs en code Gray permet d'éviter ce problème : vu que seul un bit est modifié lors d'une incrémentation/décrémentation, les états transitoires n'existent tout simplement pas.
Un exemple typique, évoqué dans les chapitres précédents, est l'échange d'informations entre deux domaines d'horloge. Pour rappel, il arrive que deux portions d'un circuit imprimé aillent à des fréquences différences : on dit que le circuit à plusieurs domaines d'horloge. S'il faut échanger des informations entre ces deux domaines d'horloge, divers problèmes surviennent. Un domaine d'horloge sera plus rapide que l'autre, et pourra voir les états transitoires invisible de l'autre circuit. Et par voir, on veut dire qu'il les prendra pour des états valides, ce qui fera dysfonctionner le circuit. Pour éviter cela, diverses techniques de croisement de domaines d'horloge existent. Et les compteurs Gray en font partie : si un domaine d'horloge utilise la valeur d'un compteur de l'autre, mieux vaut que ce compteur soit un compteur Gray. Et cette situation est assez fréquente !
Un autre avantage mineurs est que la consommation d'énergie de ces compteurs est bien plus réduite qu'avec un compteur normal. Rappelons que pour fonctionner, les circuits électroniques consomment un peu d'électricité. Et la majeure partie de cette consommation sert à faire passer un bit de 0 à 1 ou de 1 à 0. Ce qui fait que quand un compteur est incrémenté ou décrémenté, cela consomme un peu d'énergie électrique.
La moyenne pour un compteur binaire normal est de 2 bits changés par incrémentation/décrémentation, contre un seul pour un compteur Gray, on devine que ces derniers consomment deux fois moins d'énergie par incrémentation. Et cet avantage a des effets en cascade sur les circuits qui suivent ce compteur. Si l'entrée de ces circuits ne change que d'un seul bit, alors leur état changera moins que si c'était deux bits. Les circuits qui suivent vont donc moins consommer.
Un autre avantage en matière de consommation énergétique est lié auxs transitions d'état douteuses. Les circuits connectés au compteur vont voir ces transitions d'état douteuses et modifier leur état interne en réaction. Bien sur, l'état final correct sera atteint une fois que le compteur sera stabilisé, ce qui effacera ces états transitoires intermédiaires. Mais chaque état intermédiaire transitoire correspond à un changement d'état, donc à une consommation d'énergie. En supprimant ces états transitoires, on réduit fortement la consommation d'énergie du circuit. Cela vaut pour le compteur Gray lui-même, mais aussi sur tous les circuits qui ont ce compteur comme entrée !
===Les compteurs ''one-hot''===
Les '''compteurs ''one-hot''''' sont appelés ainsi, car ils permettent de compter dans une représentation des nombres appelée la représentation ''one-hot''. Pour rappel, dans une telle représentation, un seul bit est à 1 pendant que les autres sont à 0. Les entiers sont codés de la manière suivante : le nombre N est encodé en mettant le énième bit à 1, avec la condition que l'on commence à compteur à partir de zéro. Il est important de remarquer que dans cette représentation, le zéro est n'est PAS codé en mettant tous les bits à 0, la valeur 0000...0000 est une valeur interdite. À la place, le zéro est codé en mettant le bit de poids faible à 1. Pour N bits, on peut encoder seulement N valeurs, dont le zéro.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! ''One-hot''
|-
| 0 || 000 || 00000001
|-
| 1 || 001 || 00000010
|-
| 2 || 010 || 00000100
|-
| 3 || 011 || 00001000
|-
| 4 || 100 || 00010000
|-
| 5 || 101 || 00100000
|-
| 6 || 110 || 01000000
|-
| 7 || 111 || 10000000
|}
Un compteur en représentation ''one-hot'' contient un nombre codé de cette manière, qui est incrémenté ou décrémenté si besoin. Pour donner un exemple, la séquence d'un compteur en anneau de 4 bits est :
* 0001 (0) ;
* 0010 (1) ;
* 0100 (2) ;
* 1000 (3) .
Un compteur ''one-hot'' basique est composé d'un registre à décalage dont on boucle la sortie sur son entrée. En faisant cela, on garantit que le registre revient à zéro lors d'un débordement, zéro étant codé avec un 1 dans le bit de poids faible. Au passage, si vous ne mettez que des 0 dans un compteur en anneau, il restera bloqué pour toujours : décaler une suite de 0 donnera la même suite de 0. Initialiser un compteur ''one-hot'' demande donc quelques subtilités qu'on détaillera plus bas.
[[File:Compteur en anneau de 4 bits.jpg|centre|vignette|upright=2|Compteur en anneau de 4 bits]]
Un compteur ''one-hot'' de N bits peut prendre N valeurs différentes, qui ont toutes un seul bit à 1. Pour 5 bits, la séquence est illustrée ci-dessous.
[[File:Lauflicht 5 LED.gif|centre|vignette|Compteur en anneau de 5 bits.]]
Faire des comparaisons avec ce type de compteur est très simple : le compteur contient la valeur N si le énième bit est à 1. Pas besoin d'utiliser de circuit comparateur, juste de lire un bit. Par contre, ce compteur n'est pas très économe en bascules. Imaginons que l'on veut un compteur qui compte jusqu'à une valeur N arbitraire : un compteur binaire normal utilisera environ <math>\log_2{(N)}</math> bascules, alors qu'un compteur ''one-hot'' demande N bascules. Mais si N est assez petit, l'économie de bascules est assez faible, alors que l'économie de circuits comparateurs/incrémenteurs l'est beaucoup plus.
Il y a peu d'applications qui utilisent des compteurs en anneau. Ils étaient autrefois utilisés dans les tous premiers ordinateurs, notamment ceux qui géraient une représentation des nombres spécifique appelée la '''''Bi-quinary coded decimal'''''. De nos jours, de tels compteurs sont utilisés dans les séquenceurs de processeurs, mais aussi dans les séquenceurs de certains périphériques, ou dans les circuits séquentiels simples qui se résument à des machines à états. Ils sont alors utilisés car très rapides, parfaitement adaptés au stockage de petites valeur, et surtout : ils n'ont pas besoin de circuit comparateur pour connaitre la valeur stockée dedans. Nous n'allons pas rentrer dans le détail de leurs utilisations, car nous en reparlerons dans la suite du cours.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les registres et mémoires adressables
| prevText=Les registres et mémoires adressables
| next=Les registres à décalage à rétroaction linéaire
| nextText=Les registres à décalage à rétroaction linéaire
}}
</noinclude>
msofrw45zm2awwwajrz4tjgut07lppd
768601
768600
2026-06-25T14:46:51Z
Mewtow
31375
/* L'intérieur d'un compteur */
768601
wikitext
text/x-wiki
Les '''compteurs/décompteurs''' sont des circuits électroniques qui mémorisent un nombre et l'incrémentent à la demande. En clair, ce sont des registres améliorés afin de supporter l'incrémentation et la décrémentation. Pour donner un exemple d'utilisation, imaginez un circuit qui compte le nombre de voitures dans un parking dans la journée. Pour cela, vous allez prendre deux circuits qui détectent respectivement l'entrée ou la sortie d'une voiture, et un compteur. Le compteur est initialisé à 0 quand le parking est vide, puis est incrémenté à chaque entrée de voiture, décrémenté à chaque sortie. Les exemples de ce type sont suffisamment nombreux pour qu'on dédie un chapitre aux compteurs.
[[File:Binary counter.gif|vignette|Illustration du fonctionnement d'un compteur modulaire binaire de 4 bits, avec un pas de compteur de 1 (le contenu est augmenté de 1 à chaque mise à jour).]]
Un compteur mémorise un nombre qui est incrémenté ou décrémenté au besoin. Le nombre mémorisé sera appelé le '''décompte''' dans ce qui suit. Il est mémorisé dans un registre à l'intérieur du compteur. Au passage, le nombre de bits <math>n</math> du compteur est appelé la '''taille du compteur''', par analogie avec les registres. Il faut cependant faire la différence entre les ''compteurs'' d'un côté et les ''décompteurs'' de l'autre. Les compteurs incrémentent le décompte, les décompteurs le décrémentent, les compteurs-décompteurs peuvent faire les deux.
==L'interface d'un compteur/décompteur==
Les compteurs et décompteurs sont des circuits synchrones et ont donc une entrée d'horloge. Les compteurs les plus simples incrémentent leur contenu à chaque cycle d'horloge, mais les plus fréquents n'incrémentent le décompte que sur demande. Pour cela, ils disposent d'une entrée '''''Count Enable''''', similaire à l'entrée ''Enable'' des registres, séparée de l'entrée d'horloge. Le décompte est incrémenté/décrémenté seulement si l'entrée Enable est à 1, lors d'un front adéquat sur le signal d'horloge.
Les compteurs ont aussi une entrée '''''Reset''''' qui permet de les remettre à zéro. Il y a parfois une entrée qui permet d'initialiser le compteur à une valeur par défaut, non-nulle. Par exemple, on peut initialiser le décompte à la valeur 5, ou une autre. Pour cela, le compteur dispose de deux entrées : une entrée sur laquelle envoyer le décompte initial, une entrée pour autoriser la réinitialisation. Les entrées en question sont appelées '''''Preload Data''''' et '''''Preload Enable'''''. La seconde entrée est parfois distincte de l'entrée de réinitialisation, pour permettre de réinitialiser le compteur soit à zéro, soit à la valeur voulue.
Il peut être utile de prévenir quand un débordement d'entier a lieu, à savoir quand le compteur n'a pas assez de bits pour encoder le décompte. Le compteur est alors remis à zéro, dans la plupart des cas. Mais il faut prévenir que le compteur a débordé, ce qui est utile pour fabriquer des circuits diviseurs de fréquence et des ''timers''. Pour cela, on ajoute une '''sortie de débordement''' au compteur, qui est mise à 1 quand le compteur déborde.
Sur les compteurs/décompteurs, il y a une entrée '''''Count Direction''''' qui décide s'il faut compter ou décompter. Typiquement, elle est à 1 s'il faut compter et 0 s'il faut décompter.
[[File:Digital counter signals.jpg|centre|vignette|upright=2|Interface d'un compteur-décompteur.]]
Dans les schémas qui vont suivre, les entrées Enable ne sont pas représentées. Il est sous-entendu qu'il y a une entrée ''Enable'' pour tous les compteurs qui vont suivre. Il existe deux méthodes pour créer de tels compteurs : la première donne ce qu'on appelle des compteurs asynchrones, et l'autre des compteurs synchrones.
==L'intérieur d'un compteur==
A une exception bien précise qu'on abordera plus bas, les compteurs sont composés d'un registre, qui mémorise le décompte, couplé à un circuit '''incrémenteur'''. Nous avions déjà abordé l'incrémenteur dans un chapitre précédent, aussi je ne vais pas ré-expliquer comment il est conçu. Tout ce qu'il faut retenir est qu'il y en a plusieurs types, le plus simple étant celui à propagation de retenue.
[[File:Fonctionnement d'un compteur (décompteur), schématique.jpg|centre|vignette|upright=2|Fonctionnement d'un compteur (décompteur), schématique. Le pas du compteur précise de combien on incrémente le compteur par cycle d'horloge.]]
L'incrémenteur le plus simple, à propagation de retenue, est fabriqué en enchainant des ''demi-additionneurs'' les uns à la suite des autres. Pour rappel, un demi-additionneur additionne deux bits. Ici, il additionne un bit de l'opérande, la retenue des colonnes précédentes. Pour le bit de poids faible, la retenue est forcé à 1. Si on combine un incrémenteur à propagation de retenue avec un registre, on obtient ce compteur :
[[File:Compteur synchrone à incrémenteur.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules D.]]
Une simplification permet de faire disparaitre les portes XOR. Les portes XOR servent ici d'inverseur commandable, commandé par la retenue entrante. Elles inversent le contenu de la bascule quand la retenue entrante vaut 1, elles laissent la bascule inchangée si la retenue vaut 0. Or, nous avons déjà une bascule qui inverse son contenu sous certaines condition : la bascule T ! Il est donc possible de fusionner chaque bascule D avec la porte XOR associée, pour donner une bascule T. Le circuit final est celui-ci :
[[File:Compteur synchrone à bascules T.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules T.]]
Il est possible d'optimiser le circuit avec les optimisations vues dans le chapitre sur les incrémenteurs. La première est le ''carry skip'' qui fait l'incrémentation non pas bit par bit, mais par paquets de deux bits. La seconde est l'anticipation de retenue. Et ces deux optimisations se marient bien avec ce qui va suivre.
===Les compteurs en cascade===
Il est possible de concevoir des compteurs à partir de compteurs plus petits, mis en cascade. Par exemple, en créant un compteur 16 bits à partir de compteurs 4 bits, enchainés l'un à la suite de l'autre.
Les compteurs mis en cascade ont les mêmes entrées et sorties que les compteurs normaux, avec cependant un détail très important : ils ont une entrée pour la retenue entrante, et une sortie pour la retenue sortante. L'entrée pour la retenue entrante précise ce qui doit être additionné au bit de poids faible. Si elle vaut zéro, l'incrémenteur n'incrémente pas l'opérande. Si elle vaut 1, le compteur est incrémenté. L'entrée de retenue fait office d'entrée ''Count Enable'', qui active ou désactive l'incrémentation.
Le compteur a aussi une sortie de débordement, qui indique que le compteur déborde. Il se trouve que cette sortie fournit la retenue pour le compteur suivant. La retenue peut être calculée en utilisant des optimisations comme l'anticipation de retenue, ce qui veut dire qu'elle est calculée sans propager les retenues, directement à partir des bits de l'opérande. Concrètement, la retenue est calculée en faisant un ET logique entre tous les bits du décompte.
[[File:Cascadable binary up-counter.jpg|centre|vignette|upright=2|Cascadable binary up-counter]]
Les compteurs sont mis en cascade de la manière suivante : leur sortie de débordement est connectée sur l'entrée ''Enable'' du compteur suivant, celle qui déclenche l'incrémentation du compteur. La sortie de débordement est notée RCO dans les schéma qui suivent, nous verrons pourquoi dans le prochaine paragraphe.
[[File:Cascaded binary counters.jpg|centre|vignette|upright=2.5|Cascaded binary counters]]
===Les compteurs modulo===
La plupart des compteurs comptent de 0 à <math>2^n - 1</math>, avec <math>n</math> la taille du compteur. Mais d'autres compteurs ne comptent pas jusque-là. Par exemple, certains compteurs ne comptent que jusqu'à 10, 150, etc. Ils sont appelés des '''compteurs ''modulo'''''. Prenons un compteur modulo 6, par exemple : il compte de 0 à 5, et est remis immédiatement à zéro quand il atteint 6. Il compte donc comme suit : 0, 1, 2, 3, 4, 5, 0, 1, 2, ...
Les compteurs ''modulo'' sont construits à partir d'un compteur normal, couplé à un circuit comparateur qui remet à zéro le registre quand il atteint la valeur maximale. Par exemple, on peut imaginer un compteur modulo 6 est construit à partir d'un compteur 4 bits qui compte de 0 à 15 (donc un compteur modulo 16), mais qui est remis à zéro quand il atteint 6. Le circuit comparateur vérifie si la valeur maximale 6 est atteinte et met à 1 l'entrée ''Reset'' si c'est le cas. Le comparateur est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Td4bfig4.png|centre|vignette|upright=2|Compteur modulo N.]]
Pour une minorité de compteurs ''modulo'', la valeur maximale est configurable. Pour cela, le compteur est associé à un ''registre de configuration'' qui mémorise la valeur maximale souhaitée. A chaque cycle d'horloge, la valeur dans le compteur est comparée au registre de configuration. Si elles sont identiques, le compteur est remis à zéro. Le compteur est associé au registre de configuration et à un comparateur qui vérifie que les deux sont égaux. Pour le moment, nous ne savons pas faire de circuits comparateurs, ce qui fait qu'on ne peut pas expliquer ce circuit plus en détail.
[[File:4 Bit Counter Prog 1.svg|centre|vignette|upright=2|Compteur 4 bits à valeur maximale programmable.]]
Les compteurs ont tous une sortie de débordement, qui indique quand le compteur déborde. Pour les compteurs modulo, la sortie n'est autre que la sortie du comparateur. Pour les compteurs non-modulo, la sortie de débordement est une sortie du circuit combinatoire qui incrémente le compteur.
===Les mal-nommés ''compteurs asynchrones''===
Les '''compteurs asynchrones''' n'utilisent pas de circuit incrémenteur. A la place, ils sont fabriqués à partir d'un registre à décalage. Ils utilisent moins de portes logiques que les compteurs synchrones. Et ils ne sont pas forcément plus difficiles à comprendre. Précisons cependant qu'ils sont très mal nommés, à savoir que ce sont bien des circuits synchrones.
Pour comprendre comment fonctionne un compteur asynchrone, il faut regarder la séquence des premiers entiers :
* 000 ;
* 001 ;
* 010 ;
* 011 ;
* 100 ;
* 101 ;
* 110 ;
* 111.
Il faut remarquer que le bit de poids faible s'inverse à chaque cycle d'horloge. Pour les colonnes suivantes, le bit s'inverse quand le bit de la colonne précédente passe de 1 à 0, lors d'un front descendant sur la colonne précédente. Maintenant que l'on sait cela, on peut créer un compteur avec des bascules T (elles inversent leur contenu à chaque cycle d'horloge). La première colonne inverse son contenu à chaque cycle, elle correspond donc à une bascule T reliée directement à l'horloge. Les autres colonnes utilisent des bascules T activées sur front descendant.
: Attention, la bascule la plus à gauche stocke le bit de poids faible, pas celui de poids fort. Cela sera pareil dans tous les schémas qui suivront.
[[File:AsyncCounter Alternativ 2.svg|centre|vignette|upright=2|Compteur asynchrone de 4 bits.]]
Le compteur précédent ne peut pas être réinitialisé, ce qui pose des problèmes pour implémenter des compteurs modulo. Pour cela, il faut que les bascules du compteur aient une entrée de réinitialisation ''Reset'', qui les force à se remettre à zéro. Il suffit alors de connecter ensemble les entrées ''Reset'' des bascules à l'entrée ''Reset'' du compteur.
[[File:AsyncCounter mod 8 with RST .svg|centre|vignette|upright=2|Compteur réinitialisable.]]
Implémenter un compteur modulo demande d'ajouter un comparateur qui détecte quand la valeur maximale est atteinte, afin de commander l'entrée de réinitialisation. Un tel circuit est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Modulo-5-Zähler.svg|centre|vignette|upright=2|Compteur modulo 10.]]
Il est aussi possible d'utiliser des bascules D à la place des bascules T simplifiées. En effet, une bascule T simplifiée est identique à une bascule D dont on boucle la sortie /Q sur l'entrée de données. Cette implémentation permet d'ailleurs de réinitialiser le compteur à une valeur non-nulle. Pour cela, l'entrée de chaque bascule D est précédée d'un multiplexeur, qui choisit entre le bit calculé par le compteur et celui présenté sur l'entrée de ré-initialisation. Quand l'entrée ''Reset'' est activée, les multiplexeurs connectent les bascules aux bits sur l'entrée de ré-initialisation. Dans le cas contraire, le compteur fonctionne normalement, les multiplexeurs connectant l'entrée de chaque bascule à sa sortie.
[[File:Compteur asynchrone, avec initialisation.jpg|centre|vignette|upright=2|Compteur asynchrone, avec initialisation.]]
Pour finir, la sortie de débordement n'est autre que la sortie de la dernière bascule, celle qui contient le bit de poids fort.
==Les ''timers'' : compter des durées, compter des cycles d'horloge==
Les '''''timers''''', aussi appelés ''Programmable interval timer'', sont des circuits capables de compter des durées, exprimées en cycles d'horloge. Leur fonctionnement est assez simple : ils émettent un signal quand un certain nombre de cycles est écoulé, ce nombre de cycles étant configurable. On peut ainsi générer un signal qui surviendra après 50 cycles d'horloge, ou après 100 cycles d'horloge, etc. Le signal en question est disponible sur une sortie de 1 bit, et correspond tout simplement au fait que cette sortie est mise à 1, pendant un cycle d'horloge.
Les ''timers'' sont composés d'un compteur/décompteur cadencé par un signal d'horloge. Le compteur initialisé à 0, puis est incrémenté à chaque signal d'horloge, jusqu’à atteinte d'une valeur limite où il génère un signal. Pour un décompteur, c'est la même chose, sauf que le décompteur est initialisé à sa valeur limite et est décrémenté à chaque cycle, et envoie un signal quand il atteint 0. Les ''timers'' basés sur des décompteurs sont nettement plus simples que les autres, ce qui fait qu'ils sont plus utilisés Pour que les ''timers'' soient configurables, on doit pouvoir préciser combien de cycles il faut (dé-)compter avant d'émettre un signal. On peut ainsi préciser s'il faut émettre le signal après 32 cycles d'horloge, après les 50 cycles, tous les 129 cycles, etc. Le nombre de cycles en question est envoyé sur une entrée d’initialisation du compteur.
Les ''timers'' matériels peuvent compter de deux manières différentes, appelées '''mode une fois''' et '''mode périodique'''.
* En mode une fois, le ''timer'' s'arrête une fois qu'il a atteint la limite configurée. On doit le réinitialiser manuellement, par l'intermédiaire du logiciel, pour l'utiliser une nouvelle fois. Cela permet de compter une certaine durée, exprimée en nombre de cycles d'horloge.
* En mode périodique, le ''timer'' se réinitialise automatiquement avec la valeur de départ, ce qui fait qu'il reboucle à l'infini.
Un ordinateur est rempli de ''timers'' divers. Dans ce qui va suivre, nous allons voir les principaux ''timers'', qui sont actuellement intégrés dans les PC modernes. Ils se trouvent sur la carte mère ou dans le processeur, tout dépend du ''timer''.
===Le ''watchdog timer''===
Le '''''watchdog timer''''' est un ''timer'' spécifique dont le but est de redémarrer automatiquement l'ordinateur si jamais celui-ci ne répond plus ou plante. Beaucoup de PC s'en passent, mais ce ''timer'' est très fréquent dans les architectures embarquées. Le ''watchdog timer'' est un compteur/décompteur qui doit être réinitialisé régulièrement. S'il n'est pas réinitialisé, le ''watchdog timer'' déborde (revient à 0 ou atteint 0) et envoie un signal qui redémarre le système. Le système est conçu pour réinitialiser le ''watchdog timer'' régulièrement, ce qui signifie que le système n'est pas censé redémarrer. Si jamais le système dysfonctionne gravement, le système ne pourra pas réinitialiser le ''watchdog timer'' et le système est redémarré automatiquement ou mis en arrêt.
[[File:SimpleWatchdogTimer.gif|centre|vignette|upright=2|Le ''Watchdog Timer'' et l'ordinateur.]]
===Le ''Time Stamp Counter'' des processeurs x86===
Tous les processeurs des PC actuels sont des processeurs dits x86. Nous ne pouvons pas expliquer ce que cela signifie pour le moment, retenez juste ce terme. Sachez que tous les processeurs x86 contiennent un compteur de 64 bits, appelé le '''''Time Stamp Counter''''', qui mémorise le nombre de cycles d'horloge qu'a effectué le processeur depuis son démarrage. Les programmes peuvent accéder à ce registre assez facilement, ce qui est utile pour faire des mesures ou comparer les performances de deux applications. Il permet de compter combien de cycles d'horloge met un morceau de code à s’exécuter, combien de cycles prend une instruction à s’exécuter, etc. Les processeurs non-x86 ont un registre équivalent, que ce soit les processeurs ARM ou d'autres.
Malheureusement, ce compteur est tombé en désuétude pour tout un tas de raisons. La principale est que les processeurs actuels font varier leur fréquence suivant les besoins. Ils augmentent leur fréquence quand on leur demande de faire beaucoup de calculs, et se mettent en mode basse(fréquence pour économiser de l'énergie si on ne leur demande pas grand chose. Avec une fréquence variable, le ''Time Stamp Counter'' perd complétement en fiabilité. Intel a tenté de corriger ce défaut en incrémentant ce registre à une fréquence constante, différente de celle du processeur, ce qui est encore le cas sur les processeurs Intel actuels. Le comportement est un peu différent sur les processeurs AMD, qui cadencent ce ''timer'' à la fréquence du processeur mais utilisent des mécanismes de synchronisation assez complexes pour corriger l'effet de la fréquence variable.
===L'horloge temps réel===
L''''horloge temps réel''' est un ''timer'' qui génère une fréquence de 1024 Hz, soit près d'un Kilohertz. Dans ce qui suit, nous la noterons RTC, ce qui est l'acronyme du terme anglais ''Real Time Clock''. La RTC prend en entrée un signal d'horloge de 32KHz, généré par un oscillateur à Quartz, et fournit en sortie un signal de fréquence 32 fois plus faible, c'est à dire de 1 KHz. Pour cela, elle est réglée en mode répétitif et son décompteur interne est initialisé à 32. La RTC génère donc un signal toutes les millisecondes, qui est envoyé au processeur. On peut, en théorie, changer la fréquence de la RTC, mais c'est rarement une bonne idée.
En théorie, la RTC permet de compter des durées assez courtes, comme le ''ping'' (le temps de latence d'un réseau, pour simplifier), le temps de rafraichissement de l'écran, ou bien d'autres choses. Mais dans les faits, l'horloge temps réel sa fréquence n'aide pas : 1024 Hz est proche de 1000, mais pas assez pour faire des mesures à la milliseconde près, chose qui est nécessaire pour mesurer le ''ping'' ou d'autres choses utiles. A la place, l'ordinateur l'utilise pour que l'ordinateur soit toujours à l'heure. L'ordinateur sait quelle heure il est avec une précision de l'ordre de la seconde (vous pouvez regarder le bureau de Windows dans le coin inférieur droite de votre écran pour vous en convaincre).
===Le ''Programmable Interval Timer'' : l'Intel 8253===
L'Intel 8253 était un ''timer'' programmable autrefois soudé sur les cartes mères des premiers PC. Il fût suivi par l'Intel 8254, qui en était une légère amélioration. Il était cadencé par une horloge maitre, générée par un oscillateur à Quartz, dont la fréquence est de 32 768 Hertz, soit 2^15 cycles d'horloge par seconde. S'il n'est plus présent dans un boitier sur la carte mère, on trouve toujours un circuit semblable au 8253 à l'intérieur du ''chipset'' de la carte mère, voire à l'intérieur du processeur, pour des raisons de compatibilité. L'intérieur de l'Intel 8253 est illustré ci-dessous. Nous allons expliquer l'ensemble de ce schéma, rassurez-vous, mais les explications seront plus simples à comprendre si vous survolez ce schéma en premier lieu.
[[File:Intel 8253 block diagram.svg|centre|vignette|upright=2|Intel 8253, intérieur.]]
L'Intel 8253 contient trois compteurs de 16 bits, numérotés 0, 1 et 2. Pour chaque compteur, l'entrée CLOCK est celle de l'horloge de 32 MHz, l'entrée GATE active ou désactive le compteur, la sortie fournit le signal voulu et/ou la fréquence de sortie. Les trois compteurs étaient utilisés pour dériver plusieurs fréquences allant de 18,2 Hz à environ 500 KHz. Par exemple, il était utilisé par défaut pour le rafraichissement de la mémoire (D)RAM, mais il était souvent reprogrammé pour servir à générer des fréquences spécifiques par le BIOS ou la carte graphique.
[[File:Intel 8253 and 8254.svg|vignette|Intel 8253 and 8254]]
L'Intel 8253 lui-même possède plusieurs entrées et sorties. En premier lieu, on voit un port de 8 bits connecté aux trois compteurs, qui permet à l'Intel 8253 de communiquer avec le reste de l'ordinateur. La communication se fait dans les deux sens : soit de l'ordinateur vers les compteurs, soit des compteurs vers l'ordinateur. Dans le sens ordinateur -> compteurs, cela permet à l'ordinateur de programmer les compteurs, de les initialiser. Dans l'autre sens, cela permet de récupérer le contenu des compteurs, même si ce n'est pas très utilisé. Il y a aussi 5 entrées de configuration :
* Deux bits A0 et A1 pour sélectionner le compteur voulu avec son numéro.
* Un bit RD à mettre à 0 pour que l'ordinateur récupère le compteur sélectionné sur le port de 8 bits.
* Un bit WR à mettre à 0 pour que l'ordinateur modifie le compteur sélectionné, en envoyant le nombre pour l'initialisation sur le port de 8 bits.
* Un bit CS qui active ou désactive l'Intel 8253 et permet de l'allumer ou de l’éteindre.
L'Intel 8253 intégre un registre de 8 bits, le ''Control Word register'' qui mémorise la configuration de l'Intel 8253. Pour programmer les trois compteurs, il faut écrire un mot de 8 bits dans ce ''Control Word register''. Pour écrire dans le ''Control Word register'', il faut mettre le bit CS à 0 (on active l'Intel 8253), mettre le bit RDà 1 , le bit WR à 0 le bit WR (on indique qu'on fait une écriture), sélectionner le ''Control Word register'' en mettant les deux bits A0 et A1 à 1, puis envoyer la configuration du ''Control Word register'' sur le port de 8 bits.
===Le ''High Precision Event Timer'' (HPET)===
De nos jours, l'horloge temps réel et l'Intel 8253/8254 tendent à être remplacé par un autre ''timer'', le ''High Precision Event Timer'' (HPET). Il s'agit d'un compteur de 64 bits, dont la fréquence est d'au moins 10 MHz. Il s'agit bien d'un compteur et non d'un décompteur. Il gère nativement plusieurs valeurs limites à laquelle générer un signal, qui sont configurables. Pour cela, il est couplé à plusieurs comparateurs, chacun associé à un registre pour mémoriser la valeur limite. Il doit y avoir au moins trois comparateurs/registres, mais le nombre peut monter jusqu’à 256.
[[File:High Precision Event Timer.png|centre|vignette|upright=2|High Precision Event Timer]]
Il faut noter que les systèmes d'exploitation conçus avant le HPET ne peuvent pas l'utiliser, pour des raisons de compatibilité matérielle. C'est le cas de Windows XP avant le Service Pack 3. C'est la raison pour laquelle les cartes mères émulent RTC et PIT dans leurs circuits. D'ailleurs, pour économiser des circuits, les cartes mères modernes émulent le PIT et la RTC avec le HPET : le premier comparateur fournisse la fréquence de 1024 Hz de la RTC, 3 autres comparateurs remplacent l'Intel 8253.
Le HPET gère de nombreux modes de fonctionnement : ses comparateurs peuvent être configuré en mode une fois ou périodique, on peut lui demander d'émuler la RTC et le PIT, etc. Chaque comparateur doit pouvoir fonctionner en mode une fois, et au moins un comparateur doit pouvoir fonctionner en mode périodique. Aussi, il contient aussi 3 registres de configuration. Notons qu'il est aussi possible de lire ou écrire dans le compteur de 64 bits, mais ce n'est pas recommandé.
==Les compteurs utilisant des représentations binaires non-classiques==
Les compteurs classiques encodent leur décompte en binaire normal sur <math>n</math> bits, mais il faut savoir que d'autres compteurs utilisent le BCD, d'autre le code Gray, etc. Nous allons voir dans ce qui suit deux types de compteurs : ceux qui comptent en code Gray, ceux qui comptent en représentation ''one-hot''.
===Les compteurs en code Gray===
Pour rappel, le code Gray permet de coder des nombres d'une manière un peu différente du binaire normal. Son avantage principal est que lorsqu'on incrémente ou décrémente un nombre, seul un bit change ! Pour comparer, en binaire normal, lorsqu'on passe incrémente un nombre, il peut y avoir plusieurs bits qui changent. La moyenne est d'environ deux bits par incrémentation. Les compteurs en code Gray ont beaucoup d'avantages, qui sont tous liés à cette propriété.
Le premier l'absence d'état transitoires douteux. Le problème est que les bits modifiés par une incrémentation ne le sont pas en même temps. Les bits de poids faibles sont modifiés avant les autres. Évidemment, à la fin du calcul, on obtient le résultat final, correct. Mais pendant le temps de calcul, le compteur peut se retrouver dans un état transitoire, où seuls les bits de poids faibles ont été modifiés. C'est un problème si le contenu de ce compteur est relié à des circuits rapides, qui peuvent voir cet état transitoire, mais ne le doivent pas sous peine de dysfonctionner. L'usage de compteurs en code Gray permet d'éviter ce problème : vu que seul un bit est modifié lors d'une incrémentation/décrémentation, les états transitoires n'existent tout simplement pas.
Un exemple typique, évoqué dans les chapitres précédents, est l'échange d'informations entre deux domaines d'horloge. Pour rappel, il arrive que deux portions d'un circuit imprimé aillent à des fréquences différences : on dit que le circuit à plusieurs domaines d'horloge. S'il faut échanger des informations entre ces deux domaines d'horloge, divers problèmes surviennent. Un domaine d'horloge sera plus rapide que l'autre, et pourra voir les états transitoires invisible de l'autre circuit. Et par voir, on veut dire qu'il les prendra pour des états valides, ce qui fera dysfonctionner le circuit. Pour éviter cela, diverses techniques de croisement de domaines d'horloge existent. Et les compteurs Gray en font partie : si un domaine d'horloge utilise la valeur d'un compteur de l'autre, mieux vaut que ce compteur soit un compteur Gray. Et cette situation est assez fréquente !
Un autre avantage mineurs est que la consommation d'énergie de ces compteurs est bien plus réduite qu'avec un compteur normal. Rappelons que pour fonctionner, les circuits électroniques consomment un peu d'électricité. Et la majeure partie de cette consommation sert à faire passer un bit de 0 à 1 ou de 1 à 0. Ce qui fait que quand un compteur est incrémenté ou décrémenté, cela consomme un peu d'énergie électrique.
La moyenne pour un compteur binaire normal est de 2 bits changés par incrémentation/décrémentation, contre un seul pour un compteur Gray, on devine que ces derniers consomment deux fois moins d'énergie par incrémentation. Et cet avantage a des effets en cascade sur les circuits qui suivent ce compteur. Si l'entrée de ces circuits ne change que d'un seul bit, alors leur état changera moins que si c'était deux bits. Les circuits qui suivent vont donc moins consommer.
Un autre avantage en matière de consommation énergétique est lié auxs transitions d'état douteuses. Les circuits connectés au compteur vont voir ces transitions d'état douteuses et modifier leur état interne en réaction. Bien sur, l'état final correct sera atteint une fois que le compteur sera stabilisé, ce qui effacera ces états transitoires intermédiaires. Mais chaque état intermédiaire transitoire correspond à un changement d'état, donc à une consommation d'énergie. En supprimant ces états transitoires, on réduit fortement la consommation d'énergie du circuit. Cela vaut pour le compteur Gray lui-même, mais aussi sur tous les circuits qui ont ce compteur comme entrée !
===Les compteurs ''one-hot''===
Les '''compteurs ''one-hot''''' sont appelés ainsi, car ils permettent de compter dans une représentation des nombres appelée la représentation ''one-hot''. Pour rappel, dans une telle représentation, un seul bit est à 1 pendant que les autres sont à 0. Les entiers sont codés de la manière suivante : le nombre N est encodé en mettant le énième bit à 1, avec la condition que l'on commence à compteur à partir de zéro. Il est important de remarquer que dans cette représentation, le zéro est n'est PAS codé en mettant tous les bits à 0, la valeur 0000...0000 est une valeur interdite. À la place, le zéro est codé en mettant le bit de poids faible à 1. Pour N bits, on peut encoder seulement N valeurs, dont le zéro.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! ''One-hot''
|-
| 0 || 000 || 00000001
|-
| 1 || 001 || 00000010
|-
| 2 || 010 || 00000100
|-
| 3 || 011 || 00001000
|-
| 4 || 100 || 00010000
|-
| 5 || 101 || 00100000
|-
| 6 || 110 || 01000000
|-
| 7 || 111 || 10000000
|}
Un compteur en représentation ''one-hot'' contient un nombre codé de cette manière, qui est incrémenté ou décrémenté si besoin. Pour donner un exemple, la séquence d'un compteur en anneau de 4 bits est :
* 0001 (0) ;
* 0010 (1) ;
* 0100 (2) ;
* 1000 (3) .
Un compteur ''one-hot'' basique est composé d'un registre à décalage dont on boucle la sortie sur son entrée. En faisant cela, on garantit que le registre revient à zéro lors d'un débordement, zéro étant codé avec un 1 dans le bit de poids faible. Au passage, si vous ne mettez que des 0 dans un compteur en anneau, il restera bloqué pour toujours : décaler une suite de 0 donnera la même suite de 0. Initialiser un compteur ''one-hot'' demande donc quelques subtilités qu'on détaillera plus bas.
[[File:Compteur en anneau de 4 bits.jpg|centre|vignette|upright=2|Compteur en anneau de 4 bits]]
Un compteur ''one-hot'' de N bits peut prendre N valeurs différentes, qui ont toutes un seul bit à 1. Pour 5 bits, la séquence est illustrée ci-dessous.
[[File:Lauflicht 5 LED.gif|centre|vignette|Compteur en anneau de 5 bits.]]
Faire des comparaisons avec ce type de compteur est très simple : le compteur contient la valeur N si le énième bit est à 1. Pas besoin d'utiliser de circuit comparateur, juste de lire un bit. Par contre, ce compteur n'est pas très économe en bascules. Imaginons que l'on veut un compteur qui compte jusqu'à une valeur N arbitraire : un compteur binaire normal utilisera environ <math>\log_2{(N)}</math> bascules, alors qu'un compteur ''one-hot'' demande N bascules. Mais si N est assez petit, l'économie de bascules est assez faible, alors que l'économie de circuits comparateurs/incrémenteurs l'est beaucoup plus.
Il y a peu d'applications qui utilisent des compteurs en anneau. Ils étaient autrefois utilisés dans les tous premiers ordinateurs, notamment ceux qui géraient une représentation des nombres spécifique appelée la '''''Bi-quinary coded decimal'''''. De nos jours, de tels compteurs sont utilisés dans les séquenceurs de processeurs, mais aussi dans les séquenceurs de certains périphériques, ou dans les circuits séquentiels simples qui se résument à des machines à états. Ils sont alors utilisés car très rapides, parfaitement adaptés au stockage de petites valeur, et surtout : ils n'ont pas besoin de circuit comparateur pour connaitre la valeur stockée dedans. Nous n'allons pas rentrer dans le détail de leurs utilisations, car nous en reparlerons dans la suite du cours.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les registres et mémoires adressables
| prevText=Les registres et mémoires adressables
| next=Les registres à décalage à rétroaction linéaire
| nextText=Les registres à décalage à rétroaction linéaire
}}
</noinclude>
qfux7d30gsfi8hw1ea7r0mgpsgltkvt
768603
768601
2026-06-25T14:48:52Z
Mewtow
31375
/* Les compteurs modulo */
768603
wikitext
text/x-wiki
Les '''compteurs/décompteurs''' sont des circuits électroniques qui mémorisent un nombre et l'incrémentent à la demande. En clair, ce sont des registres améliorés afin de supporter l'incrémentation et la décrémentation. Pour donner un exemple d'utilisation, imaginez un circuit qui compte le nombre de voitures dans un parking dans la journée. Pour cela, vous allez prendre deux circuits qui détectent respectivement l'entrée ou la sortie d'une voiture, et un compteur. Le compteur est initialisé à 0 quand le parking est vide, puis est incrémenté à chaque entrée de voiture, décrémenté à chaque sortie. Les exemples de ce type sont suffisamment nombreux pour qu'on dédie un chapitre aux compteurs.
[[File:Binary counter.gif|vignette|Illustration du fonctionnement d'un compteur modulaire binaire de 4 bits, avec un pas de compteur de 1 (le contenu est augmenté de 1 à chaque mise à jour).]]
Un compteur mémorise un nombre qui est incrémenté ou décrémenté au besoin. Le nombre mémorisé sera appelé le '''décompte''' dans ce qui suit. Il est mémorisé dans un registre à l'intérieur du compteur. Au passage, le nombre de bits <math>n</math> du compteur est appelé la '''taille du compteur''', par analogie avec les registres. Il faut cependant faire la différence entre les ''compteurs'' d'un côté et les ''décompteurs'' de l'autre. Les compteurs incrémentent le décompte, les décompteurs le décrémentent, les compteurs-décompteurs peuvent faire les deux.
==L'interface d'un compteur/décompteur==
Les compteurs et décompteurs sont des circuits synchrones et ont donc une entrée d'horloge. Les compteurs les plus simples incrémentent leur contenu à chaque cycle d'horloge, mais les plus fréquents n'incrémentent le décompte que sur demande. Pour cela, ils disposent d'une entrée '''''Count Enable''''', similaire à l'entrée ''Enable'' des registres, séparée de l'entrée d'horloge. Le décompte est incrémenté/décrémenté seulement si l'entrée Enable est à 1, lors d'un front adéquat sur le signal d'horloge.
Les compteurs ont aussi une entrée '''''Reset''''' qui permet de les remettre à zéro. Il y a parfois une entrée qui permet d'initialiser le compteur à une valeur par défaut, non-nulle. Par exemple, on peut initialiser le décompte à la valeur 5, ou une autre. Pour cela, le compteur dispose de deux entrées : une entrée sur laquelle envoyer le décompte initial, une entrée pour autoriser la réinitialisation. Les entrées en question sont appelées '''''Preload Data''''' et '''''Preload Enable'''''. La seconde entrée est parfois distincte de l'entrée de réinitialisation, pour permettre de réinitialiser le compteur soit à zéro, soit à la valeur voulue.
Il peut être utile de prévenir quand un débordement d'entier a lieu, à savoir quand le compteur n'a pas assez de bits pour encoder le décompte. Le compteur est alors remis à zéro, dans la plupart des cas. Mais il faut prévenir que le compteur a débordé, ce qui est utile pour fabriquer des circuits diviseurs de fréquence et des ''timers''. Pour cela, on ajoute une '''sortie de débordement''' au compteur, qui est mise à 1 quand le compteur déborde.
Sur les compteurs/décompteurs, il y a une entrée '''''Count Direction''''' qui décide s'il faut compter ou décompter. Typiquement, elle est à 1 s'il faut compter et 0 s'il faut décompter.
[[File:Digital counter signals.jpg|centre|vignette|upright=2|Interface d'un compteur-décompteur.]]
Dans les schémas qui vont suivre, les entrées Enable ne sont pas représentées. Il est sous-entendu qu'il y a une entrée ''Enable'' pour tous les compteurs qui vont suivre. Il existe deux méthodes pour créer de tels compteurs : la première donne ce qu'on appelle des compteurs asynchrones, et l'autre des compteurs synchrones.
==L'intérieur d'un compteur==
A une exception bien précise qu'on abordera plus bas, les compteurs sont composés d'un registre, qui mémorise le décompte, couplé à un circuit '''incrémenteur'''. Nous avions déjà abordé l'incrémenteur dans un chapitre précédent, aussi je ne vais pas ré-expliquer comment il est conçu. Tout ce qu'il faut retenir est qu'il y en a plusieurs types, le plus simple étant celui à propagation de retenue.
[[File:Fonctionnement d'un compteur (décompteur), schématique.jpg|centre|vignette|upright=2|Fonctionnement d'un compteur (décompteur), schématique. Le pas du compteur précise de combien on incrémente le compteur par cycle d'horloge.]]
L'incrémenteur le plus simple, à propagation de retenue, est fabriqué en enchainant des ''demi-additionneurs'' les uns à la suite des autres. Pour rappel, un demi-additionneur additionne deux bits. Ici, il additionne un bit de l'opérande, la retenue des colonnes précédentes. Pour le bit de poids faible, la retenue est forcé à 1. Si on combine un incrémenteur à propagation de retenue avec un registre, on obtient ce compteur :
[[File:Compteur synchrone à incrémenteur.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules D.]]
Une simplification permet de faire disparaitre les portes XOR. Les portes XOR servent ici d'inverseur commandable, commandé par la retenue entrante. Elles inversent le contenu de la bascule quand la retenue entrante vaut 1, elles laissent la bascule inchangée si la retenue vaut 0. Or, nous avons déjà une bascule qui inverse son contenu sous certaines condition : la bascule T ! Il est donc possible de fusionner chaque bascule D avec la porte XOR associée, pour donner une bascule T. Le circuit final est celui-ci :
[[File:Compteur synchrone à bascules T.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules T.]]
Il est possible d'optimiser le circuit avec les optimisations vues dans le chapitre sur les incrémenteurs. La première est le ''carry skip'' qui fait l'incrémentation non pas bit par bit, mais par paquets de deux bits. La seconde est l'anticipation de retenue. Et ces deux optimisations se marient bien avec ce qui va suivre.
===Les compteurs en cascade===
Il est possible de concevoir des compteurs à partir de compteurs plus petits, mis en cascade. Par exemple, en créant un compteur 16 bits à partir de compteurs 4 bits, enchainés l'un à la suite de l'autre.
Les compteurs mis en cascade ont les mêmes entrées et sorties que les compteurs normaux, avec cependant un détail très important : ils ont une entrée pour la retenue entrante, et une sortie pour la retenue sortante. L'entrée pour la retenue entrante précise ce qui doit être additionné au bit de poids faible. Si elle vaut zéro, l'incrémenteur n'incrémente pas l'opérande. Si elle vaut 1, le compteur est incrémenté. L'entrée de retenue fait office d'entrée ''Count Enable'', qui active ou désactive l'incrémentation.
Le compteur a aussi une sortie de débordement, qui indique que le compteur déborde. Il se trouve que cette sortie fournit la retenue pour le compteur suivant. La retenue peut être calculée en utilisant des optimisations comme l'anticipation de retenue, ce qui veut dire qu'elle est calculée sans propager les retenues, directement à partir des bits de l'opérande. Concrètement, la retenue est calculée en faisant un ET logique entre tous les bits du décompte.
[[File:Cascadable binary up-counter.jpg|centre|vignette|upright=2|Cascadable binary up-counter]]
Les compteurs sont mis en cascade de la manière suivante : leur sortie de débordement est connectée sur l'entrée ''Enable'' du compteur suivant, celle qui déclenche l'incrémentation du compteur. La sortie de débordement est notée RCO dans les schéma qui suivent, nous verrons pourquoi dans le prochaine paragraphe.
[[File:Cascaded binary counters.jpg|centre|vignette|upright=2.5|Cascaded binary counters]]
===Les compteurs modulo===
La plupart des compteurs comptent de 0 à <math>2^n - 1</math>, avec <math>n</math> la taille du compteur. Mais d'autres compteurs ne comptent pas jusque-là. Par exemple, certains compteurs ne comptent que jusqu'à 10, 150, etc. Ils sont appelés des '''compteurs ''modulo'''''. Prenons un compteur modulo 6, par exemple : il compte de 0 à 5, et est remis immédiatement à zéro quand il atteint 6. Il compte donc comme suit : 0, 1, 2, 3, 4, 5, 0, 1, 2, ...
Les compteurs ''modulo'' sont construits à partir d'un compteur normal, couplé à un circuit comparateur qui remet à zéro le registre quand il atteint la valeur maximale. Par exemple, on peut imaginer un compteur modulo 6 est construit à partir d'un compteur 4 bits qui compte de 0 à 15 (donc un compteur modulo 16), mais qui est remis à zéro quand il atteint 6. Le circuit comparateur vérifie si la valeur maximale 6 est atteinte et met à 1 l'entrée ''Reset'' si c'est le cas. Le comparateur est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Td4bfig4.png|centre|vignette|upright=1.5|Compteur modulo N.]]
Pour une minorité de compteurs ''modulo'', la valeur maximale est configurable. Pour cela, le compteur est associé à un ''registre de configuration'' qui mémorise la valeur maximale souhaitée. A chaque cycle d'horloge, la valeur dans le compteur est comparée au registre de configuration. Si elles sont identiques, le compteur est remis à zéro. Le compteur est associé au registre de configuration et à un comparateur qui vérifie que les deux sont égaux. Pour le moment, nous ne savons pas faire de circuits comparateurs, ce qui fait qu'on ne peut pas expliquer ce circuit plus en détail.
[[File:4 Bit Counter Prog 1.svg|centre|vignette|upright=2|Compteur 4 bits à valeur maximale programmable.]]
Les compteurs ont tous une sortie de débordement, qui indique quand le compteur déborde. Pour les compteurs modulo, la sortie n'est autre que la sortie du comparateur. Pour les compteurs non-modulo, la sortie de débordement est une sortie du circuit combinatoire qui incrémente le compteur.
===Les mal-nommés ''compteurs asynchrones''===
Les '''compteurs asynchrones''' n'utilisent pas de circuit incrémenteur. A la place, ils sont fabriqués à partir d'un registre à décalage. Ils utilisent moins de portes logiques que les compteurs synchrones. Et ils ne sont pas forcément plus difficiles à comprendre. Précisons cependant qu'ils sont très mal nommés, à savoir que ce sont bien des circuits synchrones.
Pour comprendre comment fonctionne un compteur asynchrone, il faut regarder la séquence des premiers entiers :
* 000 ;
* 001 ;
* 010 ;
* 011 ;
* 100 ;
* 101 ;
* 110 ;
* 111.
Il faut remarquer que le bit de poids faible s'inverse à chaque cycle d'horloge. Pour les colonnes suivantes, le bit s'inverse quand le bit de la colonne précédente passe de 1 à 0, lors d'un front descendant sur la colonne précédente. Maintenant que l'on sait cela, on peut créer un compteur avec des bascules T (elles inversent leur contenu à chaque cycle d'horloge). La première colonne inverse son contenu à chaque cycle, elle correspond donc à une bascule T reliée directement à l'horloge. Les autres colonnes utilisent des bascules T activées sur front descendant.
: Attention, la bascule la plus à gauche stocke le bit de poids faible, pas celui de poids fort. Cela sera pareil dans tous les schémas qui suivront.
[[File:AsyncCounter Alternativ 2.svg|centre|vignette|upright=2|Compteur asynchrone de 4 bits.]]
Le compteur précédent ne peut pas être réinitialisé, ce qui pose des problèmes pour implémenter des compteurs modulo. Pour cela, il faut que les bascules du compteur aient une entrée de réinitialisation ''Reset'', qui les force à se remettre à zéro. Il suffit alors de connecter ensemble les entrées ''Reset'' des bascules à l'entrée ''Reset'' du compteur.
[[File:AsyncCounter mod 8 with RST .svg|centre|vignette|upright=2|Compteur réinitialisable.]]
Implémenter un compteur modulo demande d'ajouter un comparateur qui détecte quand la valeur maximale est atteinte, afin de commander l'entrée de réinitialisation. Un tel circuit est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Modulo-5-Zähler.svg|centre|vignette|upright=2|Compteur modulo 10.]]
Il est aussi possible d'utiliser des bascules D à la place des bascules T simplifiées. En effet, une bascule T simplifiée est identique à une bascule D dont on boucle la sortie /Q sur l'entrée de données. Cette implémentation permet d'ailleurs de réinitialiser le compteur à une valeur non-nulle. Pour cela, l'entrée de chaque bascule D est précédée d'un multiplexeur, qui choisit entre le bit calculé par le compteur et celui présenté sur l'entrée de ré-initialisation. Quand l'entrée ''Reset'' est activée, les multiplexeurs connectent les bascules aux bits sur l'entrée de ré-initialisation. Dans le cas contraire, le compteur fonctionne normalement, les multiplexeurs connectant l'entrée de chaque bascule à sa sortie.
[[File:Compteur asynchrone, avec initialisation.jpg|centre|vignette|upright=2|Compteur asynchrone, avec initialisation.]]
Pour finir, la sortie de débordement n'est autre que la sortie de la dernière bascule, celle qui contient le bit de poids fort.
==Les ''timers'' : compter des durées, compter des cycles d'horloge==
Les '''''timers''''', aussi appelés ''Programmable interval timer'', sont des circuits capables de compter des durées, exprimées en cycles d'horloge. Leur fonctionnement est assez simple : ils émettent un signal quand un certain nombre de cycles est écoulé, ce nombre de cycles étant configurable. On peut ainsi générer un signal qui surviendra après 50 cycles d'horloge, ou après 100 cycles d'horloge, etc. Le signal en question est disponible sur une sortie de 1 bit, et correspond tout simplement au fait que cette sortie est mise à 1, pendant un cycle d'horloge.
Les ''timers'' sont composés d'un compteur/décompteur cadencé par un signal d'horloge. Le compteur initialisé à 0, puis est incrémenté à chaque signal d'horloge, jusqu’à atteinte d'une valeur limite où il génère un signal. Pour un décompteur, c'est la même chose, sauf que le décompteur est initialisé à sa valeur limite et est décrémenté à chaque cycle, et envoie un signal quand il atteint 0. Les ''timers'' basés sur des décompteurs sont nettement plus simples que les autres, ce qui fait qu'ils sont plus utilisés Pour que les ''timers'' soient configurables, on doit pouvoir préciser combien de cycles il faut (dé-)compter avant d'émettre un signal. On peut ainsi préciser s'il faut émettre le signal après 32 cycles d'horloge, après les 50 cycles, tous les 129 cycles, etc. Le nombre de cycles en question est envoyé sur une entrée d’initialisation du compteur.
Les ''timers'' matériels peuvent compter de deux manières différentes, appelées '''mode une fois''' et '''mode périodique'''.
* En mode une fois, le ''timer'' s'arrête une fois qu'il a atteint la limite configurée. On doit le réinitialiser manuellement, par l'intermédiaire du logiciel, pour l'utiliser une nouvelle fois. Cela permet de compter une certaine durée, exprimée en nombre de cycles d'horloge.
* En mode périodique, le ''timer'' se réinitialise automatiquement avec la valeur de départ, ce qui fait qu'il reboucle à l'infini.
Un ordinateur est rempli de ''timers'' divers. Dans ce qui va suivre, nous allons voir les principaux ''timers'', qui sont actuellement intégrés dans les PC modernes. Ils se trouvent sur la carte mère ou dans le processeur, tout dépend du ''timer''.
===Le ''watchdog timer''===
Le '''''watchdog timer''''' est un ''timer'' spécifique dont le but est de redémarrer automatiquement l'ordinateur si jamais celui-ci ne répond plus ou plante. Beaucoup de PC s'en passent, mais ce ''timer'' est très fréquent dans les architectures embarquées. Le ''watchdog timer'' est un compteur/décompteur qui doit être réinitialisé régulièrement. S'il n'est pas réinitialisé, le ''watchdog timer'' déborde (revient à 0 ou atteint 0) et envoie un signal qui redémarre le système. Le système est conçu pour réinitialiser le ''watchdog timer'' régulièrement, ce qui signifie que le système n'est pas censé redémarrer. Si jamais le système dysfonctionne gravement, le système ne pourra pas réinitialiser le ''watchdog timer'' et le système est redémarré automatiquement ou mis en arrêt.
[[File:SimpleWatchdogTimer.gif|centre|vignette|upright=2|Le ''Watchdog Timer'' et l'ordinateur.]]
===Le ''Time Stamp Counter'' des processeurs x86===
Tous les processeurs des PC actuels sont des processeurs dits x86. Nous ne pouvons pas expliquer ce que cela signifie pour le moment, retenez juste ce terme. Sachez que tous les processeurs x86 contiennent un compteur de 64 bits, appelé le '''''Time Stamp Counter''''', qui mémorise le nombre de cycles d'horloge qu'a effectué le processeur depuis son démarrage. Les programmes peuvent accéder à ce registre assez facilement, ce qui est utile pour faire des mesures ou comparer les performances de deux applications. Il permet de compter combien de cycles d'horloge met un morceau de code à s’exécuter, combien de cycles prend une instruction à s’exécuter, etc. Les processeurs non-x86 ont un registre équivalent, que ce soit les processeurs ARM ou d'autres.
Malheureusement, ce compteur est tombé en désuétude pour tout un tas de raisons. La principale est que les processeurs actuels font varier leur fréquence suivant les besoins. Ils augmentent leur fréquence quand on leur demande de faire beaucoup de calculs, et se mettent en mode basse(fréquence pour économiser de l'énergie si on ne leur demande pas grand chose. Avec une fréquence variable, le ''Time Stamp Counter'' perd complétement en fiabilité. Intel a tenté de corriger ce défaut en incrémentant ce registre à une fréquence constante, différente de celle du processeur, ce qui est encore le cas sur les processeurs Intel actuels. Le comportement est un peu différent sur les processeurs AMD, qui cadencent ce ''timer'' à la fréquence du processeur mais utilisent des mécanismes de synchronisation assez complexes pour corriger l'effet de la fréquence variable.
===L'horloge temps réel===
L''''horloge temps réel''' est un ''timer'' qui génère une fréquence de 1024 Hz, soit près d'un Kilohertz. Dans ce qui suit, nous la noterons RTC, ce qui est l'acronyme du terme anglais ''Real Time Clock''. La RTC prend en entrée un signal d'horloge de 32KHz, généré par un oscillateur à Quartz, et fournit en sortie un signal de fréquence 32 fois plus faible, c'est à dire de 1 KHz. Pour cela, elle est réglée en mode répétitif et son décompteur interne est initialisé à 32. La RTC génère donc un signal toutes les millisecondes, qui est envoyé au processeur. On peut, en théorie, changer la fréquence de la RTC, mais c'est rarement une bonne idée.
En théorie, la RTC permet de compter des durées assez courtes, comme le ''ping'' (le temps de latence d'un réseau, pour simplifier), le temps de rafraichissement de l'écran, ou bien d'autres choses. Mais dans les faits, l'horloge temps réel sa fréquence n'aide pas : 1024 Hz est proche de 1000, mais pas assez pour faire des mesures à la milliseconde près, chose qui est nécessaire pour mesurer le ''ping'' ou d'autres choses utiles. A la place, l'ordinateur l'utilise pour que l'ordinateur soit toujours à l'heure. L'ordinateur sait quelle heure il est avec une précision de l'ordre de la seconde (vous pouvez regarder le bureau de Windows dans le coin inférieur droite de votre écran pour vous en convaincre).
===Le ''Programmable Interval Timer'' : l'Intel 8253===
L'Intel 8253 était un ''timer'' programmable autrefois soudé sur les cartes mères des premiers PC. Il fût suivi par l'Intel 8254, qui en était une légère amélioration. Il était cadencé par une horloge maitre, générée par un oscillateur à Quartz, dont la fréquence est de 32 768 Hertz, soit 2^15 cycles d'horloge par seconde. S'il n'est plus présent dans un boitier sur la carte mère, on trouve toujours un circuit semblable au 8253 à l'intérieur du ''chipset'' de la carte mère, voire à l'intérieur du processeur, pour des raisons de compatibilité. L'intérieur de l'Intel 8253 est illustré ci-dessous. Nous allons expliquer l'ensemble de ce schéma, rassurez-vous, mais les explications seront plus simples à comprendre si vous survolez ce schéma en premier lieu.
[[File:Intel 8253 block diagram.svg|centre|vignette|upright=2|Intel 8253, intérieur.]]
L'Intel 8253 contient trois compteurs de 16 bits, numérotés 0, 1 et 2. Pour chaque compteur, l'entrée CLOCK est celle de l'horloge de 32 MHz, l'entrée GATE active ou désactive le compteur, la sortie fournit le signal voulu et/ou la fréquence de sortie. Les trois compteurs étaient utilisés pour dériver plusieurs fréquences allant de 18,2 Hz à environ 500 KHz. Par exemple, il était utilisé par défaut pour le rafraichissement de la mémoire (D)RAM, mais il était souvent reprogrammé pour servir à générer des fréquences spécifiques par le BIOS ou la carte graphique.
[[File:Intel 8253 and 8254.svg|vignette|Intel 8253 and 8254]]
L'Intel 8253 lui-même possède plusieurs entrées et sorties. En premier lieu, on voit un port de 8 bits connecté aux trois compteurs, qui permet à l'Intel 8253 de communiquer avec le reste de l'ordinateur. La communication se fait dans les deux sens : soit de l'ordinateur vers les compteurs, soit des compteurs vers l'ordinateur. Dans le sens ordinateur -> compteurs, cela permet à l'ordinateur de programmer les compteurs, de les initialiser. Dans l'autre sens, cela permet de récupérer le contenu des compteurs, même si ce n'est pas très utilisé. Il y a aussi 5 entrées de configuration :
* Deux bits A0 et A1 pour sélectionner le compteur voulu avec son numéro.
* Un bit RD à mettre à 0 pour que l'ordinateur récupère le compteur sélectionné sur le port de 8 bits.
* Un bit WR à mettre à 0 pour que l'ordinateur modifie le compteur sélectionné, en envoyant le nombre pour l'initialisation sur le port de 8 bits.
* Un bit CS qui active ou désactive l'Intel 8253 et permet de l'allumer ou de l’éteindre.
L'Intel 8253 intégre un registre de 8 bits, le ''Control Word register'' qui mémorise la configuration de l'Intel 8253. Pour programmer les trois compteurs, il faut écrire un mot de 8 bits dans ce ''Control Word register''. Pour écrire dans le ''Control Word register'', il faut mettre le bit CS à 0 (on active l'Intel 8253), mettre le bit RDà 1 , le bit WR à 0 le bit WR (on indique qu'on fait une écriture), sélectionner le ''Control Word register'' en mettant les deux bits A0 et A1 à 1, puis envoyer la configuration du ''Control Word register'' sur le port de 8 bits.
===Le ''High Precision Event Timer'' (HPET)===
De nos jours, l'horloge temps réel et l'Intel 8253/8254 tendent à être remplacé par un autre ''timer'', le ''High Precision Event Timer'' (HPET). Il s'agit d'un compteur de 64 bits, dont la fréquence est d'au moins 10 MHz. Il s'agit bien d'un compteur et non d'un décompteur. Il gère nativement plusieurs valeurs limites à laquelle générer un signal, qui sont configurables. Pour cela, il est couplé à plusieurs comparateurs, chacun associé à un registre pour mémoriser la valeur limite. Il doit y avoir au moins trois comparateurs/registres, mais le nombre peut monter jusqu’à 256.
[[File:High Precision Event Timer.png|centre|vignette|upright=2|High Precision Event Timer]]
Il faut noter que les systèmes d'exploitation conçus avant le HPET ne peuvent pas l'utiliser, pour des raisons de compatibilité matérielle. C'est le cas de Windows XP avant le Service Pack 3. C'est la raison pour laquelle les cartes mères émulent RTC et PIT dans leurs circuits. D'ailleurs, pour économiser des circuits, les cartes mères modernes émulent le PIT et la RTC avec le HPET : le premier comparateur fournisse la fréquence de 1024 Hz de la RTC, 3 autres comparateurs remplacent l'Intel 8253.
Le HPET gère de nombreux modes de fonctionnement : ses comparateurs peuvent être configuré en mode une fois ou périodique, on peut lui demander d'émuler la RTC et le PIT, etc. Chaque comparateur doit pouvoir fonctionner en mode une fois, et au moins un comparateur doit pouvoir fonctionner en mode périodique. Aussi, il contient aussi 3 registres de configuration. Notons qu'il est aussi possible de lire ou écrire dans le compteur de 64 bits, mais ce n'est pas recommandé.
==Les compteurs utilisant des représentations binaires non-classiques==
Les compteurs classiques encodent leur décompte en binaire normal sur <math>n</math> bits, mais il faut savoir que d'autres compteurs utilisent le BCD, d'autre le code Gray, etc. Nous allons voir dans ce qui suit deux types de compteurs : ceux qui comptent en code Gray, ceux qui comptent en représentation ''one-hot''.
===Les compteurs en code Gray===
Pour rappel, le code Gray permet de coder des nombres d'une manière un peu différente du binaire normal. Son avantage principal est que lorsqu'on incrémente ou décrémente un nombre, seul un bit change ! Pour comparer, en binaire normal, lorsqu'on passe incrémente un nombre, il peut y avoir plusieurs bits qui changent. La moyenne est d'environ deux bits par incrémentation. Les compteurs en code Gray ont beaucoup d'avantages, qui sont tous liés à cette propriété.
Le premier l'absence d'état transitoires douteux. Le problème est que les bits modifiés par une incrémentation ne le sont pas en même temps. Les bits de poids faibles sont modifiés avant les autres. Évidemment, à la fin du calcul, on obtient le résultat final, correct. Mais pendant le temps de calcul, le compteur peut se retrouver dans un état transitoire, où seuls les bits de poids faibles ont été modifiés. C'est un problème si le contenu de ce compteur est relié à des circuits rapides, qui peuvent voir cet état transitoire, mais ne le doivent pas sous peine de dysfonctionner. L'usage de compteurs en code Gray permet d'éviter ce problème : vu que seul un bit est modifié lors d'une incrémentation/décrémentation, les états transitoires n'existent tout simplement pas.
Un exemple typique, évoqué dans les chapitres précédents, est l'échange d'informations entre deux domaines d'horloge. Pour rappel, il arrive que deux portions d'un circuit imprimé aillent à des fréquences différences : on dit que le circuit à plusieurs domaines d'horloge. S'il faut échanger des informations entre ces deux domaines d'horloge, divers problèmes surviennent. Un domaine d'horloge sera plus rapide que l'autre, et pourra voir les états transitoires invisible de l'autre circuit. Et par voir, on veut dire qu'il les prendra pour des états valides, ce qui fera dysfonctionner le circuit. Pour éviter cela, diverses techniques de croisement de domaines d'horloge existent. Et les compteurs Gray en font partie : si un domaine d'horloge utilise la valeur d'un compteur de l'autre, mieux vaut que ce compteur soit un compteur Gray. Et cette situation est assez fréquente !
Un autre avantage mineurs est que la consommation d'énergie de ces compteurs est bien plus réduite qu'avec un compteur normal. Rappelons que pour fonctionner, les circuits électroniques consomment un peu d'électricité. Et la majeure partie de cette consommation sert à faire passer un bit de 0 à 1 ou de 1 à 0. Ce qui fait que quand un compteur est incrémenté ou décrémenté, cela consomme un peu d'énergie électrique.
La moyenne pour un compteur binaire normal est de 2 bits changés par incrémentation/décrémentation, contre un seul pour un compteur Gray, on devine que ces derniers consomment deux fois moins d'énergie par incrémentation. Et cet avantage a des effets en cascade sur les circuits qui suivent ce compteur. Si l'entrée de ces circuits ne change que d'un seul bit, alors leur état changera moins que si c'était deux bits. Les circuits qui suivent vont donc moins consommer.
Un autre avantage en matière de consommation énergétique est lié auxs transitions d'état douteuses. Les circuits connectés au compteur vont voir ces transitions d'état douteuses et modifier leur état interne en réaction. Bien sur, l'état final correct sera atteint une fois que le compteur sera stabilisé, ce qui effacera ces états transitoires intermédiaires. Mais chaque état intermédiaire transitoire correspond à un changement d'état, donc à une consommation d'énergie. En supprimant ces états transitoires, on réduit fortement la consommation d'énergie du circuit. Cela vaut pour le compteur Gray lui-même, mais aussi sur tous les circuits qui ont ce compteur comme entrée !
===Les compteurs ''one-hot''===
Les '''compteurs ''one-hot''''' sont appelés ainsi, car ils permettent de compter dans une représentation des nombres appelée la représentation ''one-hot''. Pour rappel, dans une telle représentation, un seul bit est à 1 pendant que les autres sont à 0. Les entiers sont codés de la manière suivante : le nombre N est encodé en mettant le énième bit à 1, avec la condition que l'on commence à compteur à partir de zéro. Il est important de remarquer que dans cette représentation, le zéro est n'est PAS codé en mettant tous les bits à 0, la valeur 0000...0000 est une valeur interdite. À la place, le zéro est codé en mettant le bit de poids faible à 1. Pour N bits, on peut encoder seulement N valeurs, dont le zéro.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! ''One-hot''
|-
| 0 || 000 || 00000001
|-
| 1 || 001 || 00000010
|-
| 2 || 010 || 00000100
|-
| 3 || 011 || 00001000
|-
| 4 || 100 || 00010000
|-
| 5 || 101 || 00100000
|-
| 6 || 110 || 01000000
|-
| 7 || 111 || 10000000
|}
Un compteur en représentation ''one-hot'' contient un nombre codé de cette manière, qui est incrémenté ou décrémenté si besoin. Pour donner un exemple, la séquence d'un compteur en anneau de 4 bits est :
* 0001 (0) ;
* 0010 (1) ;
* 0100 (2) ;
* 1000 (3) .
Un compteur ''one-hot'' basique est composé d'un registre à décalage dont on boucle la sortie sur son entrée. En faisant cela, on garantit que le registre revient à zéro lors d'un débordement, zéro étant codé avec un 1 dans le bit de poids faible. Au passage, si vous ne mettez que des 0 dans un compteur en anneau, il restera bloqué pour toujours : décaler une suite de 0 donnera la même suite de 0. Initialiser un compteur ''one-hot'' demande donc quelques subtilités qu'on détaillera plus bas.
[[File:Compteur en anneau de 4 bits.jpg|centre|vignette|upright=2|Compteur en anneau de 4 bits]]
Un compteur ''one-hot'' de N bits peut prendre N valeurs différentes, qui ont toutes un seul bit à 1. Pour 5 bits, la séquence est illustrée ci-dessous.
[[File:Lauflicht 5 LED.gif|centre|vignette|Compteur en anneau de 5 bits.]]
Faire des comparaisons avec ce type de compteur est très simple : le compteur contient la valeur N si le énième bit est à 1. Pas besoin d'utiliser de circuit comparateur, juste de lire un bit. Par contre, ce compteur n'est pas très économe en bascules. Imaginons que l'on veut un compteur qui compte jusqu'à une valeur N arbitraire : un compteur binaire normal utilisera environ <math>\log_2{(N)}</math> bascules, alors qu'un compteur ''one-hot'' demande N bascules. Mais si N est assez petit, l'économie de bascules est assez faible, alors que l'économie de circuits comparateurs/incrémenteurs l'est beaucoup plus.
Il y a peu d'applications qui utilisent des compteurs en anneau. Ils étaient autrefois utilisés dans les tous premiers ordinateurs, notamment ceux qui géraient une représentation des nombres spécifique appelée la '''''Bi-quinary coded decimal'''''. De nos jours, de tels compteurs sont utilisés dans les séquenceurs de processeurs, mais aussi dans les séquenceurs de certains périphériques, ou dans les circuits séquentiels simples qui se résument à des machines à états. Ils sont alors utilisés car très rapides, parfaitement adaptés au stockage de petites valeur, et surtout : ils n'ont pas besoin de circuit comparateur pour connaitre la valeur stockée dedans. Nous n'allons pas rentrer dans le détail de leurs utilisations, car nous en reparlerons dans la suite du cours.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les registres et mémoires adressables
| prevText=Les registres et mémoires adressables
| next=Les registres à décalage à rétroaction linéaire
| nextText=Les registres à décalage à rétroaction linéaire
}}
</noinclude>
gb566mwx9648ilshs0jcvuwfsnyo5mh
768612
768603
2026-06-25T15:19:52Z
Mewtow
31375
/* Les mal-nommés compteurs asynchrones */ Déplacement autre chapitre
768612
wikitext
text/x-wiki
Les '''compteurs/décompteurs''' sont des circuits électroniques qui mémorisent un nombre et l'incrémentent à la demande. En clair, ce sont des registres améliorés afin de supporter l'incrémentation et la décrémentation. Pour donner un exemple d'utilisation, imaginez un circuit qui compte le nombre de voitures dans un parking dans la journée. Pour cela, vous allez prendre deux circuits qui détectent respectivement l'entrée ou la sortie d'une voiture, et un compteur. Le compteur est initialisé à 0 quand le parking est vide, puis est incrémenté à chaque entrée de voiture, décrémenté à chaque sortie. Les exemples de ce type sont suffisamment nombreux pour qu'on dédie un chapitre aux compteurs.
[[File:Binary counter.gif|vignette|Illustration du fonctionnement d'un compteur modulaire binaire de 4 bits, avec un pas de compteur de 1 (le contenu est augmenté de 1 à chaque mise à jour).]]
Un compteur mémorise un nombre qui est incrémenté ou décrémenté au besoin. Le nombre mémorisé sera appelé le '''décompte''' dans ce qui suit. Il est mémorisé dans un registre à l'intérieur du compteur. Au passage, le nombre de bits <math>n</math> du compteur est appelé la '''taille du compteur''', par analogie avec les registres. Il faut cependant faire la différence entre les ''compteurs'' d'un côté et les ''décompteurs'' de l'autre. Les compteurs incrémentent le décompte, les décompteurs le décrémentent, les compteurs-décompteurs peuvent faire les deux.
==L'interface d'un compteur/décompteur==
Les compteurs et décompteurs sont des circuits synchrones et ont donc une entrée d'horloge. Les compteurs les plus simples incrémentent leur contenu à chaque cycle d'horloge, mais les plus fréquents n'incrémentent le décompte que sur demande. Pour cela, ils disposent d'une entrée '''''Count Enable''''', similaire à l'entrée ''Enable'' des registres, séparée de l'entrée d'horloge. Le décompte est incrémenté/décrémenté seulement si l'entrée Enable est à 1, lors d'un front adéquat sur le signal d'horloge.
Les compteurs ont aussi une entrée '''''Reset''''' qui permet de les remettre à zéro. Il y a parfois une entrée qui permet d'initialiser le compteur à une valeur par défaut, non-nulle. Par exemple, on peut initialiser le décompte à la valeur 5, ou une autre. Pour cela, le compteur dispose de deux entrées : une entrée sur laquelle envoyer le décompte initial, une entrée pour autoriser la réinitialisation. Les entrées en question sont appelées '''''Preload Data''''' et '''''Preload Enable'''''. La seconde entrée est parfois distincte de l'entrée de réinitialisation, pour permettre de réinitialiser le compteur soit à zéro, soit à la valeur voulue.
Il peut être utile de prévenir quand un débordement d'entier a lieu, à savoir quand le compteur n'a pas assez de bits pour encoder le décompte. Le compteur est alors remis à zéro, dans la plupart des cas. Mais il faut prévenir que le compteur a débordé, ce qui est utile pour fabriquer des circuits diviseurs de fréquence et des ''timers''. Pour cela, on ajoute une '''sortie de débordement''' au compteur, qui est mise à 1 quand le compteur déborde.
Sur les compteurs/décompteurs, il y a une entrée '''''Count Direction''''' qui décide s'il faut compter ou décompter. Typiquement, elle est à 1 s'il faut compter et 0 s'il faut décompter.
[[File:Digital counter signals.jpg|centre|vignette|upright=2|Interface d'un compteur-décompteur.]]
Dans les schémas qui vont suivre, les entrées Enable ne sont pas représentées. Il est sous-entendu qu'il y a une entrée ''Enable'' pour tous les compteurs qui vont suivre. Il existe deux méthodes pour créer de tels compteurs : la première donne ce qu'on appelle des compteurs asynchrones, et l'autre des compteurs synchrones.
==L'intérieur d'un compteur==
A une exception bien précise qu'on abordera plus bas, les compteurs sont composés d'un registre, qui mémorise le décompte, couplé à un circuit '''incrémenteur'''. Nous avions déjà abordé l'incrémenteur dans un chapitre précédent, aussi je ne vais pas ré-expliquer comment il est conçu. Tout ce qu'il faut retenir est qu'il y en a plusieurs types, le plus simple étant celui à propagation de retenue.
[[File:Fonctionnement d'un compteur (décompteur), schématique.jpg|centre|vignette|upright=2|Fonctionnement d'un compteur (décompteur), schématique. Le pas du compteur précise de combien on incrémente le compteur par cycle d'horloge.]]
L'incrémenteur le plus simple, à propagation de retenue, est fabriqué en enchainant des ''demi-additionneurs'' les uns à la suite des autres. Pour rappel, un demi-additionneur additionne deux bits. Ici, il additionne un bit de l'opérande, la retenue des colonnes précédentes. Pour le bit de poids faible, la retenue est forcé à 1. Si on combine un incrémenteur à propagation de retenue avec un registre, on obtient ce compteur :
[[File:Compteur synchrone à incrémenteur.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules D.]]
Une simplification permet de faire disparaitre les portes XOR. Les portes XOR servent ici d'inverseur commandable, commandé par la retenue entrante. Elles inversent le contenu de la bascule quand la retenue entrante vaut 1, elles laissent la bascule inchangée si la retenue vaut 0. Or, nous avons déjà une bascule qui inverse son contenu sous certaines condition : la bascule T ! Il est donc possible de fusionner chaque bascule D avec la porte XOR associée, pour donner une bascule T. Le circuit final est celui-ci :
[[File:Compteur synchrone à bascules T.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules T.]]
Il est possible d'optimiser le circuit avec les optimisations vues dans le chapitre sur les incrémenteurs. La première est le ''carry skip'' qui fait l'incrémentation non pas bit par bit, mais par paquets de deux bits. La seconde est l'anticipation de retenue. Et ces deux optimisations se marient bien avec ce qui va suivre.
===Les compteurs en cascade===
Il est possible de concevoir des compteurs à partir de compteurs plus petits, mis en cascade. Par exemple, en créant un compteur 16 bits à partir de compteurs 4 bits, enchainés l'un à la suite de l'autre.
Les compteurs mis en cascade ont les mêmes entrées et sorties que les compteurs normaux, avec cependant un détail très important : ils ont une entrée pour la retenue entrante, et une sortie pour la retenue sortante. L'entrée pour la retenue entrante précise ce qui doit être additionné au bit de poids faible. Si elle vaut zéro, l'incrémenteur n'incrémente pas l'opérande. Si elle vaut 1, le compteur est incrémenté. L'entrée de retenue fait office d'entrée ''Count Enable'', qui active ou désactive l'incrémentation.
Le compteur a aussi une sortie de débordement, qui indique que le compteur déborde. Il se trouve que cette sortie fournit la retenue pour le compteur suivant. La retenue peut être calculée en utilisant des optimisations comme l'anticipation de retenue, ce qui veut dire qu'elle est calculée sans propager les retenues, directement à partir des bits de l'opérande. Concrètement, la retenue est calculée en faisant un ET logique entre tous les bits du décompte.
[[File:Cascadable binary up-counter.jpg|centre|vignette|upright=2|Cascadable binary up-counter]]
Les compteurs sont mis en cascade de la manière suivante : leur sortie de débordement est connectée sur l'entrée ''Enable'' du compteur suivant, celle qui déclenche l'incrémentation du compteur. La sortie de débordement est notée RCO dans les schéma qui suivent, nous verrons pourquoi dans le prochaine paragraphe.
[[File:Cascaded binary counters.jpg|centre|vignette|upright=2.5|Cascaded binary counters]]
===Les compteurs modulo===
La plupart des compteurs comptent de 0 à <math>2^n - 1</math>, avec <math>n</math> la taille du compteur. Mais d'autres compteurs ne comptent pas jusque-là. Par exemple, certains compteurs ne comptent que jusqu'à 10, 150, etc. Ils sont appelés des '''compteurs ''modulo'''''. Prenons un compteur modulo 6, par exemple : il compte de 0 à 5, et est remis immédiatement à zéro quand il atteint 6. Il compte donc comme suit : 0, 1, 2, 3, 4, 5, 0, 1, 2, ...
Les compteurs ''modulo'' sont construits à partir d'un compteur normal, couplé à un circuit comparateur qui remet à zéro le registre quand il atteint la valeur maximale. Par exemple, on peut imaginer un compteur modulo 6 est construit à partir d'un compteur 4 bits qui compte de 0 à 15 (donc un compteur modulo 16), mais qui est remis à zéro quand il atteint 6. Le circuit comparateur vérifie si la valeur maximale 6 est atteinte et met à 1 l'entrée ''Reset'' si c'est le cas. Le comparateur est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Td4bfig4.png|centre|vignette|upright=1.5|Compteur modulo N.]]
Pour une minorité de compteurs ''modulo'', la valeur maximale est configurable. Pour cela, le compteur est associé à un ''registre de configuration'' qui mémorise la valeur maximale souhaitée. A chaque cycle d'horloge, la valeur dans le compteur est comparée au registre de configuration. Si elles sont identiques, le compteur est remis à zéro. Le compteur est associé au registre de configuration et à un comparateur qui vérifie que les deux sont égaux. Pour le moment, nous ne savons pas faire de circuits comparateurs, ce qui fait qu'on ne peut pas expliquer ce circuit plus en détail.
[[File:4 Bit Counter Prog 1.svg|centre|vignette|upright=2|Compteur 4 bits à valeur maximale programmable.]]
Les compteurs ont tous une sortie de débordement, qui indique quand le compteur déborde. Pour les compteurs modulo, la sortie n'est autre que la sortie du comparateur. Pour les compteurs non-modulo, la sortie de débordement est une sortie du circuit combinatoire qui incrémente le compteur.
==Les ''timers'' : compter des durées, compter des cycles d'horloge==
Les '''''timers''''', aussi appelés ''Programmable interval timer'', sont des circuits capables de compter des durées, exprimées en cycles d'horloge. Leur fonctionnement est assez simple : ils émettent un signal quand un certain nombre de cycles est écoulé, ce nombre de cycles étant configurable. On peut ainsi générer un signal qui surviendra après 50 cycles d'horloge, ou après 100 cycles d'horloge, etc. Le signal en question est disponible sur une sortie de 1 bit, et correspond tout simplement au fait que cette sortie est mise à 1, pendant un cycle d'horloge.
Les ''timers'' sont composés d'un compteur/décompteur cadencé par un signal d'horloge. Le compteur initialisé à 0, puis est incrémenté à chaque signal d'horloge, jusqu’à atteinte d'une valeur limite où il génère un signal. Pour un décompteur, c'est la même chose, sauf que le décompteur est initialisé à sa valeur limite et est décrémenté à chaque cycle, et envoie un signal quand il atteint 0. Les ''timers'' basés sur des décompteurs sont nettement plus simples que les autres, ce qui fait qu'ils sont plus utilisés Pour que les ''timers'' soient configurables, on doit pouvoir préciser combien de cycles il faut (dé-)compter avant d'émettre un signal. On peut ainsi préciser s'il faut émettre le signal après 32 cycles d'horloge, après les 50 cycles, tous les 129 cycles, etc. Le nombre de cycles en question est envoyé sur une entrée d’initialisation du compteur.
Les ''timers'' matériels peuvent compter de deux manières différentes, appelées '''mode une fois''' et '''mode périodique'''.
* En mode une fois, le ''timer'' s'arrête une fois qu'il a atteint la limite configurée. On doit le réinitialiser manuellement, par l'intermédiaire du logiciel, pour l'utiliser une nouvelle fois. Cela permet de compter une certaine durée, exprimée en nombre de cycles d'horloge.
* En mode périodique, le ''timer'' se réinitialise automatiquement avec la valeur de départ, ce qui fait qu'il reboucle à l'infini.
Un ordinateur est rempli de ''timers'' divers. Dans ce qui va suivre, nous allons voir les principaux ''timers'', qui sont actuellement intégrés dans les PC modernes. Ils se trouvent sur la carte mère ou dans le processeur, tout dépend du ''timer''.
===Le ''watchdog timer''===
Le '''''watchdog timer''''' est un ''timer'' spécifique dont le but est de redémarrer automatiquement l'ordinateur si jamais celui-ci ne répond plus ou plante. Beaucoup de PC s'en passent, mais ce ''timer'' est très fréquent dans les architectures embarquées. Le ''watchdog timer'' est un compteur/décompteur qui doit être réinitialisé régulièrement. S'il n'est pas réinitialisé, le ''watchdog timer'' déborde (revient à 0 ou atteint 0) et envoie un signal qui redémarre le système. Le système est conçu pour réinitialiser le ''watchdog timer'' régulièrement, ce qui signifie que le système n'est pas censé redémarrer. Si jamais le système dysfonctionne gravement, le système ne pourra pas réinitialiser le ''watchdog timer'' et le système est redémarré automatiquement ou mis en arrêt.
[[File:SimpleWatchdogTimer.gif|centre|vignette|upright=2|Le ''Watchdog Timer'' et l'ordinateur.]]
===Le ''Time Stamp Counter'' des processeurs x86===
Tous les processeurs des PC actuels sont des processeurs dits x86. Nous ne pouvons pas expliquer ce que cela signifie pour le moment, retenez juste ce terme. Sachez que tous les processeurs x86 contiennent un compteur de 64 bits, appelé le '''''Time Stamp Counter''''', qui mémorise le nombre de cycles d'horloge qu'a effectué le processeur depuis son démarrage. Les programmes peuvent accéder à ce registre assez facilement, ce qui est utile pour faire des mesures ou comparer les performances de deux applications. Il permet de compter combien de cycles d'horloge met un morceau de code à s’exécuter, combien de cycles prend une instruction à s’exécuter, etc. Les processeurs non-x86 ont un registre équivalent, que ce soit les processeurs ARM ou d'autres.
Malheureusement, ce compteur est tombé en désuétude pour tout un tas de raisons. La principale est que les processeurs actuels font varier leur fréquence suivant les besoins. Ils augmentent leur fréquence quand on leur demande de faire beaucoup de calculs, et se mettent en mode basse(fréquence pour économiser de l'énergie si on ne leur demande pas grand chose. Avec une fréquence variable, le ''Time Stamp Counter'' perd complétement en fiabilité. Intel a tenté de corriger ce défaut en incrémentant ce registre à une fréquence constante, différente de celle du processeur, ce qui est encore le cas sur les processeurs Intel actuels. Le comportement est un peu différent sur les processeurs AMD, qui cadencent ce ''timer'' à la fréquence du processeur mais utilisent des mécanismes de synchronisation assez complexes pour corriger l'effet de la fréquence variable.
===L'horloge temps réel===
L''''horloge temps réel''' est un ''timer'' qui génère une fréquence de 1024 Hz, soit près d'un Kilohertz. Dans ce qui suit, nous la noterons RTC, ce qui est l'acronyme du terme anglais ''Real Time Clock''. La RTC prend en entrée un signal d'horloge de 32KHz, généré par un oscillateur à Quartz, et fournit en sortie un signal de fréquence 32 fois plus faible, c'est à dire de 1 KHz. Pour cela, elle est réglée en mode répétitif et son décompteur interne est initialisé à 32. La RTC génère donc un signal toutes les millisecondes, qui est envoyé au processeur. On peut, en théorie, changer la fréquence de la RTC, mais c'est rarement une bonne idée.
En théorie, la RTC permet de compter des durées assez courtes, comme le ''ping'' (le temps de latence d'un réseau, pour simplifier), le temps de rafraichissement de l'écran, ou bien d'autres choses. Mais dans les faits, l'horloge temps réel sa fréquence n'aide pas : 1024 Hz est proche de 1000, mais pas assez pour faire des mesures à la milliseconde près, chose qui est nécessaire pour mesurer le ''ping'' ou d'autres choses utiles. A la place, l'ordinateur l'utilise pour que l'ordinateur soit toujours à l'heure. L'ordinateur sait quelle heure il est avec une précision de l'ordre de la seconde (vous pouvez regarder le bureau de Windows dans le coin inférieur droite de votre écran pour vous en convaincre).
===Le ''Programmable Interval Timer'' : l'Intel 8253===
L'Intel 8253 était un ''timer'' programmable autrefois soudé sur les cartes mères des premiers PC. Il fût suivi par l'Intel 8254, qui en était une légère amélioration. Il était cadencé par une horloge maitre, générée par un oscillateur à Quartz, dont la fréquence est de 32 768 Hertz, soit 2^15 cycles d'horloge par seconde. S'il n'est plus présent dans un boitier sur la carte mère, on trouve toujours un circuit semblable au 8253 à l'intérieur du ''chipset'' de la carte mère, voire à l'intérieur du processeur, pour des raisons de compatibilité. L'intérieur de l'Intel 8253 est illustré ci-dessous. Nous allons expliquer l'ensemble de ce schéma, rassurez-vous, mais les explications seront plus simples à comprendre si vous survolez ce schéma en premier lieu.
[[File:Intel 8253 block diagram.svg|centre|vignette|upright=2|Intel 8253, intérieur.]]
L'Intel 8253 contient trois compteurs de 16 bits, numérotés 0, 1 et 2. Pour chaque compteur, l'entrée CLOCK est celle de l'horloge de 32 MHz, l'entrée GATE active ou désactive le compteur, la sortie fournit le signal voulu et/ou la fréquence de sortie. Les trois compteurs étaient utilisés pour dériver plusieurs fréquences allant de 18,2 Hz à environ 500 KHz. Par exemple, il était utilisé par défaut pour le rafraichissement de la mémoire (D)RAM, mais il était souvent reprogrammé pour servir à générer des fréquences spécifiques par le BIOS ou la carte graphique.
[[File:Intel 8253 and 8254.svg|vignette|Intel 8253 and 8254]]
L'Intel 8253 lui-même possède plusieurs entrées et sorties. En premier lieu, on voit un port de 8 bits connecté aux trois compteurs, qui permet à l'Intel 8253 de communiquer avec le reste de l'ordinateur. La communication se fait dans les deux sens : soit de l'ordinateur vers les compteurs, soit des compteurs vers l'ordinateur. Dans le sens ordinateur -> compteurs, cela permet à l'ordinateur de programmer les compteurs, de les initialiser. Dans l'autre sens, cela permet de récupérer le contenu des compteurs, même si ce n'est pas très utilisé. Il y a aussi 5 entrées de configuration :
* Deux bits A0 et A1 pour sélectionner le compteur voulu avec son numéro.
* Un bit RD à mettre à 0 pour que l'ordinateur récupère le compteur sélectionné sur le port de 8 bits.
* Un bit WR à mettre à 0 pour que l'ordinateur modifie le compteur sélectionné, en envoyant le nombre pour l'initialisation sur le port de 8 bits.
* Un bit CS qui active ou désactive l'Intel 8253 et permet de l'allumer ou de l’éteindre.
L'Intel 8253 intégre un registre de 8 bits, le ''Control Word register'' qui mémorise la configuration de l'Intel 8253. Pour programmer les trois compteurs, il faut écrire un mot de 8 bits dans ce ''Control Word register''. Pour écrire dans le ''Control Word register'', il faut mettre le bit CS à 0 (on active l'Intel 8253), mettre le bit RDà 1 , le bit WR à 0 le bit WR (on indique qu'on fait une écriture), sélectionner le ''Control Word register'' en mettant les deux bits A0 et A1 à 1, puis envoyer la configuration du ''Control Word register'' sur le port de 8 bits.
===Le ''High Precision Event Timer'' (HPET)===
De nos jours, l'horloge temps réel et l'Intel 8253/8254 tendent à être remplacé par un autre ''timer'', le ''High Precision Event Timer'' (HPET). Il s'agit d'un compteur de 64 bits, dont la fréquence est d'au moins 10 MHz. Il s'agit bien d'un compteur et non d'un décompteur. Il gère nativement plusieurs valeurs limites à laquelle générer un signal, qui sont configurables. Pour cela, il est couplé à plusieurs comparateurs, chacun associé à un registre pour mémoriser la valeur limite. Il doit y avoir au moins trois comparateurs/registres, mais le nombre peut monter jusqu’à 256.
[[File:High Precision Event Timer.png|centre|vignette|upright=2|High Precision Event Timer]]
Il faut noter que les systèmes d'exploitation conçus avant le HPET ne peuvent pas l'utiliser, pour des raisons de compatibilité matérielle. C'est le cas de Windows XP avant le Service Pack 3. C'est la raison pour laquelle les cartes mères émulent RTC et PIT dans leurs circuits. D'ailleurs, pour économiser des circuits, les cartes mères modernes émulent le PIT et la RTC avec le HPET : le premier comparateur fournisse la fréquence de 1024 Hz de la RTC, 3 autres comparateurs remplacent l'Intel 8253.
Le HPET gère de nombreux modes de fonctionnement : ses comparateurs peuvent être configuré en mode une fois ou périodique, on peut lui demander d'émuler la RTC et le PIT, etc. Chaque comparateur doit pouvoir fonctionner en mode une fois, et au moins un comparateur doit pouvoir fonctionner en mode périodique. Aussi, il contient aussi 3 registres de configuration. Notons qu'il est aussi possible de lire ou écrire dans le compteur de 64 bits, mais ce n'est pas recommandé.
==Les compteurs utilisant des représentations binaires non-classiques==
Les compteurs classiques encodent leur décompte en binaire normal sur <math>n</math> bits, mais il faut savoir que d'autres compteurs utilisent le BCD, d'autre le code Gray, etc. Nous allons voir dans ce qui suit deux types de compteurs : ceux qui comptent en code Gray, ceux qui comptent en représentation ''one-hot''.
===Les compteurs en code Gray===
Pour rappel, le code Gray permet de coder des nombres d'une manière un peu différente du binaire normal. Son avantage principal est que lorsqu'on incrémente ou décrémente un nombre, seul un bit change ! Pour comparer, en binaire normal, lorsqu'on passe incrémente un nombre, il peut y avoir plusieurs bits qui changent. La moyenne est d'environ deux bits par incrémentation. Les compteurs en code Gray ont beaucoup d'avantages, qui sont tous liés à cette propriété.
Le premier l'absence d'état transitoires douteux. Le problème est que les bits modifiés par une incrémentation ne le sont pas en même temps. Les bits de poids faibles sont modifiés avant les autres. Évidemment, à la fin du calcul, on obtient le résultat final, correct. Mais pendant le temps de calcul, le compteur peut se retrouver dans un état transitoire, où seuls les bits de poids faibles ont été modifiés. C'est un problème si le contenu de ce compteur est relié à des circuits rapides, qui peuvent voir cet état transitoire, mais ne le doivent pas sous peine de dysfonctionner. L'usage de compteurs en code Gray permet d'éviter ce problème : vu que seul un bit est modifié lors d'une incrémentation/décrémentation, les états transitoires n'existent tout simplement pas.
Un exemple typique, évoqué dans les chapitres précédents, est l'échange d'informations entre deux domaines d'horloge. Pour rappel, il arrive que deux portions d'un circuit imprimé aillent à des fréquences différences : on dit que le circuit à plusieurs domaines d'horloge. S'il faut échanger des informations entre ces deux domaines d'horloge, divers problèmes surviennent. Un domaine d'horloge sera plus rapide que l'autre, et pourra voir les états transitoires invisible de l'autre circuit. Et par voir, on veut dire qu'il les prendra pour des états valides, ce qui fera dysfonctionner le circuit. Pour éviter cela, diverses techniques de croisement de domaines d'horloge existent. Et les compteurs Gray en font partie : si un domaine d'horloge utilise la valeur d'un compteur de l'autre, mieux vaut que ce compteur soit un compteur Gray. Et cette situation est assez fréquente !
Un autre avantage mineurs est que la consommation d'énergie de ces compteurs est bien plus réduite qu'avec un compteur normal. Rappelons que pour fonctionner, les circuits électroniques consomment un peu d'électricité. Et la majeure partie de cette consommation sert à faire passer un bit de 0 à 1 ou de 1 à 0. Ce qui fait que quand un compteur est incrémenté ou décrémenté, cela consomme un peu d'énergie électrique.
La moyenne pour un compteur binaire normal est de 2 bits changés par incrémentation/décrémentation, contre un seul pour un compteur Gray, on devine que ces derniers consomment deux fois moins d'énergie par incrémentation. Et cet avantage a des effets en cascade sur les circuits qui suivent ce compteur. Si l'entrée de ces circuits ne change que d'un seul bit, alors leur état changera moins que si c'était deux bits. Les circuits qui suivent vont donc moins consommer.
Un autre avantage en matière de consommation énergétique est lié auxs transitions d'état douteuses. Les circuits connectés au compteur vont voir ces transitions d'état douteuses et modifier leur état interne en réaction. Bien sur, l'état final correct sera atteint une fois que le compteur sera stabilisé, ce qui effacera ces états transitoires intermédiaires. Mais chaque état intermédiaire transitoire correspond à un changement d'état, donc à une consommation d'énergie. En supprimant ces états transitoires, on réduit fortement la consommation d'énergie du circuit. Cela vaut pour le compteur Gray lui-même, mais aussi sur tous les circuits qui ont ce compteur comme entrée !
===Les compteurs ''one-hot''===
Les '''compteurs ''one-hot''''' sont appelés ainsi, car ils permettent de compter dans une représentation des nombres appelée la représentation ''one-hot''. Pour rappel, dans une telle représentation, un seul bit est à 1 pendant que les autres sont à 0. Les entiers sont codés de la manière suivante : le nombre N est encodé en mettant le énième bit à 1, avec la condition que l'on commence à compteur à partir de zéro. Il est important de remarquer que dans cette représentation, le zéro est n'est PAS codé en mettant tous les bits à 0, la valeur 0000...0000 est une valeur interdite. À la place, le zéro est codé en mettant le bit de poids faible à 1. Pour N bits, on peut encoder seulement N valeurs, dont le zéro.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! ''One-hot''
|-
| 0 || 000 || 00000001
|-
| 1 || 001 || 00000010
|-
| 2 || 010 || 00000100
|-
| 3 || 011 || 00001000
|-
| 4 || 100 || 00010000
|-
| 5 || 101 || 00100000
|-
| 6 || 110 || 01000000
|-
| 7 || 111 || 10000000
|}
Un compteur en représentation ''one-hot'' contient un nombre codé de cette manière, qui est incrémenté ou décrémenté si besoin. Pour donner un exemple, la séquence d'un compteur en anneau de 4 bits est :
* 0001 (0) ;
* 0010 (1) ;
* 0100 (2) ;
* 1000 (3) .
Un compteur ''one-hot'' basique est composé d'un registre à décalage dont on boucle la sortie sur son entrée. En faisant cela, on garantit que le registre revient à zéro lors d'un débordement, zéro étant codé avec un 1 dans le bit de poids faible. Au passage, si vous ne mettez que des 0 dans un compteur en anneau, il restera bloqué pour toujours : décaler une suite de 0 donnera la même suite de 0. Initialiser un compteur ''one-hot'' demande donc quelques subtilités qu'on détaillera plus bas.
[[File:Compteur en anneau de 4 bits.jpg|centre|vignette|upright=2|Compteur en anneau de 4 bits]]
Un compteur ''one-hot'' de N bits peut prendre N valeurs différentes, qui ont toutes un seul bit à 1. Pour 5 bits, la séquence est illustrée ci-dessous.
[[File:Lauflicht 5 LED.gif|centre|vignette|Compteur en anneau de 5 bits.]]
Faire des comparaisons avec ce type de compteur est très simple : le compteur contient la valeur N si le énième bit est à 1. Pas besoin d'utiliser de circuit comparateur, juste de lire un bit. Par contre, ce compteur n'est pas très économe en bascules. Imaginons que l'on veut un compteur qui compte jusqu'à une valeur N arbitraire : un compteur binaire normal utilisera environ <math>\log_2{(N)}</math> bascules, alors qu'un compteur ''one-hot'' demande N bascules. Mais si N est assez petit, l'économie de bascules est assez faible, alors que l'économie de circuits comparateurs/incrémenteurs l'est beaucoup plus.
Il y a peu d'applications qui utilisent des compteurs en anneau. Ils étaient autrefois utilisés dans les tous premiers ordinateurs, notamment ceux qui géraient une représentation des nombres spécifique appelée la '''''Bi-quinary coded decimal'''''. De nos jours, de tels compteurs sont utilisés dans les séquenceurs de processeurs, mais aussi dans les séquenceurs de certains périphériques, ou dans les circuits séquentiels simples qui se résument à des machines à états. Ils sont alors utilisés car très rapides, parfaitement adaptés au stockage de petites valeur, et surtout : ils n'ont pas besoin de circuit comparateur pour connaitre la valeur stockée dedans. Nous n'allons pas rentrer dans le détail de leurs utilisations, car nous en reparlerons dans la suite du cours.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les registres et mémoires adressables
| prevText=Les registres et mémoires adressables
| next=Les registres à décalage à rétroaction linéaire
| nextText=Les registres à décalage à rétroaction linéaire
}}
</noinclude>
fcqonwqsbjhp2mrw646eqe3an6fdhwa
768615
768612
2026-06-25T15:20:28Z
Mewtow
31375
/* Les compteurs utilisant des représentations binaires non-classiques */
768615
wikitext
text/x-wiki
Les '''compteurs/décompteurs''' sont des circuits électroniques qui mémorisent un nombre et l'incrémentent à la demande. En clair, ce sont des registres améliorés afin de supporter l'incrémentation et la décrémentation. Pour donner un exemple d'utilisation, imaginez un circuit qui compte le nombre de voitures dans un parking dans la journée. Pour cela, vous allez prendre deux circuits qui détectent respectivement l'entrée ou la sortie d'une voiture, et un compteur. Le compteur est initialisé à 0 quand le parking est vide, puis est incrémenté à chaque entrée de voiture, décrémenté à chaque sortie. Les exemples de ce type sont suffisamment nombreux pour qu'on dédie un chapitre aux compteurs.
[[File:Binary counter.gif|vignette|Illustration du fonctionnement d'un compteur modulaire binaire de 4 bits, avec un pas de compteur de 1 (le contenu est augmenté de 1 à chaque mise à jour).]]
Un compteur mémorise un nombre qui est incrémenté ou décrémenté au besoin. Le nombre mémorisé sera appelé le '''décompte''' dans ce qui suit. Il est mémorisé dans un registre à l'intérieur du compteur. Au passage, le nombre de bits <math>n</math> du compteur est appelé la '''taille du compteur''', par analogie avec les registres. Il faut cependant faire la différence entre les ''compteurs'' d'un côté et les ''décompteurs'' de l'autre. Les compteurs incrémentent le décompte, les décompteurs le décrémentent, les compteurs-décompteurs peuvent faire les deux.
==L'interface d'un compteur/décompteur==
Les compteurs et décompteurs sont des circuits synchrones et ont donc une entrée d'horloge. Les compteurs les plus simples incrémentent leur contenu à chaque cycle d'horloge, mais les plus fréquents n'incrémentent le décompte que sur demande. Pour cela, ils disposent d'une entrée '''''Count Enable''''', similaire à l'entrée ''Enable'' des registres, séparée de l'entrée d'horloge. Le décompte est incrémenté/décrémenté seulement si l'entrée Enable est à 1, lors d'un front adéquat sur le signal d'horloge.
Les compteurs ont aussi une entrée '''''Reset''''' qui permet de les remettre à zéro. Il y a parfois une entrée qui permet d'initialiser le compteur à une valeur par défaut, non-nulle. Par exemple, on peut initialiser le décompte à la valeur 5, ou une autre. Pour cela, le compteur dispose de deux entrées : une entrée sur laquelle envoyer le décompte initial, une entrée pour autoriser la réinitialisation. Les entrées en question sont appelées '''''Preload Data''''' et '''''Preload Enable'''''. La seconde entrée est parfois distincte de l'entrée de réinitialisation, pour permettre de réinitialiser le compteur soit à zéro, soit à la valeur voulue.
Il peut être utile de prévenir quand un débordement d'entier a lieu, à savoir quand le compteur n'a pas assez de bits pour encoder le décompte. Le compteur est alors remis à zéro, dans la plupart des cas. Mais il faut prévenir que le compteur a débordé, ce qui est utile pour fabriquer des circuits diviseurs de fréquence et des ''timers''. Pour cela, on ajoute une '''sortie de débordement''' au compteur, qui est mise à 1 quand le compteur déborde.
Sur les compteurs/décompteurs, il y a une entrée '''''Count Direction''''' qui décide s'il faut compter ou décompter. Typiquement, elle est à 1 s'il faut compter et 0 s'il faut décompter.
[[File:Digital counter signals.jpg|centre|vignette|upright=2|Interface d'un compteur-décompteur.]]
Dans les schémas qui vont suivre, les entrées Enable ne sont pas représentées. Il est sous-entendu qu'il y a une entrée ''Enable'' pour tous les compteurs qui vont suivre. Il existe deux méthodes pour créer de tels compteurs : la première donne ce qu'on appelle des compteurs asynchrones, et l'autre des compteurs synchrones.
==L'intérieur d'un compteur==
A une exception bien précise qu'on abordera plus bas, les compteurs sont composés d'un registre, qui mémorise le décompte, couplé à un circuit '''incrémenteur'''. Nous avions déjà abordé l'incrémenteur dans un chapitre précédent, aussi je ne vais pas ré-expliquer comment il est conçu. Tout ce qu'il faut retenir est qu'il y en a plusieurs types, le plus simple étant celui à propagation de retenue.
[[File:Fonctionnement d'un compteur (décompteur), schématique.jpg|centre|vignette|upright=2|Fonctionnement d'un compteur (décompteur), schématique. Le pas du compteur précise de combien on incrémente le compteur par cycle d'horloge.]]
L'incrémenteur le plus simple, à propagation de retenue, est fabriqué en enchainant des ''demi-additionneurs'' les uns à la suite des autres. Pour rappel, un demi-additionneur additionne deux bits. Ici, il additionne un bit de l'opérande, la retenue des colonnes précédentes. Pour le bit de poids faible, la retenue est forcé à 1. Si on combine un incrémenteur à propagation de retenue avec un registre, on obtient ce compteur :
[[File:Compteur synchrone à incrémenteur.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules D.]]
Une simplification permet de faire disparaitre les portes XOR. Les portes XOR servent ici d'inverseur commandable, commandé par la retenue entrante. Elles inversent le contenu de la bascule quand la retenue entrante vaut 1, elles laissent la bascule inchangée si la retenue vaut 0. Or, nous avons déjà une bascule qui inverse son contenu sous certaines condition : la bascule T ! Il est donc possible de fusionner chaque bascule D avec la porte XOR associée, pour donner une bascule T. Le circuit final est celui-ci :
[[File:Compteur synchrone à bascules T.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules T.]]
Il est possible d'optimiser le circuit avec les optimisations vues dans le chapitre sur les incrémenteurs. La première est le ''carry skip'' qui fait l'incrémentation non pas bit par bit, mais par paquets de deux bits. La seconde est l'anticipation de retenue. Et ces deux optimisations se marient bien avec ce qui va suivre.
===Les compteurs en cascade===
Il est possible de concevoir des compteurs à partir de compteurs plus petits, mis en cascade. Par exemple, en créant un compteur 16 bits à partir de compteurs 4 bits, enchainés l'un à la suite de l'autre.
Les compteurs mis en cascade ont les mêmes entrées et sorties que les compteurs normaux, avec cependant un détail très important : ils ont une entrée pour la retenue entrante, et une sortie pour la retenue sortante. L'entrée pour la retenue entrante précise ce qui doit être additionné au bit de poids faible. Si elle vaut zéro, l'incrémenteur n'incrémente pas l'opérande. Si elle vaut 1, le compteur est incrémenté. L'entrée de retenue fait office d'entrée ''Count Enable'', qui active ou désactive l'incrémentation.
Le compteur a aussi une sortie de débordement, qui indique que le compteur déborde. Il se trouve que cette sortie fournit la retenue pour le compteur suivant. La retenue peut être calculée en utilisant des optimisations comme l'anticipation de retenue, ce qui veut dire qu'elle est calculée sans propager les retenues, directement à partir des bits de l'opérande. Concrètement, la retenue est calculée en faisant un ET logique entre tous les bits du décompte.
[[File:Cascadable binary up-counter.jpg|centre|vignette|upright=2|Cascadable binary up-counter]]
Les compteurs sont mis en cascade de la manière suivante : leur sortie de débordement est connectée sur l'entrée ''Enable'' du compteur suivant, celle qui déclenche l'incrémentation du compteur. La sortie de débordement est notée RCO dans les schéma qui suivent, nous verrons pourquoi dans le prochaine paragraphe.
[[File:Cascaded binary counters.jpg|centre|vignette|upright=2.5|Cascaded binary counters]]
===Les compteurs modulo===
La plupart des compteurs comptent de 0 à <math>2^n - 1</math>, avec <math>n</math> la taille du compteur. Mais d'autres compteurs ne comptent pas jusque-là. Par exemple, certains compteurs ne comptent que jusqu'à 10, 150, etc. Ils sont appelés des '''compteurs ''modulo'''''. Prenons un compteur modulo 6, par exemple : il compte de 0 à 5, et est remis immédiatement à zéro quand il atteint 6. Il compte donc comme suit : 0, 1, 2, 3, 4, 5, 0, 1, 2, ...
Les compteurs ''modulo'' sont construits à partir d'un compteur normal, couplé à un circuit comparateur qui remet à zéro le registre quand il atteint la valeur maximale. Par exemple, on peut imaginer un compteur modulo 6 est construit à partir d'un compteur 4 bits qui compte de 0 à 15 (donc un compteur modulo 16), mais qui est remis à zéro quand il atteint 6. Le circuit comparateur vérifie si la valeur maximale 6 est atteinte et met à 1 l'entrée ''Reset'' si c'est le cas. Le comparateur est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Td4bfig4.png|centre|vignette|upright=1.5|Compteur modulo N.]]
Pour une minorité de compteurs ''modulo'', la valeur maximale est configurable. Pour cela, le compteur est associé à un ''registre de configuration'' qui mémorise la valeur maximale souhaitée. A chaque cycle d'horloge, la valeur dans le compteur est comparée au registre de configuration. Si elles sont identiques, le compteur est remis à zéro. Le compteur est associé au registre de configuration et à un comparateur qui vérifie que les deux sont égaux. Pour le moment, nous ne savons pas faire de circuits comparateurs, ce qui fait qu'on ne peut pas expliquer ce circuit plus en détail.
[[File:4 Bit Counter Prog 1.svg|centre|vignette|upright=2|Compteur 4 bits à valeur maximale programmable.]]
Les compteurs ont tous une sortie de débordement, qui indique quand le compteur déborde. Pour les compteurs modulo, la sortie n'est autre que la sortie du comparateur. Pour les compteurs non-modulo, la sortie de débordement est une sortie du circuit combinatoire qui incrémente le compteur.
==Les ''timers'' : compter des durées, compter des cycles d'horloge==
Les '''''timers''''', aussi appelés ''Programmable interval timer'', sont des circuits capables de compter des durées, exprimées en cycles d'horloge. Leur fonctionnement est assez simple : ils émettent un signal quand un certain nombre de cycles est écoulé, ce nombre de cycles étant configurable. On peut ainsi générer un signal qui surviendra après 50 cycles d'horloge, ou après 100 cycles d'horloge, etc. Le signal en question est disponible sur une sortie de 1 bit, et correspond tout simplement au fait que cette sortie est mise à 1, pendant un cycle d'horloge.
Les ''timers'' sont composés d'un compteur/décompteur cadencé par un signal d'horloge. Le compteur initialisé à 0, puis est incrémenté à chaque signal d'horloge, jusqu’à atteinte d'une valeur limite où il génère un signal. Pour un décompteur, c'est la même chose, sauf que le décompteur est initialisé à sa valeur limite et est décrémenté à chaque cycle, et envoie un signal quand il atteint 0. Les ''timers'' basés sur des décompteurs sont nettement plus simples que les autres, ce qui fait qu'ils sont plus utilisés Pour que les ''timers'' soient configurables, on doit pouvoir préciser combien de cycles il faut (dé-)compter avant d'émettre un signal. On peut ainsi préciser s'il faut émettre le signal après 32 cycles d'horloge, après les 50 cycles, tous les 129 cycles, etc. Le nombre de cycles en question est envoyé sur une entrée d’initialisation du compteur.
Les ''timers'' matériels peuvent compter de deux manières différentes, appelées '''mode une fois''' et '''mode périodique'''.
* En mode une fois, le ''timer'' s'arrête une fois qu'il a atteint la limite configurée. On doit le réinitialiser manuellement, par l'intermédiaire du logiciel, pour l'utiliser une nouvelle fois. Cela permet de compter une certaine durée, exprimée en nombre de cycles d'horloge.
* En mode périodique, le ''timer'' se réinitialise automatiquement avec la valeur de départ, ce qui fait qu'il reboucle à l'infini.
Un ordinateur est rempli de ''timers'' divers. Dans ce qui va suivre, nous allons voir les principaux ''timers'', qui sont actuellement intégrés dans les PC modernes. Ils se trouvent sur la carte mère ou dans le processeur, tout dépend du ''timer''.
===Le ''watchdog timer''===
Le '''''watchdog timer''''' est un ''timer'' spécifique dont le but est de redémarrer automatiquement l'ordinateur si jamais celui-ci ne répond plus ou plante. Beaucoup de PC s'en passent, mais ce ''timer'' est très fréquent dans les architectures embarquées. Le ''watchdog timer'' est un compteur/décompteur qui doit être réinitialisé régulièrement. S'il n'est pas réinitialisé, le ''watchdog timer'' déborde (revient à 0 ou atteint 0) et envoie un signal qui redémarre le système. Le système est conçu pour réinitialiser le ''watchdog timer'' régulièrement, ce qui signifie que le système n'est pas censé redémarrer. Si jamais le système dysfonctionne gravement, le système ne pourra pas réinitialiser le ''watchdog timer'' et le système est redémarré automatiquement ou mis en arrêt.
[[File:SimpleWatchdogTimer.gif|centre|vignette|upright=2|Le ''Watchdog Timer'' et l'ordinateur.]]
===Le ''Time Stamp Counter'' des processeurs x86===
Tous les processeurs des PC actuels sont des processeurs dits x86. Nous ne pouvons pas expliquer ce que cela signifie pour le moment, retenez juste ce terme. Sachez que tous les processeurs x86 contiennent un compteur de 64 bits, appelé le '''''Time Stamp Counter''''', qui mémorise le nombre de cycles d'horloge qu'a effectué le processeur depuis son démarrage. Les programmes peuvent accéder à ce registre assez facilement, ce qui est utile pour faire des mesures ou comparer les performances de deux applications. Il permet de compter combien de cycles d'horloge met un morceau de code à s’exécuter, combien de cycles prend une instruction à s’exécuter, etc. Les processeurs non-x86 ont un registre équivalent, que ce soit les processeurs ARM ou d'autres.
Malheureusement, ce compteur est tombé en désuétude pour tout un tas de raisons. La principale est que les processeurs actuels font varier leur fréquence suivant les besoins. Ils augmentent leur fréquence quand on leur demande de faire beaucoup de calculs, et se mettent en mode basse(fréquence pour économiser de l'énergie si on ne leur demande pas grand chose. Avec une fréquence variable, le ''Time Stamp Counter'' perd complétement en fiabilité. Intel a tenté de corriger ce défaut en incrémentant ce registre à une fréquence constante, différente de celle du processeur, ce qui est encore le cas sur les processeurs Intel actuels. Le comportement est un peu différent sur les processeurs AMD, qui cadencent ce ''timer'' à la fréquence du processeur mais utilisent des mécanismes de synchronisation assez complexes pour corriger l'effet de la fréquence variable.
===L'horloge temps réel===
L''''horloge temps réel''' est un ''timer'' qui génère une fréquence de 1024 Hz, soit près d'un Kilohertz. Dans ce qui suit, nous la noterons RTC, ce qui est l'acronyme du terme anglais ''Real Time Clock''. La RTC prend en entrée un signal d'horloge de 32KHz, généré par un oscillateur à Quartz, et fournit en sortie un signal de fréquence 32 fois plus faible, c'est à dire de 1 KHz. Pour cela, elle est réglée en mode répétitif et son décompteur interne est initialisé à 32. La RTC génère donc un signal toutes les millisecondes, qui est envoyé au processeur. On peut, en théorie, changer la fréquence de la RTC, mais c'est rarement une bonne idée.
En théorie, la RTC permet de compter des durées assez courtes, comme le ''ping'' (le temps de latence d'un réseau, pour simplifier), le temps de rafraichissement de l'écran, ou bien d'autres choses. Mais dans les faits, l'horloge temps réel sa fréquence n'aide pas : 1024 Hz est proche de 1000, mais pas assez pour faire des mesures à la milliseconde près, chose qui est nécessaire pour mesurer le ''ping'' ou d'autres choses utiles. A la place, l'ordinateur l'utilise pour que l'ordinateur soit toujours à l'heure. L'ordinateur sait quelle heure il est avec une précision de l'ordre de la seconde (vous pouvez regarder le bureau de Windows dans le coin inférieur droite de votre écran pour vous en convaincre).
===Le ''Programmable Interval Timer'' : l'Intel 8253===
L'Intel 8253 était un ''timer'' programmable autrefois soudé sur les cartes mères des premiers PC. Il fût suivi par l'Intel 8254, qui en était une légère amélioration. Il était cadencé par une horloge maitre, générée par un oscillateur à Quartz, dont la fréquence est de 32 768 Hertz, soit 2^15 cycles d'horloge par seconde. S'il n'est plus présent dans un boitier sur la carte mère, on trouve toujours un circuit semblable au 8253 à l'intérieur du ''chipset'' de la carte mère, voire à l'intérieur du processeur, pour des raisons de compatibilité. L'intérieur de l'Intel 8253 est illustré ci-dessous. Nous allons expliquer l'ensemble de ce schéma, rassurez-vous, mais les explications seront plus simples à comprendre si vous survolez ce schéma en premier lieu.
[[File:Intel 8253 block diagram.svg|centre|vignette|upright=2|Intel 8253, intérieur.]]
L'Intel 8253 contient trois compteurs de 16 bits, numérotés 0, 1 et 2. Pour chaque compteur, l'entrée CLOCK est celle de l'horloge de 32 MHz, l'entrée GATE active ou désactive le compteur, la sortie fournit le signal voulu et/ou la fréquence de sortie. Les trois compteurs étaient utilisés pour dériver plusieurs fréquences allant de 18,2 Hz à environ 500 KHz. Par exemple, il était utilisé par défaut pour le rafraichissement de la mémoire (D)RAM, mais il était souvent reprogrammé pour servir à générer des fréquences spécifiques par le BIOS ou la carte graphique.
[[File:Intel 8253 and 8254.svg|vignette|Intel 8253 and 8254]]
L'Intel 8253 lui-même possède plusieurs entrées et sorties. En premier lieu, on voit un port de 8 bits connecté aux trois compteurs, qui permet à l'Intel 8253 de communiquer avec le reste de l'ordinateur. La communication se fait dans les deux sens : soit de l'ordinateur vers les compteurs, soit des compteurs vers l'ordinateur. Dans le sens ordinateur -> compteurs, cela permet à l'ordinateur de programmer les compteurs, de les initialiser. Dans l'autre sens, cela permet de récupérer le contenu des compteurs, même si ce n'est pas très utilisé. Il y a aussi 5 entrées de configuration :
* Deux bits A0 et A1 pour sélectionner le compteur voulu avec son numéro.
* Un bit RD à mettre à 0 pour que l'ordinateur récupère le compteur sélectionné sur le port de 8 bits.
* Un bit WR à mettre à 0 pour que l'ordinateur modifie le compteur sélectionné, en envoyant le nombre pour l'initialisation sur le port de 8 bits.
* Un bit CS qui active ou désactive l'Intel 8253 et permet de l'allumer ou de l’éteindre.
L'Intel 8253 intégre un registre de 8 bits, le ''Control Word register'' qui mémorise la configuration de l'Intel 8253. Pour programmer les trois compteurs, il faut écrire un mot de 8 bits dans ce ''Control Word register''. Pour écrire dans le ''Control Word register'', il faut mettre le bit CS à 0 (on active l'Intel 8253), mettre le bit RDà 1 , le bit WR à 0 le bit WR (on indique qu'on fait une écriture), sélectionner le ''Control Word register'' en mettant les deux bits A0 et A1 à 1, puis envoyer la configuration du ''Control Word register'' sur le port de 8 bits.
===Le ''High Precision Event Timer'' (HPET)===
De nos jours, l'horloge temps réel et l'Intel 8253/8254 tendent à être remplacé par un autre ''timer'', le ''High Precision Event Timer'' (HPET). Il s'agit d'un compteur de 64 bits, dont la fréquence est d'au moins 10 MHz. Il s'agit bien d'un compteur et non d'un décompteur. Il gère nativement plusieurs valeurs limites à laquelle générer un signal, qui sont configurables. Pour cela, il est couplé à plusieurs comparateurs, chacun associé à un registre pour mémoriser la valeur limite. Il doit y avoir au moins trois comparateurs/registres, mais le nombre peut monter jusqu’à 256.
[[File:High Precision Event Timer.png|centre|vignette|upright=2|High Precision Event Timer]]
Il faut noter que les systèmes d'exploitation conçus avant le HPET ne peuvent pas l'utiliser, pour des raisons de compatibilité matérielle. C'est le cas de Windows XP avant le Service Pack 3. C'est la raison pour laquelle les cartes mères émulent RTC et PIT dans leurs circuits. D'ailleurs, pour économiser des circuits, les cartes mères modernes émulent le PIT et la RTC avec le HPET : le premier comparateur fournisse la fréquence de 1024 Hz de la RTC, 3 autres comparateurs remplacent l'Intel 8253.
Le HPET gère de nombreux modes de fonctionnement : ses comparateurs peuvent être configuré en mode une fois ou périodique, on peut lui demander d'émuler la RTC et le PIT, etc. Chaque comparateur doit pouvoir fonctionner en mode une fois, et au moins un comparateur doit pouvoir fonctionner en mode périodique. Aussi, il contient aussi 3 registres de configuration. Notons qu'il est aussi possible de lire ou écrire dans le compteur de 64 bits, mais ce n'est pas recommandé.
==Les compteurs en code Gray==
Les compteurs classiques encodent leur décompte en binaire normal sur <math>n</math> bits, mais il faut savoir que d'autres compteurs utilisent le BCD, d'autre le code Gray, etc. Nous allons voir dans ce qui suit ceux qui comptent en code Gray.
Pour rappel, le code Gray permet de coder des nombres d'une manière un peu différente du binaire normal. Son avantage principal est que lorsqu'on incrémente ou décrémente un nombre, seul un bit change ! Pour comparer, en binaire normal, lorsqu'on passe incrémente un nombre, il peut y avoir plusieurs bits qui changent. La moyenne est d'environ deux bits par incrémentation. Les compteurs en code Gray ont beaucoup d'avantages, qui sont tous liés à cette propriété.
Le premier l'absence d'état transitoires douteux. Le problème est que les bits modifiés par une incrémentation ne le sont pas en même temps. Les bits de poids faibles sont modifiés avant les autres. Évidemment, à la fin du calcul, on obtient le résultat final, correct. Mais pendant le temps de calcul, le compteur peut se retrouver dans un état transitoire, où seuls les bits de poids faibles ont été modifiés. C'est un problème si le contenu de ce compteur est relié à des circuits rapides, qui peuvent voir cet état transitoire, mais ne le doivent pas sous peine de dysfonctionner. L'usage de compteurs en code Gray permet d'éviter ce problème : vu que seul un bit est modifié lors d'une incrémentation/décrémentation, les états transitoires n'existent tout simplement pas.
Un exemple typique, évoqué dans les chapitres précédents, est l'échange d'informations entre deux domaines d'horloge. Pour rappel, il arrive que deux portions d'un circuit imprimé aillent à des fréquences différences : on dit que le circuit à plusieurs domaines d'horloge. S'il faut échanger des informations entre ces deux domaines d'horloge, divers problèmes surviennent. Un domaine d'horloge sera plus rapide que l'autre, et pourra voir les états transitoires invisible de l'autre circuit. Et par voir, on veut dire qu'il les prendra pour des états valides, ce qui fera dysfonctionner le circuit. Pour éviter cela, diverses techniques de croisement de domaines d'horloge existent. Et les compteurs Gray en font partie : si un domaine d'horloge utilise la valeur d'un compteur de l'autre, mieux vaut que ce compteur soit un compteur Gray. Et cette situation est assez fréquente !
Un autre avantage mineurs est que la consommation d'énergie de ces compteurs est bien plus réduite qu'avec un compteur normal. Rappelons que pour fonctionner, les circuits électroniques consomment un peu d'électricité. Et la majeure partie de cette consommation sert à faire passer un bit de 0 à 1 ou de 1 à 0. Ce qui fait que quand un compteur est incrémenté ou décrémenté, cela consomme un peu d'énergie électrique.
La moyenne pour un compteur binaire normal est de 2 bits changés par incrémentation/décrémentation, contre un seul pour un compteur Gray, on devine que ces derniers consomment deux fois moins d'énergie par incrémentation. Et cet avantage a des effets en cascade sur les circuits qui suivent ce compteur. Si l'entrée de ces circuits ne change que d'un seul bit, alors leur état changera moins que si c'était deux bits. Les circuits qui suivent vont donc moins consommer.
Un autre avantage en matière de consommation énergétique est lié auxs transitions d'état douteuses. Les circuits connectés au compteur vont voir ces transitions d'état douteuses et modifier leur état interne en réaction. Bien sur, l'état final correct sera atteint une fois que le compteur sera stabilisé, ce qui effacera ces états transitoires intermédiaires. Mais chaque état intermédiaire transitoire correspond à un changement d'état, donc à une consommation d'énergie. En supprimant ces états transitoires, on réduit fortement la consommation d'énergie du circuit. Cela vaut pour le compteur Gray lui-même, mais aussi sur tous les circuits qui ont ce compteur comme entrée !
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les registres et mémoires adressables
| prevText=Les registres et mémoires adressables
| next=Les registres à décalage à rétroaction linéaire
| nextText=Les registres à décalage à rétroaction linéaire
}}
</noinclude>
ggjt25tfuab2cdiklcodn5mvk1hntgs
768623
768615
2026-06-25T15:22:31Z
Mewtow
31375
/* Les compteurs en code Gray */
768623
wikitext
text/x-wiki
Les '''compteurs/décompteurs''' sont des circuits électroniques qui mémorisent un nombre et l'incrémentent à la demande. En clair, ce sont des registres améliorés afin de supporter l'incrémentation et la décrémentation. Pour donner un exemple d'utilisation, imaginez un circuit qui compte le nombre de voitures dans un parking dans la journée. Pour cela, vous allez prendre deux circuits qui détectent respectivement l'entrée ou la sortie d'une voiture, et un compteur. Le compteur est initialisé à 0 quand le parking est vide, puis est incrémenté à chaque entrée de voiture, décrémenté à chaque sortie. Les exemples de ce type sont suffisamment nombreux pour qu'on dédie un chapitre aux compteurs.
[[File:Binary counter.gif|vignette|Illustration du fonctionnement d'un compteur modulaire binaire de 4 bits, avec un pas de compteur de 1 (le contenu est augmenté de 1 à chaque mise à jour).]]
Un compteur mémorise un nombre qui est incrémenté ou décrémenté au besoin. Le nombre mémorisé sera appelé le '''décompte''' dans ce qui suit. Il est mémorisé dans un registre à l'intérieur du compteur. Au passage, le nombre de bits <math>n</math> du compteur est appelé la '''taille du compteur''', par analogie avec les registres. Il faut cependant faire la différence entre les ''compteurs'' d'un côté et les ''décompteurs'' de l'autre. Les compteurs incrémentent le décompte, les décompteurs le décrémentent, les compteurs-décompteurs peuvent faire les deux.
==L'interface d'un compteur/décompteur==
Les compteurs et décompteurs sont des circuits synchrones et ont donc une entrée d'horloge. Les compteurs les plus simples incrémentent leur contenu à chaque cycle d'horloge, mais les plus fréquents n'incrémentent le décompte que sur demande. Pour cela, ils disposent d'une entrée '''''Count Enable''''', similaire à l'entrée ''Enable'' des registres, séparée de l'entrée d'horloge. Le décompte est incrémenté/décrémenté seulement si l'entrée Enable est à 1, lors d'un front adéquat sur le signal d'horloge.
Les compteurs ont aussi une entrée '''''Reset''''' qui permet de les remettre à zéro. Il y a parfois une entrée qui permet d'initialiser le compteur à une valeur par défaut, non-nulle. Par exemple, on peut initialiser le décompte à la valeur 5, ou une autre. Pour cela, le compteur dispose de deux entrées : une entrée sur laquelle envoyer le décompte initial, une entrée pour autoriser la réinitialisation. Les entrées en question sont appelées '''''Preload Data''''' et '''''Preload Enable'''''. La seconde entrée est parfois distincte de l'entrée de réinitialisation, pour permettre de réinitialiser le compteur soit à zéro, soit à la valeur voulue.
Il peut être utile de prévenir quand un débordement d'entier a lieu, à savoir quand le compteur n'a pas assez de bits pour encoder le décompte. Le compteur est alors remis à zéro, dans la plupart des cas. Mais il faut prévenir que le compteur a débordé, ce qui est utile pour fabriquer des circuits diviseurs de fréquence et des ''timers''. Pour cela, on ajoute une '''sortie de débordement''' au compteur, qui est mise à 1 quand le compteur déborde.
Sur les compteurs/décompteurs, il y a une entrée '''''Count Direction''''' qui décide s'il faut compter ou décompter. Typiquement, elle est à 1 s'il faut compter et 0 s'il faut décompter.
[[File:Digital counter signals.jpg|centre|vignette|upright=2|Interface d'un compteur-décompteur.]]
Dans les schémas qui vont suivre, les entrées Enable ne sont pas représentées. Il est sous-entendu qu'il y a une entrée ''Enable'' pour tous les compteurs qui vont suivre. Il existe deux méthodes pour créer de tels compteurs : la première donne ce qu'on appelle des compteurs asynchrones, et l'autre des compteurs synchrones.
==L'intérieur d'un compteur==
A une exception bien précise qu'on abordera plus bas, les compteurs sont composés d'un registre, qui mémorise le décompte, couplé à un circuit '''incrémenteur'''. Nous avions déjà abordé l'incrémenteur dans un chapitre précédent, aussi je ne vais pas ré-expliquer comment il est conçu. Tout ce qu'il faut retenir est qu'il y en a plusieurs types, le plus simple étant celui à propagation de retenue.
[[File:Fonctionnement d'un compteur (décompteur), schématique.jpg|centre|vignette|upright=2|Fonctionnement d'un compteur (décompteur), schématique. Le pas du compteur précise de combien on incrémente le compteur par cycle d'horloge.]]
L'incrémenteur le plus simple, à propagation de retenue, est fabriqué en enchainant des ''demi-additionneurs'' les uns à la suite des autres. Pour rappel, un demi-additionneur additionne deux bits. Ici, il additionne un bit de l'opérande, la retenue des colonnes précédentes. Pour le bit de poids faible, la retenue est forcé à 1. Si on combine un incrémenteur à propagation de retenue avec un registre, on obtient ce compteur :
[[File:Compteur synchrone à incrémenteur.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules D.]]
Une simplification permet de faire disparaitre les portes XOR. Les portes XOR servent ici d'inverseur commandable, commandé par la retenue entrante. Elles inversent le contenu de la bascule quand la retenue entrante vaut 1, elles laissent la bascule inchangée si la retenue vaut 0. Or, nous avons déjà une bascule qui inverse son contenu sous certaines condition : la bascule T ! Il est donc possible de fusionner chaque bascule D avec la porte XOR associée, pour donner une bascule T. Le circuit final est celui-ci :
[[File:Compteur synchrone à bascules T.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules T.]]
Il est possible d'optimiser le circuit avec les optimisations vues dans le chapitre sur les incrémenteurs. La première est le ''carry skip'' qui fait l'incrémentation non pas bit par bit, mais par paquets de deux bits. La seconde est l'anticipation de retenue. Et ces deux optimisations se marient bien avec ce qui va suivre.
===Les compteurs en cascade===
Il est possible de concevoir des compteurs à partir de compteurs plus petits, mis en cascade. Par exemple, en créant un compteur 16 bits à partir de compteurs 4 bits, enchainés l'un à la suite de l'autre.
Les compteurs mis en cascade ont les mêmes entrées et sorties que les compteurs normaux, avec cependant un détail très important : ils ont une entrée pour la retenue entrante, et une sortie pour la retenue sortante. L'entrée pour la retenue entrante précise ce qui doit être additionné au bit de poids faible. Si elle vaut zéro, l'incrémenteur n'incrémente pas l'opérande. Si elle vaut 1, le compteur est incrémenté. L'entrée de retenue fait office d'entrée ''Count Enable'', qui active ou désactive l'incrémentation.
Le compteur a aussi une sortie de débordement, qui indique que le compteur déborde. Il se trouve que cette sortie fournit la retenue pour le compteur suivant. La retenue peut être calculée en utilisant des optimisations comme l'anticipation de retenue, ce qui veut dire qu'elle est calculée sans propager les retenues, directement à partir des bits de l'opérande. Concrètement, la retenue est calculée en faisant un ET logique entre tous les bits du décompte.
[[File:Cascadable binary up-counter.jpg|centre|vignette|upright=2|Cascadable binary up-counter]]
Les compteurs sont mis en cascade de la manière suivante : leur sortie de débordement est connectée sur l'entrée ''Enable'' du compteur suivant, celle qui déclenche l'incrémentation du compteur. La sortie de débordement est notée RCO dans les schéma qui suivent, nous verrons pourquoi dans le prochaine paragraphe.
[[File:Cascaded binary counters.jpg|centre|vignette|upright=2.5|Cascaded binary counters]]
===Les compteurs modulo===
La plupart des compteurs comptent de 0 à <math>2^n - 1</math>, avec <math>n</math> la taille du compteur. Mais d'autres compteurs ne comptent pas jusque-là. Par exemple, certains compteurs ne comptent que jusqu'à 10, 150, etc. Ils sont appelés des '''compteurs ''modulo'''''. Prenons un compteur modulo 6, par exemple : il compte de 0 à 5, et est remis immédiatement à zéro quand il atteint 6. Il compte donc comme suit : 0, 1, 2, 3, 4, 5, 0, 1, 2, ...
Les compteurs ''modulo'' sont construits à partir d'un compteur normal, couplé à un circuit comparateur qui remet à zéro le registre quand il atteint la valeur maximale. Par exemple, on peut imaginer un compteur modulo 6 est construit à partir d'un compteur 4 bits qui compte de 0 à 15 (donc un compteur modulo 16), mais qui est remis à zéro quand il atteint 6. Le circuit comparateur vérifie si la valeur maximale 6 est atteinte et met à 1 l'entrée ''Reset'' si c'est le cas. Le comparateur est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Td4bfig4.png|centre|vignette|upright=1.5|Compteur modulo N.]]
Pour une minorité de compteurs ''modulo'', la valeur maximale est configurable. Pour cela, le compteur est associé à un ''registre de configuration'' qui mémorise la valeur maximale souhaitée. A chaque cycle d'horloge, la valeur dans le compteur est comparée au registre de configuration. Si elles sont identiques, le compteur est remis à zéro. Le compteur est associé au registre de configuration et à un comparateur qui vérifie que les deux sont égaux. Pour le moment, nous ne savons pas faire de circuits comparateurs, ce qui fait qu'on ne peut pas expliquer ce circuit plus en détail.
[[File:4 Bit Counter Prog 1.svg|centre|vignette|upright=2|Compteur 4 bits à valeur maximale programmable.]]
Les compteurs ont tous une sortie de débordement, qui indique quand le compteur déborde. Pour les compteurs modulo, la sortie n'est autre que la sortie du comparateur. Pour les compteurs non-modulo, la sortie de débordement est une sortie du circuit combinatoire qui incrémente le compteur.
==Les ''timers'' : compter des durées, compter des cycles d'horloge==
Les '''''timers''''', aussi appelés ''Programmable interval timer'', sont des circuits capables de compter des durées, exprimées en cycles d'horloge. Leur fonctionnement est assez simple : ils émettent un signal quand un certain nombre de cycles est écoulé, ce nombre de cycles étant configurable. On peut ainsi générer un signal qui surviendra après 50 cycles d'horloge, ou après 100 cycles d'horloge, etc. Le signal en question est disponible sur une sortie de 1 bit, et correspond tout simplement au fait que cette sortie est mise à 1, pendant un cycle d'horloge.
Les ''timers'' sont composés d'un compteur/décompteur cadencé par un signal d'horloge. Le compteur initialisé à 0, puis est incrémenté à chaque signal d'horloge, jusqu’à atteinte d'une valeur limite où il génère un signal. Pour un décompteur, c'est la même chose, sauf que le décompteur est initialisé à sa valeur limite et est décrémenté à chaque cycle, et envoie un signal quand il atteint 0. Les ''timers'' basés sur des décompteurs sont nettement plus simples que les autres, ce qui fait qu'ils sont plus utilisés Pour que les ''timers'' soient configurables, on doit pouvoir préciser combien de cycles il faut (dé-)compter avant d'émettre un signal. On peut ainsi préciser s'il faut émettre le signal après 32 cycles d'horloge, après les 50 cycles, tous les 129 cycles, etc. Le nombre de cycles en question est envoyé sur une entrée d’initialisation du compteur.
Les ''timers'' matériels peuvent compter de deux manières différentes, appelées '''mode une fois''' et '''mode périodique'''.
* En mode une fois, le ''timer'' s'arrête une fois qu'il a atteint la limite configurée. On doit le réinitialiser manuellement, par l'intermédiaire du logiciel, pour l'utiliser une nouvelle fois. Cela permet de compter une certaine durée, exprimée en nombre de cycles d'horloge.
* En mode périodique, le ''timer'' se réinitialise automatiquement avec la valeur de départ, ce qui fait qu'il reboucle à l'infini.
Un ordinateur est rempli de ''timers'' divers. Dans ce qui va suivre, nous allons voir les principaux ''timers'', qui sont actuellement intégrés dans les PC modernes. Ils se trouvent sur la carte mère ou dans le processeur, tout dépend du ''timer''.
===Le ''watchdog timer''===
Le '''''watchdog timer''''' est un ''timer'' spécifique dont le but est de redémarrer automatiquement l'ordinateur si jamais celui-ci ne répond plus ou plante. Beaucoup de PC s'en passent, mais ce ''timer'' est très fréquent dans les architectures embarquées. Le ''watchdog timer'' est un compteur/décompteur qui doit être réinitialisé régulièrement. S'il n'est pas réinitialisé, le ''watchdog timer'' déborde (revient à 0 ou atteint 0) et envoie un signal qui redémarre le système. Le système est conçu pour réinitialiser le ''watchdog timer'' régulièrement, ce qui signifie que le système n'est pas censé redémarrer. Si jamais le système dysfonctionne gravement, le système ne pourra pas réinitialiser le ''watchdog timer'' et le système est redémarré automatiquement ou mis en arrêt.
[[File:SimpleWatchdogTimer.gif|centre|vignette|upright=2|Le ''Watchdog Timer'' et l'ordinateur.]]
===Le ''Time Stamp Counter'' des processeurs x86===
Tous les processeurs des PC actuels sont des processeurs dits x86. Nous ne pouvons pas expliquer ce que cela signifie pour le moment, retenez juste ce terme. Sachez que tous les processeurs x86 contiennent un compteur de 64 bits, appelé le '''''Time Stamp Counter''''', qui mémorise le nombre de cycles d'horloge qu'a effectué le processeur depuis son démarrage. Les programmes peuvent accéder à ce registre assez facilement, ce qui est utile pour faire des mesures ou comparer les performances de deux applications. Il permet de compter combien de cycles d'horloge met un morceau de code à s’exécuter, combien de cycles prend une instruction à s’exécuter, etc. Les processeurs non-x86 ont un registre équivalent, que ce soit les processeurs ARM ou d'autres.
Malheureusement, ce compteur est tombé en désuétude pour tout un tas de raisons. La principale est que les processeurs actuels font varier leur fréquence suivant les besoins. Ils augmentent leur fréquence quand on leur demande de faire beaucoup de calculs, et se mettent en mode basse(fréquence pour économiser de l'énergie si on ne leur demande pas grand chose. Avec une fréquence variable, le ''Time Stamp Counter'' perd complétement en fiabilité. Intel a tenté de corriger ce défaut en incrémentant ce registre à une fréquence constante, différente de celle du processeur, ce qui est encore le cas sur les processeurs Intel actuels. Le comportement est un peu différent sur les processeurs AMD, qui cadencent ce ''timer'' à la fréquence du processeur mais utilisent des mécanismes de synchronisation assez complexes pour corriger l'effet de la fréquence variable.
===L'horloge temps réel===
L''''horloge temps réel''' est un ''timer'' qui génère une fréquence de 1024 Hz, soit près d'un Kilohertz. Dans ce qui suit, nous la noterons RTC, ce qui est l'acronyme du terme anglais ''Real Time Clock''. La RTC prend en entrée un signal d'horloge de 32KHz, généré par un oscillateur à Quartz, et fournit en sortie un signal de fréquence 32 fois plus faible, c'est à dire de 1 KHz. Pour cela, elle est réglée en mode répétitif et son décompteur interne est initialisé à 32. La RTC génère donc un signal toutes les millisecondes, qui est envoyé au processeur. On peut, en théorie, changer la fréquence de la RTC, mais c'est rarement une bonne idée.
En théorie, la RTC permet de compter des durées assez courtes, comme le ''ping'' (le temps de latence d'un réseau, pour simplifier), le temps de rafraichissement de l'écran, ou bien d'autres choses. Mais dans les faits, l'horloge temps réel sa fréquence n'aide pas : 1024 Hz est proche de 1000, mais pas assez pour faire des mesures à la milliseconde près, chose qui est nécessaire pour mesurer le ''ping'' ou d'autres choses utiles. A la place, l'ordinateur l'utilise pour que l'ordinateur soit toujours à l'heure. L'ordinateur sait quelle heure il est avec une précision de l'ordre de la seconde (vous pouvez regarder le bureau de Windows dans le coin inférieur droite de votre écran pour vous en convaincre).
===Le ''Programmable Interval Timer'' : l'Intel 8253===
L'Intel 8253 était un ''timer'' programmable autrefois soudé sur les cartes mères des premiers PC. Il fût suivi par l'Intel 8254, qui en était une légère amélioration. Il était cadencé par une horloge maitre, générée par un oscillateur à Quartz, dont la fréquence est de 32 768 Hertz, soit 2^15 cycles d'horloge par seconde. S'il n'est plus présent dans un boitier sur la carte mère, on trouve toujours un circuit semblable au 8253 à l'intérieur du ''chipset'' de la carte mère, voire à l'intérieur du processeur, pour des raisons de compatibilité. L'intérieur de l'Intel 8253 est illustré ci-dessous. Nous allons expliquer l'ensemble de ce schéma, rassurez-vous, mais les explications seront plus simples à comprendre si vous survolez ce schéma en premier lieu.
[[File:Intel 8253 block diagram.svg|centre|vignette|upright=2|Intel 8253, intérieur.]]
L'Intel 8253 contient trois compteurs de 16 bits, numérotés 0, 1 et 2. Pour chaque compteur, l'entrée CLOCK est celle de l'horloge de 32 MHz, l'entrée GATE active ou désactive le compteur, la sortie fournit le signal voulu et/ou la fréquence de sortie. Les trois compteurs étaient utilisés pour dériver plusieurs fréquences allant de 18,2 Hz à environ 500 KHz. Par exemple, il était utilisé par défaut pour le rafraichissement de la mémoire (D)RAM, mais il était souvent reprogrammé pour servir à générer des fréquences spécifiques par le BIOS ou la carte graphique.
[[File:Intel 8253 and 8254.svg|vignette|Intel 8253 and 8254]]
L'Intel 8253 lui-même possède plusieurs entrées et sorties. En premier lieu, on voit un port de 8 bits connecté aux trois compteurs, qui permet à l'Intel 8253 de communiquer avec le reste de l'ordinateur. La communication se fait dans les deux sens : soit de l'ordinateur vers les compteurs, soit des compteurs vers l'ordinateur. Dans le sens ordinateur -> compteurs, cela permet à l'ordinateur de programmer les compteurs, de les initialiser. Dans l'autre sens, cela permet de récupérer le contenu des compteurs, même si ce n'est pas très utilisé. Il y a aussi 5 entrées de configuration :
* Deux bits A0 et A1 pour sélectionner le compteur voulu avec son numéro.
* Un bit RD à mettre à 0 pour que l'ordinateur récupère le compteur sélectionné sur le port de 8 bits.
* Un bit WR à mettre à 0 pour que l'ordinateur modifie le compteur sélectionné, en envoyant le nombre pour l'initialisation sur le port de 8 bits.
* Un bit CS qui active ou désactive l'Intel 8253 et permet de l'allumer ou de l’éteindre.
L'Intel 8253 intégre un registre de 8 bits, le ''Control Word register'' qui mémorise la configuration de l'Intel 8253. Pour programmer les trois compteurs, il faut écrire un mot de 8 bits dans ce ''Control Word register''. Pour écrire dans le ''Control Word register'', il faut mettre le bit CS à 0 (on active l'Intel 8253), mettre le bit RDà 1 , le bit WR à 0 le bit WR (on indique qu'on fait une écriture), sélectionner le ''Control Word register'' en mettant les deux bits A0 et A1 à 1, puis envoyer la configuration du ''Control Word register'' sur le port de 8 bits.
===Le ''High Precision Event Timer'' (HPET)===
De nos jours, l'horloge temps réel et l'Intel 8253/8254 tendent à être remplacé par un autre ''timer'', le ''High Precision Event Timer'' (HPET). Il s'agit d'un compteur de 64 bits, dont la fréquence est d'au moins 10 MHz. Il s'agit bien d'un compteur et non d'un décompteur. Il gère nativement plusieurs valeurs limites à laquelle générer un signal, qui sont configurables. Pour cela, il est couplé à plusieurs comparateurs, chacun associé à un registre pour mémoriser la valeur limite. Il doit y avoir au moins trois comparateurs/registres, mais le nombre peut monter jusqu’à 256.
[[File:High Precision Event Timer.png|centre|vignette|upright=2|High Precision Event Timer]]
Il faut noter que les systèmes d'exploitation conçus avant le HPET ne peuvent pas l'utiliser, pour des raisons de compatibilité matérielle. C'est le cas de Windows XP avant le Service Pack 3. C'est la raison pour laquelle les cartes mères émulent RTC et PIT dans leurs circuits. D'ailleurs, pour économiser des circuits, les cartes mères modernes émulent le PIT et la RTC avec le HPET : le premier comparateur fournisse la fréquence de 1024 Hz de la RTC, 3 autres comparateurs remplacent l'Intel 8253.
Le HPET gère de nombreux modes de fonctionnement : ses comparateurs peuvent être configuré en mode une fois ou périodique, on peut lui demander d'émuler la RTC et le PIT, etc. Chaque comparateur doit pouvoir fonctionner en mode une fois, et au moins un comparateur doit pouvoir fonctionner en mode périodique. Aussi, il contient aussi 3 registres de configuration. Notons qu'il est aussi possible de lire ou écrire dans le compteur de 64 bits, mais ce n'est pas recommandé.
==Les compteurs en code Gray==
Les compteurs classiques encodent leur décompte en binaire normal sur <math>n</math> bits, mais il faut savoir que d'autres compteurs utilisent le BCD, d'autre le code Gray, etc. Nous allons voir dans ce qui suit ceux qui comptent en code Gray.
Pour rappel, le code Gray permet de coder des nombres d'une manière un peu différente du binaire normal. Son avantage principal est que lorsqu'on incrémente ou décrémente un nombre, seul un bit change ! Pour comparer, en binaire normal, lorsqu'on passe incrémente un nombre, il peut y avoir plusieurs bits qui changent. La moyenne est d'environ deux bits par incrémentation. Les compteurs en code Gray ont beaucoup d'avantages, qui sont tous liés à cette propriété.
Le premier l'absence d'état transitoires douteux. Le problème est que les bits modifiés par une incrémentation ne le sont pas en même temps. Les bits de poids faibles sont modifiés avant les autres. Évidemment, à la fin du calcul, on obtient le résultat final, correct. Mais pendant le temps de calcul, le compteur peut se retrouver dans un état transitoire, où seuls les bits de poids faibles ont été modifiés. C'est un problème si le contenu de ce compteur est relié à des circuits rapides, qui peuvent voir cet état transitoire, mais ne le doivent pas sous peine de dysfonctionner. L'usage de compteurs en code Gray permet d'éviter ce problème : vu que seul un bit est modifié lors d'une incrémentation/décrémentation, les états transitoires n'existent tout simplement pas.
Un exemple typique, évoqué dans les chapitres précédents, est l'échange d'informations entre deux domaines d'horloge. Pour rappel, il arrive que deux portions d'un circuit imprimé aillent à des fréquences différences : on dit que le circuit à plusieurs domaines d'horloge. S'il faut échanger des informations entre ces deux domaines d'horloge, divers problèmes surviennent. Un domaine d'horloge sera plus rapide que l'autre, et pourra voir les états transitoires invisible de l'autre circuit. Et par voir, on veut dire qu'il les prendra pour des états valides, ce qui fera dysfonctionner le circuit. Pour éviter cela, diverses techniques de croisement de domaines d'horloge existent. Et les compteurs Gray en font partie : si un domaine d'horloge utilise la valeur d'un compteur de l'autre, mieux vaut que ce compteur soit un compteur Gray. Et cette situation est assez fréquente !
Un autre avantage mineurs est que la consommation d'énergie de ces compteurs est bien plus réduite qu'avec un compteur normal. Rappelons que pour fonctionner, les circuits électroniques consomment un peu d'électricité. Et la majeure partie de cette consommation sert à faire passer un bit de 0 à 1 ou de 1 à 0. Ce qui fait que quand un compteur est incrémenté ou décrémenté, cela consomme un peu d'énergie électrique.
La moyenne pour un compteur binaire normal est de 2 bits changés par incrémentation/décrémentation, contre un seul pour un compteur Gray, on devine que ces derniers consomment deux fois moins d'énergie par incrémentation. Et cet avantage a des effets en cascade sur les circuits qui suivent ce compteur. Si l'entrée de ces circuits ne change que d'un seul bit, alors leur état changera moins que si c'était deux bits. Les circuits qui suivent vont donc moins consommer.
Un autre avantage en matière de consommation énergétique est lié auxs transitions d'état douteuses. Les circuits connectés au compteur vont voir ces transitions d'état douteuses et modifier leur état interne en réaction. Bien sur, l'état final correct sera atteint une fois que le compteur sera stabilisé, ce qui effacera ces états transitoires intermédiaires. Mais chaque état intermédiaire transitoire correspond à un changement d'état, donc à une consommation d'énergie. En supprimant ces états transitoires, on réduit fortement la consommation d'énergie du circuit. Cela vaut pour le compteur Gray lui-même, mais aussi sur tous les circuits qui ont ce compteur comme entrée !
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les registres et mémoires adressables
| prevText=Les registres et mémoires adressables
| next=Les registres à décalage et les LSFR
| nextText=Les registres à décalage et les LSFR
}}
</noinclude>
eg3o1xfpmih4of0isn08pzy3p837fiw
768626
768623
2026-06-25T15:23:29Z
Mewtow
31375
/* L'intérieur d'un compteur */
768626
wikitext
text/x-wiki
Les '''compteurs/décompteurs''' sont des circuits électroniques qui mémorisent un nombre et l'incrémentent à la demande. En clair, ce sont des registres améliorés afin de supporter l'incrémentation et la décrémentation. Pour donner un exemple d'utilisation, imaginez un circuit qui compte le nombre de voitures dans un parking dans la journée. Pour cela, vous allez prendre deux circuits qui détectent respectivement l'entrée ou la sortie d'une voiture, et un compteur. Le compteur est initialisé à 0 quand le parking est vide, puis est incrémenté à chaque entrée de voiture, décrémenté à chaque sortie. Les exemples de ce type sont suffisamment nombreux pour qu'on dédie un chapitre aux compteurs.
[[File:Binary counter.gif|vignette|Illustration du fonctionnement d'un compteur modulaire binaire de 4 bits, avec un pas de compteur de 1 (le contenu est augmenté de 1 à chaque mise à jour).]]
Un compteur mémorise un nombre qui est incrémenté ou décrémenté au besoin. Le nombre mémorisé sera appelé le '''décompte''' dans ce qui suit. Il est mémorisé dans un registre à l'intérieur du compteur. Au passage, le nombre de bits <math>n</math> du compteur est appelé la '''taille du compteur''', par analogie avec les registres. Il faut cependant faire la différence entre les ''compteurs'' d'un côté et les ''décompteurs'' de l'autre. Les compteurs incrémentent le décompte, les décompteurs le décrémentent, les compteurs-décompteurs peuvent faire les deux.
==L'interface d'un compteur/décompteur==
Les compteurs et décompteurs sont des circuits synchrones et ont donc une entrée d'horloge. Les compteurs les plus simples incrémentent leur contenu à chaque cycle d'horloge, mais les plus fréquents n'incrémentent le décompte que sur demande. Pour cela, ils disposent d'une entrée '''''Count Enable''''', similaire à l'entrée ''Enable'' des registres, séparée de l'entrée d'horloge. Le décompte est incrémenté/décrémenté seulement si l'entrée Enable est à 1, lors d'un front adéquat sur le signal d'horloge.
Les compteurs ont aussi une entrée '''''Reset''''' qui permet de les remettre à zéro. Il y a parfois une entrée qui permet d'initialiser le compteur à une valeur par défaut, non-nulle. Par exemple, on peut initialiser le décompte à la valeur 5, ou une autre. Pour cela, le compteur dispose de deux entrées : une entrée sur laquelle envoyer le décompte initial, une entrée pour autoriser la réinitialisation. Les entrées en question sont appelées '''''Preload Data''''' et '''''Preload Enable'''''. La seconde entrée est parfois distincte de l'entrée de réinitialisation, pour permettre de réinitialiser le compteur soit à zéro, soit à la valeur voulue.
Il peut être utile de prévenir quand un débordement d'entier a lieu, à savoir quand le compteur n'a pas assez de bits pour encoder le décompte. Le compteur est alors remis à zéro, dans la plupart des cas. Mais il faut prévenir que le compteur a débordé, ce qui est utile pour fabriquer des circuits diviseurs de fréquence et des ''timers''. Pour cela, on ajoute une '''sortie de débordement''' au compteur, qui est mise à 1 quand le compteur déborde.
Sur les compteurs/décompteurs, il y a une entrée '''''Count Direction''''' qui décide s'il faut compter ou décompter. Typiquement, elle est à 1 s'il faut compter et 0 s'il faut décompter.
[[File:Digital counter signals.jpg|centre|vignette|upright=2|Interface d'un compteur-décompteur.]]
Dans les schémas qui vont suivre, les entrées Enable ne sont pas représentées. Il est sous-entendu qu'il y a une entrée ''Enable'' pour tous les compteurs qui vont suivre. Il existe deux méthodes pour créer de tels compteurs : la première donne ce qu'on appelle des compteurs asynchrones, et l'autre des compteurs synchrones.
==L'intérieur d'un compteur==
A une exception bien précise qu'on abordera plus bas, les compteurs sont composés d'un registre, qui mémorise le décompte, couplé à un circuit '''incrémenteur'''. Nous avions déjà abordé l'incrémenteur dans un chapitre précédent, aussi je ne vais pas ré-expliquer comment il est conçu. Tout ce qu'il faut retenir est qu'il y en a plusieurs types, le plus simple étant celui à propagation de retenue.
[[File:Fonctionnement d'un compteur (décompteur), schématique.jpg|centre|vignette|upright=2|Fonctionnement d'un compteur (décompteur), schématique. Le pas du compteur précise de combien on incrémente le compteur par cycle d'horloge.]]
===Les compteurs synchrones basiques===
L'incrémenteur le plus simple, à propagation de retenue, est fabriqué en enchainant des ''demi-additionneurs'' les uns à la suite des autres. Pour rappel, un demi-additionneur additionne deux bits. Ici, il additionne un bit de l'opérande, la retenue des colonnes précédentes. Pour le bit de poids faible, la retenue est forcé à 1. Si on combine un incrémenteur à propagation de retenue avec un registre, on obtient ce compteur :
[[File:Compteur synchrone à incrémenteur.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules D.]]
Une simplification permet de faire disparaitre les portes XOR. Les portes XOR servent ici d'inverseur commandable, commandé par la retenue entrante. Elles inversent le contenu de la bascule quand la retenue entrante vaut 1, elles laissent la bascule inchangée si la retenue vaut 0. Or, nous avons déjà une bascule qui inverse son contenu sous certaines condition : la bascule T ! Il est donc possible de fusionner chaque bascule D avec la porte XOR associée, pour donner une bascule T. Le circuit final est celui-ci :
[[File:Compteur synchrone à bascules T.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules T.]]
Il est possible d'optimiser le circuit avec les optimisations vues dans le chapitre sur les incrémenteurs. La première est le ''carry skip'' qui fait l'incrémentation non pas bit par bit, mais par paquets de deux bits. La seconde est l'anticipation de retenue. Et ces deux optimisations se marient bien avec ce qui va suivre.
===Les compteurs en cascade===
Il est possible de concevoir des compteurs à partir de compteurs plus petits, mis en cascade. Par exemple, en créant un compteur 16 bits à partir de compteurs 4 bits, enchainés l'un à la suite de l'autre.
Les compteurs mis en cascade ont les mêmes entrées et sorties que les compteurs normaux, avec cependant un détail très important : ils ont une entrée pour la retenue entrante, et une sortie pour la retenue sortante. L'entrée pour la retenue entrante précise ce qui doit être additionné au bit de poids faible. Si elle vaut zéro, l'incrémenteur n'incrémente pas l'opérande. Si elle vaut 1, le compteur est incrémenté. L'entrée de retenue fait office d'entrée ''Count Enable'', qui active ou désactive l'incrémentation.
Le compteur a aussi une sortie de débordement, qui indique que le compteur déborde. Il se trouve que cette sortie fournit la retenue pour le compteur suivant. La retenue peut être calculée en utilisant des optimisations comme l'anticipation de retenue, ce qui veut dire qu'elle est calculée sans propager les retenues, directement à partir des bits de l'opérande. Concrètement, la retenue est calculée en faisant un ET logique entre tous les bits du décompte.
[[File:Cascadable binary up-counter.jpg|centre|vignette|upright=2|Cascadable binary up-counter]]
Les compteurs sont mis en cascade de la manière suivante : leur sortie de débordement est connectée sur l'entrée ''Enable'' du compteur suivant, celle qui déclenche l'incrémentation du compteur. La sortie de débordement est notée RCO dans les schéma qui suivent, nous verrons pourquoi dans le prochaine paragraphe.
[[File:Cascaded binary counters.jpg|centre|vignette|upright=2.5|Cascaded binary counters]]
===Les compteurs modulo===
La plupart des compteurs comptent de 0 à <math>2^n - 1</math>, avec <math>n</math> la taille du compteur. Mais d'autres compteurs ne comptent pas jusque-là. Par exemple, certains compteurs ne comptent que jusqu'à 10, 150, etc. Ils sont appelés des '''compteurs ''modulo'''''. Prenons un compteur modulo 6, par exemple : il compte de 0 à 5, et est remis immédiatement à zéro quand il atteint 6. Il compte donc comme suit : 0, 1, 2, 3, 4, 5, 0, 1, 2, ...
Les compteurs ''modulo'' sont construits à partir d'un compteur normal, couplé à un circuit comparateur qui remet à zéro le registre quand il atteint la valeur maximale. Par exemple, on peut imaginer un compteur modulo 6 est construit à partir d'un compteur 4 bits qui compte de 0 à 15 (donc un compteur modulo 16), mais qui est remis à zéro quand il atteint 6. Le circuit comparateur vérifie si la valeur maximale 6 est atteinte et met à 1 l'entrée ''Reset'' si c'est le cas. Le comparateur est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Td4bfig4.png|centre|vignette|upright=1.5|Compteur modulo N.]]
Pour une minorité de compteurs ''modulo'', la valeur maximale est configurable. Pour cela, le compteur est associé à un ''registre de configuration'' qui mémorise la valeur maximale souhaitée. A chaque cycle d'horloge, la valeur dans le compteur est comparée au registre de configuration. Si elles sont identiques, le compteur est remis à zéro. Le compteur est associé au registre de configuration et à un comparateur qui vérifie que les deux sont égaux. Pour le moment, nous ne savons pas faire de circuits comparateurs, ce qui fait qu'on ne peut pas expliquer ce circuit plus en détail.
[[File:4 Bit Counter Prog 1.svg|centre|vignette|upright=2|Compteur 4 bits à valeur maximale programmable.]]
Les compteurs ont tous une sortie de débordement, qui indique quand le compteur déborde. Pour les compteurs modulo, la sortie n'est autre que la sortie du comparateur. Pour les compteurs non-modulo, la sortie de débordement est une sortie du circuit combinatoire qui incrémente le compteur.
==Les ''timers'' : compter des durées, compter des cycles d'horloge==
Les '''''timers''''', aussi appelés ''Programmable interval timer'', sont des circuits capables de compter des durées, exprimées en cycles d'horloge. Leur fonctionnement est assez simple : ils émettent un signal quand un certain nombre de cycles est écoulé, ce nombre de cycles étant configurable. On peut ainsi générer un signal qui surviendra après 50 cycles d'horloge, ou après 100 cycles d'horloge, etc. Le signal en question est disponible sur une sortie de 1 bit, et correspond tout simplement au fait que cette sortie est mise à 1, pendant un cycle d'horloge.
Les ''timers'' sont composés d'un compteur/décompteur cadencé par un signal d'horloge. Le compteur initialisé à 0, puis est incrémenté à chaque signal d'horloge, jusqu’à atteinte d'une valeur limite où il génère un signal. Pour un décompteur, c'est la même chose, sauf que le décompteur est initialisé à sa valeur limite et est décrémenté à chaque cycle, et envoie un signal quand il atteint 0. Les ''timers'' basés sur des décompteurs sont nettement plus simples que les autres, ce qui fait qu'ils sont plus utilisés Pour que les ''timers'' soient configurables, on doit pouvoir préciser combien de cycles il faut (dé-)compter avant d'émettre un signal. On peut ainsi préciser s'il faut émettre le signal après 32 cycles d'horloge, après les 50 cycles, tous les 129 cycles, etc. Le nombre de cycles en question est envoyé sur une entrée d’initialisation du compteur.
Les ''timers'' matériels peuvent compter de deux manières différentes, appelées '''mode une fois''' et '''mode périodique'''.
* En mode une fois, le ''timer'' s'arrête une fois qu'il a atteint la limite configurée. On doit le réinitialiser manuellement, par l'intermédiaire du logiciel, pour l'utiliser une nouvelle fois. Cela permet de compter une certaine durée, exprimée en nombre de cycles d'horloge.
* En mode périodique, le ''timer'' se réinitialise automatiquement avec la valeur de départ, ce qui fait qu'il reboucle à l'infini.
Un ordinateur est rempli de ''timers'' divers. Dans ce qui va suivre, nous allons voir les principaux ''timers'', qui sont actuellement intégrés dans les PC modernes. Ils se trouvent sur la carte mère ou dans le processeur, tout dépend du ''timer''.
===Le ''watchdog timer''===
Le '''''watchdog timer''''' est un ''timer'' spécifique dont le but est de redémarrer automatiquement l'ordinateur si jamais celui-ci ne répond plus ou plante. Beaucoup de PC s'en passent, mais ce ''timer'' est très fréquent dans les architectures embarquées. Le ''watchdog timer'' est un compteur/décompteur qui doit être réinitialisé régulièrement. S'il n'est pas réinitialisé, le ''watchdog timer'' déborde (revient à 0 ou atteint 0) et envoie un signal qui redémarre le système. Le système est conçu pour réinitialiser le ''watchdog timer'' régulièrement, ce qui signifie que le système n'est pas censé redémarrer. Si jamais le système dysfonctionne gravement, le système ne pourra pas réinitialiser le ''watchdog timer'' et le système est redémarré automatiquement ou mis en arrêt.
[[File:SimpleWatchdogTimer.gif|centre|vignette|upright=2|Le ''Watchdog Timer'' et l'ordinateur.]]
===Le ''Time Stamp Counter'' des processeurs x86===
Tous les processeurs des PC actuels sont des processeurs dits x86. Nous ne pouvons pas expliquer ce que cela signifie pour le moment, retenez juste ce terme. Sachez que tous les processeurs x86 contiennent un compteur de 64 bits, appelé le '''''Time Stamp Counter''''', qui mémorise le nombre de cycles d'horloge qu'a effectué le processeur depuis son démarrage. Les programmes peuvent accéder à ce registre assez facilement, ce qui est utile pour faire des mesures ou comparer les performances de deux applications. Il permet de compter combien de cycles d'horloge met un morceau de code à s’exécuter, combien de cycles prend une instruction à s’exécuter, etc. Les processeurs non-x86 ont un registre équivalent, que ce soit les processeurs ARM ou d'autres.
Malheureusement, ce compteur est tombé en désuétude pour tout un tas de raisons. La principale est que les processeurs actuels font varier leur fréquence suivant les besoins. Ils augmentent leur fréquence quand on leur demande de faire beaucoup de calculs, et se mettent en mode basse(fréquence pour économiser de l'énergie si on ne leur demande pas grand chose. Avec une fréquence variable, le ''Time Stamp Counter'' perd complétement en fiabilité. Intel a tenté de corriger ce défaut en incrémentant ce registre à une fréquence constante, différente de celle du processeur, ce qui est encore le cas sur les processeurs Intel actuels. Le comportement est un peu différent sur les processeurs AMD, qui cadencent ce ''timer'' à la fréquence du processeur mais utilisent des mécanismes de synchronisation assez complexes pour corriger l'effet de la fréquence variable.
===L'horloge temps réel===
L''''horloge temps réel''' est un ''timer'' qui génère une fréquence de 1024 Hz, soit près d'un Kilohertz. Dans ce qui suit, nous la noterons RTC, ce qui est l'acronyme du terme anglais ''Real Time Clock''. La RTC prend en entrée un signal d'horloge de 32KHz, généré par un oscillateur à Quartz, et fournit en sortie un signal de fréquence 32 fois plus faible, c'est à dire de 1 KHz. Pour cela, elle est réglée en mode répétitif et son décompteur interne est initialisé à 32. La RTC génère donc un signal toutes les millisecondes, qui est envoyé au processeur. On peut, en théorie, changer la fréquence de la RTC, mais c'est rarement une bonne idée.
En théorie, la RTC permet de compter des durées assez courtes, comme le ''ping'' (le temps de latence d'un réseau, pour simplifier), le temps de rafraichissement de l'écran, ou bien d'autres choses. Mais dans les faits, l'horloge temps réel sa fréquence n'aide pas : 1024 Hz est proche de 1000, mais pas assez pour faire des mesures à la milliseconde près, chose qui est nécessaire pour mesurer le ''ping'' ou d'autres choses utiles. A la place, l'ordinateur l'utilise pour que l'ordinateur soit toujours à l'heure. L'ordinateur sait quelle heure il est avec une précision de l'ordre de la seconde (vous pouvez regarder le bureau de Windows dans le coin inférieur droite de votre écran pour vous en convaincre).
===Le ''Programmable Interval Timer'' : l'Intel 8253===
L'Intel 8253 était un ''timer'' programmable autrefois soudé sur les cartes mères des premiers PC. Il fût suivi par l'Intel 8254, qui en était une légère amélioration. Il était cadencé par une horloge maitre, générée par un oscillateur à Quartz, dont la fréquence est de 32 768 Hertz, soit 2^15 cycles d'horloge par seconde. S'il n'est plus présent dans un boitier sur la carte mère, on trouve toujours un circuit semblable au 8253 à l'intérieur du ''chipset'' de la carte mère, voire à l'intérieur du processeur, pour des raisons de compatibilité. L'intérieur de l'Intel 8253 est illustré ci-dessous. Nous allons expliquer l'ensemble de ce schéma, rassurez-vous, mais les explications seront plus simples à comprendre si vous survolez ce schéma en premier lieu.
[[File:Intel 8253 block diagram.svg|centre|vignette|upright=2|Intel 8253, intérieur.]]
L'Intel 8253 contient trois compteurs de 16 bits, numérotés 0, 1 et 2. Pour chaque compteur, l'entrée CLOCK est celle de l'horloge de 32 MHz, l'entrée GATE active ou désactive le compteur, la sortie fournit le signal voulu et/ou la fréquence de sortie. Les trois compteurs étaient utilisés pour dériver plusieurs fréquences allant de 18,2 Hz à environ 500 KHz. Par exemple, il était utilisé par défaut pour le rafraichissement de la mémoire (D)RAM, mais il était souvent reprogrammé pour servir à générer des fréquences spécifiques par le BIOS ou la carte graphique.
[[File:Intel 8253 and 8254.svg|vignette|Intel 8253 and 8254]]
L'Intel 8253 lui-même possède plusieurs entrées et sorties. En premier lieu, on voit un port de 8 bits connecté aux trois compteurs, qui permet à l'Intel 8253 de communiquer avec le reste de l'ordinateur. La communication se fait dans les deux sens : soit de l'ordinateur vers les compteurs, soit des compteurs vers l'ordinateur. Dans le sens ordinateur -> compteurs, cela permet à l'ordinateur de programmer les compteurs, de les initialiser. Dans l'autre sens, cela permet de récupérer le contenu des compteurs, même si ce n'est pas très utilisé. Il y a aussi 5 entrées de configuration :
* Deux bits A0 et A1 pour sélectionner le compteur voulu avec son numéro.
* Un bit RD à mettre à 0 pour que l'ordinateur récupère le compteur sélectionné sur le port de 8 bits.
* Un bit WR à mettre à 0 pour que l'ordinateur modifie le compteur sélectionné, en envoyant le nombre pour l'initialisation sur le port de 8 bits.
* Un bit CS qui active ou désactive l'Intel 8253 et permet de l'allumer ou de l’éteindre.
L'Intel 8253 intégre un registre de 8 bits, le ''Control Word register'' qui mémorise la configuration de l'Intel 8253. Pour programmer les trois compteurs, il faut écrire un mot de 8 bits dans ce ''Control Word register''. Pour écrire dans le ''Control Word register'', il faut mettre le bit CS à 0 (on active l'Intel 8253), mettre le bit RDà 1 , le bit WR à 0 le bit WR (on indique qu'on fait une écriture), sélectionner le ''Control Word register'' en mettant les deux bits A0 et A1 à 1, puis envoyer la configuration du ''Control Word register'' sur le port de 8 bits.
===Le ''High Precision Event Timer'' (HPET)===
De nos jours, l'horloge temps réel et l'Intel 8253/8254 tendent à être remplacé par un autre ''timer'', le ''High Precision Event Timer'' (HPET). Il s'agit d'un compteur de 64 bits, dont la fréquence est d'au moins 10 MHz. Il s'agit bien d'un compteur et non d'un décompteur. Il gère nativement plusieurs valeurs limites à laquelle générer un signal, qui sont configurables. Pour cela, il est couplé à plusieurs comparateurs, chacun associé à un registre pour mémoriser la valeur limite. Il doit y avoir au moins trois comparateurs/registres, mais le nombre peut monter jusqu’à 256.
[[File:High Precision Event Timer.png|centre|vignette|upright=2|High Precision Event Timer]]
Il faut noter que les systèmes d'exploitation conçus avant le HPET ne peuvent pas l'utiliser, pour des raisons de compatibilité matérielle. C'est le cas de Windows XP avant le Service Pack 3. C'est la raison pour laquelle les cartes mères émulent RTC et PIT dans leurs circuits. D'ailleurs, pour économiser des circuits, les cartes mères modernes émulent le PIT et la RTC avec le HPET : le premier comparateur fournisse la fréquence de 1024 Hz de la RTC, 3 autres comparateurs remplacent l'Intel 8253.
Le HPET gère de nombreux modes de fonctionnement : ses comparateurs peuvent être configuré en mode une fois ou périodique, on peut lui demander d'émuler la RTC et le PIT, etc. Chaque comparateur doit pouvoir fonctionner en mode une fois, et au moins un comparateur doit pouvoir fonctionner en mode périodique. Aussi, il contient aussi 3 registres de configuration. Notons qu'il est aussi possible de lire ou écrire dans le compteur de 64 bits, mais ce n'est pas recommandé.
==Les compteurs en code Gray==
Les compteurs classiques encodent leur décompte en binaire normal sur <math>n</math> bits, mais il faut savoir que d'autres compteurs utilisent le BCD, d'autre le code Gray, etc. Nous allons voir dans ce qui suit ceux qui comptent en code Gray.
Pour rappel, le code Gray permet de coder des nombres d'une manière un peu différente du binaire normal. Son avantage principal est que lorsqu'on incrémente ou décrémente un nombre, seul un bit change ! Pour comparer, en binaire normal, lorsqu'on passe incrémente un nombre, il peut y avoir plusieurs bits qui changent. La moyenne est d'environ deux bits par incrémentation. Les compteurs en code Gray ont beaucoup d'avantages, qui sont tous liés à cette propriété.
Le premier l'absence d'état transitoires douteux. Le problème est que les bits modifiés par une incrémentation ne le sont pas en même temps. Les bits de poids faibles sont modifiés avant les autres. Évidemment, à la fin du calcul, on obtient le résultat final, correct. Mais pendant le temps de calcul, le compteur peut se retrouver dans un état transitoire, où seuls les bits de poids faibles ont été modifiés. C'est un problème si le contenu de ce compteur est relié à des circuits rapides, qui peuvent voir cet état transitoire, mais ne le doivent pas sous peine de dysfonctionner. L'usage de compteurs en code Gray permet d'éviter ce problème : vu que seul un bit est modifié lors d'une incrémentation/décrémentation, les états transitoires n'existent tout simplement pas.
Un exemple typique, évoqué dans les chapitres précédents, est l'échange d'informations entre deux domaines d'horloge. Pour rappel, il arrive que deux portions d'un circuit imprimé aillent à des fréquences différences : on dit que le circuit à plusieurs domaines d'horloge. S'il faut échanger des informations entre ces deux domaines d'horloge, divers problèmes surviennent. Un domaine d'horloge sera plus rapide que l'autre, et pourra voir les états transitoires invisible de l'autre circuit. Et par voir, on veut dire qu'il les prendra pour des états valides, ce qui fera dysfonctionner le circuit. Pour éviter cela, diverses techniques de croisement de domaines d'horloge existent. Et les compteurs Gray en font partie : si un domaine d'horloge utilise la valeur d'un compteur de l'autre, mieux vaut que ce compteur soit un compteur Gray. Et cette situation est assez fréquente !
Un autre avantage mineurs est que la consommation d'énergie de ces compteurs est bien plus réduite qu'avec un compteur normal. Rappelons que pour fonctionner, les circuits électroniques consomment un peu d'électricité. Et la majeure partie de cette consommation sert à faire passer un bit de 0 à 1 ou de 1 à 0. Ce qui fait que quand un compteur est incrémenté ou décrémenté, cela consomme un peu d'énergie électrique.
La moyenne pour un compteur binaire normal est de 2 bits changés par incrémentation/décrémentation, contre un seul pour un compteur Gray, on devine que ces derniers consomment deux fois moins d'énergie par incrémentation. Et cet avantage a des effets en cascade sur les circuits qui suivent ce compteur. Si l'entrée de ces circuits ne change que d'un seul bit, alors leur état changera moins que si c'était deux bits. Les circuits qui suivent vont donc moins consommer.
Un autre avantage en matière de consommation énergétique est lié auxs transitions d'état douteuses. Les circuits connectés au compteur vont voir ces transitions d'état douteuses et modifier leur état interne en réaction. Bien sur, l'état final correct sera atteint une fois que le compteur sera stabilisé, ce qui effacera ces états transitoires intermédiaires. Mais chaque état intermédiaire transitoire correspond à un changement d'état, donc à une consommation d'énergie. En supprimant ces états transitoires, on réduit fortement la consommation d'énergie du circuit. Cela vaut pour le compteur Gray lui-même, mais aussi sur tous les circuits qui ont ce compteur comme entrée !
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les registres et mémoires adressables
| prevText=Les registres et mémoires adressables
| next=Les registres à décalage et les LSFR
| nextText=Les registres à décalage et les LSFR
}}
</noinclude>
fg19okcpyurtw1g5mvfn498sm4fr8l0
768639
768626
2026-06-25T16:40:36Z
Mewtow
31375
/* Les compteurs en cascade */
768639
wikitext
text/x-wiki
Les '''compteurs/décompteurs''' sont des circuits électroniques qui mémorisent un nombre et l'incrémentent à la demande. En clair, ce sont des registres améliorés afin de supporter l'incrémentation et la décrémentation. Pour donner un exemple d'utilisation, imaginez un circuit qui compte le nombre de voitures dans un parking dans la journée. Pour cela, vous allez prendre deux circuits qui détectent respectivement l'entrée ou la sortie d'une voiture, et un compteur. Le compteur est initialisé à 0 quand le parking est vide, puis est incrémenté à chaque entrée de voiture, décrémenté à chaque sortie. Les exemples de ce type sont suffisamment nombreux pour qu'on dédie un chapitre aux compteurs.
[[File:Binary counter.gif|vignette|Illustration du fonctionnement d'un compteur modulaire binaire de 4 bits, avec un pas de compteur de 1 (le contenu est augmenté de 1 à chaque mise à jour).]]
Un compteur mémorise un nombre qui est incrémenté ou décrémenté au besoin. Le nombre mémorisé sera appelé le '''décompte''' dans ce qui suit. Il est mémorisé dans un registre à l'intérieur du compteur. Au passage, le nombre de bits <math>n</math> du compteur est appelé la '''taille du compteur''', par analogie avec les registres. Il faut cependant faire la différence entre les ''compteurs'' d'un côté et les ''décompteurs'' de l'autre. Les compteurs incrémentent le décompte, les décompteurs le décrémentent, les compteurs-décompteurs peuvent faire les deux.
==L'interface d'un compteur/décompteur==
Les compteurs et décompteurs sont des circuits synchrones et ont donc une entrée d'horloge. Les compteurs les plus simples incrémentent leur contenu à chaque cycle d'horloge, mais les plus fréquents n'incrémentent le décompte que sur demande. Pour cela, ils disposent d'une entrée '''''Count Enable''''', similaire à l'entrée ''Enable'' des registres, séparée de l'entrée d'horloge. Le décompte est incrémenté/décrémenté seulement si l'entrée Enable est à 1, lors d'un front adéquat sur le signal d'horloge.
Les compteurs ont aussi une entrée '''''Reset''''' qui permet de les remettre à zéro. Il y a parfois une entrée qui permet d'initialiser le compteur à une valeur par défaut, non-nulle. Par exemple, on peut initialiser le décompte à la valeur 5, ou une autre. Pour cela, le compteur dispose de deux entrées : une entrée sur laquelle envoyer le décompte initial, une entrée pour autoriser la réinitialisation. Les entrées en question sont appelées '''''Preload Data''''' et '''''Preload Enable'''''. La seconde entrée est parfois distincte de l'entrée de réinitialisation, pour permettre de réinitialiser le compteur soit à zéro, soit à la valeur voulue.
Il peut être utile de prévenir quand un débordement d'entier a lieu, à savoir quand le compteur n'a pas assez de bits pour encoder le décompte. Le compteur est alors remis à zéro, dans la plupart des cas. Mais il faut prévenir que le compteur a débordé, ce qui est utile pour fabriquer des circuits diviseurs de fréquence et des ''timers''. Pour cela, on ajoute une '''sortie de débordement''' au compteur, qui est mise à 1 quand le compteur déborde.
Sur les compteurs/décompteurs, il y a une entrée '''''Count Direction''''' qui décide s'il faut compter ou décompter. Typiquement, elle est à 1 s'il faut compter et 0 s'il faut décompter.
[[File:Digital counter signals.jpg|centre|vignette|upright=2|Interface d'un compteur-décompteur.]]
Dans les schémas qui vont suivre, les entrées Enable ne sont pas représentées. Il est sous-entendu qu'il y a une entrée ''Enable'' pour tous les compteurs qui vont suivre. Il existe deux méthodes pour créer de tels compteurs : la première donne ce qu'on appelle des compteurs asynchrones, et l'autre des compteurs synchrones.
==L'intérieur d'un compteur==
A une exception bien précise qu'on abordera plus bas, les compteurs sont composés d'un registre, qui mémorise le décompte, couplé à un circuit '''incrémenteur'''. Nous avions déjà abordé l'incrémenteur dans un chapitre précédent, aussi je ne vais pas ré-expliquer comment il est conçu. Tout ce qu'il faut retenir est qu'il y en a plusieurs types, le plus simple étant celui à propagation de retenue.
[[File:Fonctionnement d'un compteur (décompteur), schématique.jpg|centre|vignette|upright=2|Fonctionnement d'un compteur (décompteur), schématique. Le pas du compteur précise de combien on incrémente le compteur par cycle d'horloge.]]
===Les compteurs synchrones basiques===
L'incrémenteur le plus simple, à propagation de retenue, est fabriqué en enchainant des ''demi-additionneurs'' les uns à la suite des autres. Pour rappel, un demi-additionneur additionne deux bits. Ici, il additionne un bit de l'opérande, la retenue des colonnes précédentes. Pour le bit de poids faible, la retenue est forcé à 1. Si on combine un incrémenteur à propagation de retenue avec un registre, on obtient ce compteur :
[[File:Compteur synchrone à incrémenteur.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules D.]]
Une simplification permet de faire disparaitre les portes XOR. Les portes XOR servent ici d'inverseur commandable, commandé par la retenue entrante. Elles inversent le contenu de la bascule quand la retenue entrante vaut 1, elles laissent la bascule inchangée si la retenue vaut 0. Or, nous avons déjà une bascule qui inverse son contenu sous certaines condition : la bascule T ! Il est donc possible de fusionner chaque bascule D avec la porte XOR associée, pour donner une bascule T. Le circuit final est celui-ci :
[[File:Compteur synchrone à bascules T.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules T.]]
Il est possible d'optimiser le circuit avec les optimisations vues dans le chapitre sur les incrémenteurs. La première est le ''carry skip'' qui fait l'incrémentation non pas bit par bit, mais par paquets de deux bits. La seconde est l'anticipation de retenue. Et ces deux optimisations se marient bien avec ce qui va suivre.
===Les compteurs en cascade===
[[File:8 Bit Counter.svg|vignette|Compteur 8 bits fabriqués avec deux compteurs 4 bits.]]
Il est possible de concevoir des compteurs à partir de compteurs plus petits, mis en cascade. Par exemple, en créant un compteur 8 bits à partir de deux compteurs 4 bits, enchainés l'un à la suite de l'autre. Idem avec 4 compteurs 4 bits, ce qui fait un compteur de 16 bits.
Les compteurs mis en cascade ont les mêmes entrées et sorties que les compteurs normaux, avec cependant un détail très important : ils ont une entrée pour la retenue entrante, et une sortie pour la retenue sortante. L'entrée pour la retenue entrante précise ce qui doit être additionné au bit de poids faible. Si elle vaut zéro, l'incrémenteur n'incrémente pas l'opérande. Si elle vaut 1, le compteur est incrémenté. L'entrée de retenue fait office d'entrée ''Count Enable'', qui active ou désactive l'incrémentation.
Le compteur a aussi une sortie de débordement, qui indique que le compteur déborde. Il se trouve que cette sortie fournit la retenue pour le compteur suivant. La retenue peut être calculée en utilisant des optimisations comme l'anticipation de retenue, ce qui veut dire qu'elle est calculée sans propager les retenues, directement à partir des bits de l'opérande. Concrètement, la retenue est calculée en faisant un ET logique entre tous les bits du décompte.
[[File:Cascadable binary up-counter.jpg|centre|vignette|upright=2|Cascadable binary up-counter]]
Les compteurs sont mis en cascade de la manière suivante : leur sortie de débordement est connectée sur l'entrée ''Enable'' du compteur suivant, celle qui déclenche l'incrémentation du compteur. La sortie de débordement est notée RCO dans les schéma qui suivent, nous verrons pourquoi dans le prochaine paragraphe.
[[File:Cascaded binary counters.jpg|centre|vignette|upright=2.5|Cascaded binary counters]]
===Les compteurs modulo===
La plupart des compteurs comptent de 0 à <math>2^n - 1</math>, avec <math>n</math> la taille du compteur. Mais d'autres compteurs ne comptent pas jusque-là. Par exemple, certains compteurs ne comptent que jusqu'à 10, 150, etc. Ils sont appelés des '''compteurs ''modulo'''''. Prenons un compteur modulo 6, par exemple : il compte de 0 à 5, et est remis immédiatement à zéro quand il atteint 6. Il compte donc comme suit : 0, 1, 2, 3, 4, 5, 0, 1, 2, ...
Les compteurs ''modulo'' sont construits à partir d'un compteur normal, couplé à un circuit comparateur qui remet à zéro le registre quand il atteint la valeur maximale. Par exemple, on peut imaginer un compteur modulo 6 est construit à partir d'un compteur 4 bits qui compte de 0 à 15 (donc un compteur modulo 16), mais qui est remis à zéro quand il atteint 6. Le circuit comparateur vérifie si la valeur maximale 6 est atteinte et met à 1 l'entrée ''Reset'' si c'est le cas. Le comparateur est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Td4bfig4.png|centre|vignette|upright=1.5|Compteur modulo N.]]
Pour une minorité de compteurs ''modulo'', la valeur maximale est configurable. Pour cela, le compteur est associé à un ''registre de configuration'' qui mémorise la valeur maximale souhaitée. A chaque cycle d'horloge, la valeur dans le compteur est comparée au registre de configuration. Si elles sont identiques, le compteur est remis à zéro. Le compteur est associé au registre de configuration et à un comparateur qui vérifie que les deux sont égaux. Pour le moment, nous ne savons pas faire de circuits comparateurs, ce qui fait qu'on ne peut pas expliquer ce circuit plus en détail.
[[File:4 Bit Counter Prog 1.svg|centre|vignette|upright=2|Compteur 4 bits à valeur maximale programmable.]]
Les compteurs ont tous une sortie de débordement, qui indique quand le compteur déborde. Pour les compteurs modulo, la sortie n'est autre que la sortie du comparateur. Pour les compteurs non-modulo, la sortie de débordement est une sortie du circuit combinatoire qui incrémente le compteur.
==Les ''timers'' : compter des durées, compter des cycles d'horloge==
Les '''''timers''''', aussi appelés ''Programmable interval timer'', sont des circuits capables de compter des durées, exprimées en cycles d'horloge. Leur fonctionnement est assez simple : ils émettent un signal quand un certain nombre de cycles est écoulé, ce nombre de cycles étant configurable. On peut ainsi générer un signal qui surviendra après 50 cycles d'horloge, ou après 100 cycles d'horloge, etc. Le signal en question est disponible sur une sortie de 1 bit, et correspond tout simplement au fait que cette sortie est mise à 1, pendant un cycle d'horloge.
Les ''timers'' sont composés d'un compteur/décompteur cadencé par un signal d'horloge. Le compteur initialisé à 0, puis est incrémenté à chaque signal d'horloge, jusqu’à atteinte d'une valeur limite où il génère un signal. Pour un décompteur, c'est la même chose, sauf que le décompteur est initialisé à sa valeur limite et est décrémenté à chaque cycle, et envoie un signal quand il atteint 0. Les ''timers'' basés sur des décompteurs sont nettement plus simples que les autres, ce qui fait qu'ils sont plus utilisés Pour que les ''timers'' soient configurables, on doit pouvoir préciser combien de cycles il faut (dé-)compter avant d'émettre un signal. On peut ainsi préciser s'il faut émettre le signal après 32 cycles d'horloge, après les 50 cycles, tous les 129 cycles, etc. Le nombre de cycles en question est envoyé sur une entrée d’initialisation du compteur.
Les ''timers'' matériels peuvent compter de deux manières différentes, appelées '''mode une fois''' et '''mode périodique'''.
* En mode une fois, le ''timer'' s'arrête une fois qu'il a atteint la limite configurée. On doit le réinitialiser manuellement, par l'intermédiaire du logiciel, pour l'utiliser une nouvelle fois. Cela permet de compter une certaine durée, exprimée en nombre de cycles d'horloge.
* En mode périodique, le ''timer'' se réinitialise automatiquement avec la valeur de départ, ce qui fait qu'il reboucle à l'infini.
Un ordinateur est rempli de ''timers'' divers. Dans ce qui va suivre, nous allons voir les principaux ''timers'', qui sont actuellement intégrés dans les PC modernes. Ils se trouvent sur la carte mère ou dans le processeur, tout dépend du ''timer''.
===Le ''watchdog timer''===
Le '''''watchdog timer''''' est un ''timer'' spécifique dont le but est de redémarrer automatiquement l'ordinateur si jamais celui-ci ne répond plus ou plante. Beaucoup de PC s'en passent, mais ce ''timer'' est très fréquent dans les architectures embarquées. Le ''watchdog timer'' est un compteur/décompteur qui doit être réinitialisé régulièrement. S'il n'est pas réinitialisé, le ''watchdog timer'' déborde (revient à 0 ou atteint 0) et envoie un signal qui redémarre le système. Le système est conçu pour réinitialiser le ''watchdog timer'' régulièrement, ce qui signifie que le système n'est pas censé redémarrer. Si jamais le système dysfonctionne gravement, le système ne pourra pas réinitialiser le ''watchdog timer'' et le système est redémarré automatiquement ou mis en arrêt.
[[File:SimpleWatchdogTimer.gif|centre|vignette|upright=2|Le ''Watchdog Timer'' et l'ordinateur.]]
===Le ''Time Stamp Counter'' des processeurs x86===
Tous les processeurs des PC actuels sont des processeurs dits x86. Nous ne pouvons pas expliquer ce que cela signifie pour le moment, retenez juste ce terme. Sachez que tous les processeurs x86 contiennent un compteur de 64 bits, appelé le '''''Time Stamp Counter''''', qui mémorise le nombre de cycles d'horloge qu'a effectué le processeur depuis son démarrage. Les programmes peuvent accéder à ce registre assez facilement, ce qui est utile pour faire des mesures ou comparer les performances de deux applications. Il permet de compter combien de cycles d'horloge met un morceau de code à s’exécuter, combien de cycles prend une instruction à s’exécuter, etc. Les processeurs non-x86 ont un registre équivalent, que ce soit les processeurs ARM ou d'autres.
Malheureusement, ce compteur est tombé en désuétude pour tout un tas de raisons. La principale est que les processeurs actuels font varier leur fréquence suivant les besoins. Ils augmentent leur fréquence quand on leur demande de faire beaucoup de calculs, et se mettent en mode basse(fréquence pour économiser de l'énergie si on ne leur demande pas grand chose. Avec une fréquence variable, le ''Time Stamp Counter'' perd complétement en fiabilité. Intel a tenté de corriger ce défaut en incrémentant ce registre à une fréquence constante, différente de celle du processeur, ce qui est encore le cas sur les processeurs Intel actuels. Le comportement est un peu différent sur les processeurs AMD, qui cadencent ce ''timer'' à la fréquence du processeur mais utilisent des mécanismes de synchronisation assez complexes pour corriger l'effet de la fréquence variable.
===L'horloge temps réel===
L''''horloge temps réel''' est un ''timer'' qui génère une fréquence de 1024 Hz, soit près d'un Kilohertz. Dans ce qui suit, nous la noterons RTC, ce qui est l'acronyme du terme anglais ''Real Time Clock''. La RTC prend en entrée un signal d'horloge de 32KHz, généré par un oscillateur à Quartz, et fournit en sortie un signal de fréquence 32 fois plus faible, c'est à dire de 1 KHz. Pour cela, elle est réglée en mode répétitif et son décompteur interne est initialisé à 32. La RTC génère donc un signal toutes les millisecondes, qui est envoyé au processeur. On peut, en théorie, changer la fréquence de la RTC, mais c'est rarement une bonne idée.
En théorie, la RTC permet de compter des durées assez courtes, comme le ''ping'' (le temps de latence d'un réseau, pour simplifier), le temps de rafraichissement de l'écran, ou bien d'autres choses. Mais dans les faits, l'horloge temps réel sa fréquence n'aide pas : 1024 Hz est proche de 1000, mais pas assez pour faire des mesures à la milliseconde près, chose qui est nécessaire pour mesurer le ''ping'' ou d'autres choses utiles. A la place, l'ordinateur l'utilise pour que l'ordinateur soit toujours à l'heure. L'ordinateur sait quelle heure il est avec une précision de l'ordre de la seconde (vous pouvez regarder le bureau de Windows dans le coin inférieur droite de votre écran pour vous en convaincre).
===Le ''Programmable Interval Timer'' : l'Intel 8253===
L'Intel 8253 était un ''timer'' programmable autrefois soudé sur les cartes mères des premiers PC. Il fût suivi par l'Intel 8254, qui en était une légère amélioration. Il était cadencé par une horloge maitre, générée par un oscillateur à Quartz, dont la fréquence est de 32 768 Hertz, soit 2^15 cycles d'horloge par seconde. S'il n'est plus présent dans un boitier sur la carte mère, on trouve toujours un circuit semblable au 8253 à l'intérieur du ''chipset'' de la carte mère, voire à l'intérieur du processeur, pour des raisons de compatibilité. L'intérieur de l'Intel 8253 est illustré ci-dessous. Nous allons expliquer l'ensemble de ce schéma, rassurez-vous, mais les explications seront plus simples à comprendre si vous survolez ce schéma en premier lieu.
[[File:Intel 8253 block diagram.svg|centre|vignette|upright=2|Intel 8253, intérieur.]]
L'Intel 8253 contient trois compteurs de 16 bits, numérotés 0, 1 et 2. Pour chaque compteur, l'entrée CLOCK est celle de l'horloge de 32 MHz, l'entrée GATE active ou désactive le compteur, la sortie fournit le signal voulu et/ou la fréquence de sortie. Les trois compteurs étaient utilisés pour dériver plusieurs fréquences allant de 18,2 Hz à environ 500 KHz. Par exemple, il était utilisé par défaut pour le rafraichissement de la mémoire (D)RAM, mais il était souvent reprogrammé pour servir à générer des fréquences spécifiques par le BIOS ou la carte graphique.
[[File:Intel 8253 and 8254.svg|vignette|Intel 8253 and 8254]]
L'Intel 8253 lui-même possède plusieurs entrées et sorties. En premier lieu, on voit un port de 8 bits connecté aux trois compteurs, qui permet à l'Intel 8253 de communiquer avec le reste de l'ordinateur. La communication se fait dans les deux sens : soit de l'ordinateur vers les compteurs, soit des compteurs vers l'ordinateur. Dans le sens ordinateur -> compteurs, cela permet à l'ordinateur de programmer les compteurs, de les initialiser. Dans l'autre sens, cela permet de récupérer le contenu des compteurs, même si ce n'est pas très utilisé. Il y a aussi 5 entrées de configuration :
* Deux bits A0 et A1 pour sélectionner le compteur voulu avec son numéro.
* Un bit RD à mettre à 0 pour que l'ordinateur récupère le compteur sélectionné sur le port de 8 bits.
* Un bit WR à mettre à 0 pour que l'ordinateur modifie le compteur sélectionné, en envoyant le nombre pour l'initialisation sur le port de 8 bits.
* Un bit CS qui active ou désactive l'Intel 8253 et permet de l'allumer ou de l’éteindre.
L'Intel 8253 intégre un registre de 8 bits, le ''Control Word register'' qui mémorise la configuration de l'Intel 8253. Pour programmer les trois compteurs, il faut écrire un mot de 8 bits dans ce ''Control Word register''. Pour écrire dans le ''Control Word register'', il faut mettre le bit CS à 0 (on active l'Intel 8253), mettre le bit RDà 1 , le bit WR à 0 le bit WR (on indique qu'on fait une écriture), sélectionner le ''Control Word register'' en mettant les deux bits A0 et A1 à 1, puis envoyer la configuration du ''Control Word register'' sur le port de 8 bits.
===Le ''High Precision Event Timer'' (HPET)===
De nos jours, l'horloge temps réel et l'Intel 8253/8254 tendent à être remplacé par un autre ''timer'', le ''High Precision Event Timer'' (HPET). Il s'agit d'un compteur de 64 bits, dont la fréquence est d'au moins 10 MHz. Il s'agit bien d'un compteur et non d'un décompteur. Il gère nativement plusieurs valeurs limites à laquelle générer un signal, qui sont configurables. Pour cela, il est couplé à plusieurs comparateurs, chacun associé à un registre pour mémoriser la valeur limite. Il doit y avoir au moins trois comparateurs/registres, mais le nombre peut monter jusqu’à 256.
[[File:High Precision Event Timer.png|centre|vignette|upright=2|High Precision Event Timer]]
Il faut noter que les systèmes d'exploitation conçus avant le HPET ne peuvent pas l'utiliser, pour des raisons de compatibilité matérielle. C'est le cas de Windows XP avant le Service Pack 3. C'est la raison pour laquelle les cartes mères émulent RTC et PIT dans leurs circuits. D'ailleurs, pour économiser des circuits, les cartes mères modernes émulent le PIT et la RTC avec le HPET : le premier comparateur fournisse la fréquence de 1024 Hz de la RTC, 3 autres comparateurs remplacent l'Intel 8253.
Le HPET gère de nombreux modes de fonctionnement : ses comparateurs peuvent être configuré en mode une fois ou périodique, on peut lui demander d'émuler la RTC et le PIT, etc. Chaque comparateur doit pouvoir fonctionner en mode une fois, et au moins un comparateur doit pouvoir fonctionner en mode périodique. Aussi, il contient aussi 3 registres de configuration. Notons qu'il est aussi possible de lire ou écrire dans le compteur de 64 bits, mais ce n'est pas recommandé.
==Les compteurs en code Gray==
Les compteurs classiques encodent leur décompte en binaire normal sur <math>n</math> bits, mais il faut savoir que d'autres compteurs utilisent le BCD, d'autre le code Gray, etc. Nous allons voir dans ce qui suit ceux qui comptent en code Gray.
Pour rappel, le code Gray permet de coder des nombres d'une manière un peu différente du binaire normal. Son avantage principal est que lorsqu'on incrémente ou décrémente un nombre, seul un bit change ! Pour comparer, en binaire normal, lorsqu'on passe incrémente un nombre, il peut y avoir plusieurs bits qui changent. La moyenne est d'environ deux bits par incrémentation. Les compteurs en code Gray ont beaucoup d'avantages, qui sont tous liés à cette propriété.
Le premier l'absence d'état transitoires douteux. Le problème est que les bits modifiés par une incrémentation ne le sont pas en même temps. Les bits de poids faibles sont modifiés avant les autres. Évidemment, à la fin du calcul, on obtient le résultat final, correct. Mais pendant le temps de calcul, le compteur peut se retrouver dans un état transitoire, où seuls les bits de poids faibles ont été modifiés. C'est un problème si le contenu de ce compteur est relié à des circuits rapides, qui peuvent voir cet état transitoire, mais ne le doivent pas sous peine de dysfonctionner. L'usage de compteurs en code Gray permet d'éviter ce problème : vu que seul un bit est modifié lors d'une incrémentation/décrémentation, les états transitoires n'existent tout simplement pas.
Un exemple typique, évoqué dans les chapitres précédents, est l'échange d'informations entre deux domaines d'horloge. Pour rappel, il arrive que deux portions d'un circuit imprimé aillent à des fréquences différences : on dit que le circuit à plusieurs domaines d'horloge. S'il faut échanger des informations entre ces deux domaines d'horloge, divers problèmes surviennent. Un domaine d'horloge sera plus rapide que l'autre, et pourra voir les états transitoires invisible de l'autre circuit. Et par voir, on veut dire qu'il les prendra pour des états valides, ce qui fera dysfonctionner le circuit. Pour éviter cela, diverses techniques de croisement de domaines d'horloge existent. Et les compteurs Gray en font partie : si un domaine d'horloge utilise la valeur d'un compteur de l'autre, mieux vaut que ce compteur soit un compteur Gray. Et cette situation est assez fréquente !
Un autre avantage mineurs est que la consommation d'énergie de ces compteurs est bien plus réduite qu'avec un compteur normal. Rappelons que pour fonctionner, les circuits électroniques consomment un peu d'électricité. Et la majeure partie de cette consommation sert à faire passer un bit de 0 à 1 ou de 1 à 0. Ce qui fait que quand un compteur est incrémenté ou décrémenté, cela consomme un peu d'énergie électrique.
La moyenne pour un compteur binaire normal est de 2 bits changés par incrémentation/décrémentation, contre un seul pour un compteur Gray, on devine que ces derniers consomment deux fois moins d'énergie par incrémentation. Et cet avantage a des effets en cascade sur les circuits qui suivent ce compteur. Si l'entrée de ces circuits ne change que d'un seul bit, alors leur état changera moins que si c'était deux bits. Les circuits qui suivent vont donc moins consommer.
Un autre avantage en matière de consommation énergétique est lié auxs transitions d'état douteuses. Les circuits connectés au compteur vont voir ces transitions d'état douteuses et modifier leur état interne en réaction. Bien sur, l'état final correct sera atteint une fois que le compteur sera stabilisé, ce qui effacera ces états transitoires intermédiaires. Mais chaque état intermédiaire transitoire correspond à un changement d'état, donc à une consommation d'énergie. En supprimant ces états transitoires, on réduit fortement la consommation d'énergie du circuit. Cela vaut pour le compteur Gray lui-même, mais aussi sur tous les circuits qui ont ce compteur comme entrée !
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les registres et mémoires adressables
| prevText=Les registres et mémoires adressables
| next=Les registres à décalage et les LSFR
| nextText=Les registres à décalage et les LSFR
}}
</noinclude>
pi1cqbcif5upnqpj9un3ye501ias8ta
768640
768639
2026-06-25T16:51:18Z
Mewtow
31375
/* Les timers : compter des durées, compter des cycles d'horloge */
768640
wikitext
text/x-wiki
Les '''compteurs/décompteurs''' sont des circuits électroniques qui mémorisent un nombre et l'incrémentent à la demande. En clair, ce sont des registres améliorés afin de supporter l'incrémentation et la décrémentation. Pour donner un exemple d'utilisation, imaginez un circuit qui compte le nombre de voitures dans un parking dans la journée. Pour cela, vous allez prendre deux circuits qui détectent respectivement l'entrée ou la sortie d'une voiture, et un compteur. Le compteur est initialisé à 0 quand le parking est vide, puis est incrémenté à chaque entrée de voiture, décrémenté à chaque sortie. Les exemples de ce type sont suffisamment nombreux pour qu'on dédie un chapitre aux compteurs.
[[File:Binary counter.gif|vignette|Illustration du fonctionnement d'un compteur modulaire binaire de 4 bits, avec un pas de compteur de 1 (le contenu est augmenté de 1 à chaque mise à jour).]]
Un compteur mémorise un nombre qui est incrémenté ou décrémenté au besoin. Le nombre mémorisé sera appelé le '''décompte''' dans ce qui suit. Il est mémorisé dans un registre à l'intérieur du compteur. Au passage, le nombre de bits <math>n</math> du compteur est appelé la '''taille du compteur''', par analogie avec les registres. Il faut cependant faire la différence entre les ''compteurs'' d'un côté et les ''décompteurs'' de l'autre. Les compteurs incrémentent le décompte, les décompteurs le décrémentent, les compteurs-décompteurs peuvent faire les deux.
==L'interface d'un compteur/décompteur==
Les compteurs et décompteurs sont des circuits synchrones et ont donc une entrée d'horloge. Les compteurs les plus simples incrémentent leur contenu à chaque cycle d'horloge, mais les plus fréquents n'incrémentent le décompte que sur demande. Pour cela, ils disposent d'une entrée '''''Count Enable''''', similaire à l'entrée ''Enable'' des registres, séparée de l'entrée d'horloge. Le décompte est incrémenté/décrémenté seulement si l'entrée Enable est à 1, lors d'un front adéquat sur le signal d'horloge.
Les compteurs ont aussi une entrée '''''Reset''''' qui permet de les remettre à zéro. Il y a parfois une entrée qui permet d'initialiser le compteur à une valeur par défaut, non-nulle. Par exemple, on peut initialiser le décompte à la valeur 5, ou une autre. Pour cela, le compteur dispose de deux entrées : une entrée sur laquelle envoyer le décompte initial, une entrée pour autoriser la réinitialisation. Les entrées en question sont appelées '''''Preload Data''''' et '''''Preload Enable'''''. La seconde entrée est parfois distincte de l'entrée de réinitialisation, pour permettre de réinitialiser le compteur soit à zéro, soit à la valeur voulue.
Il peut être utile de prévenir quand un débordement d'entier a lieu, à savoir quand le compteur n'a pas assez de bits pour encoder le décompte. Le compteur est alors remis à zéro, dans la plupart des cas. Mais il faut prévenir que le compteur a débordé, ce qui est utile pour fabriquer des circuits diviseurs de fréquence et des ''timers''. Pour cela, on ajoute une '''sortie de débordement''' au compteur, qui est mise à 1 quand le compteur déborde.
Sur les compteurs/décompteurs, il y a une entrée '''''Count Direction''''' qui décide s'il faut compter ou décompter. Typiquement, elle est à 1 s'il faut compter et 0 s'il faut décompter.
[[File:Digital counter signals.jpg|centre|vignette|upright=2|Interface d'un compteur-décompteur.]]
Dans les schémas qui vont suivre, les entrées Enable ne sont pas représentées. Il est sous-entendu qu'il y a une entrée ''Enable'' pour tous les compteurs qui vont suivre. Il existe deux méthodes pour créer de tels compteurs : la première donne ce qu'on appelle des compteurs asynchrones, et l'autre des compteurs synchrones.
==L'intérieur d'un compteur==
A une exception bien précise qu'on abordera plus bas, les compteurs sont composés d'un registre, qui mémorise le décompte, couplé à un circuit '''incrémenteur'''. Nous avions déjà abordé l'incrémenteur dans un chapitre précédent, aussi je ne vais pas ré-expliquer comment il est conçu. Tout ce qu'il faut retenir est qu'il y en a plusieurs types, le plus simple étant celui à propagation de retenue.
[[File:Fonctionnement d'un compteur (décompteur), schématique.jpg|centre|vignette|upright=2|Fonctionnement d'un compteur (décompteur), schématique. Le pas du compteur précise de combien on incrémente le compteur par cycle d'horloge.]]
===Les compteurs synchrones basiques===
L'incrémenteur le plus simple, à propagation de retenue, est fabriqué en enchainant des ''demi-additionneurs'' les uns à la suite des autres. Pour rappel, un demi-additionneur additionne deux bits. Ici, il additionne un bit de l'opérande, la retenue des colonnes précédentes. Pour le bit de poids faible, la retenue est forcé à 1. Si on combine un incrémenteur à propagation de retenue avec un registre, on obtient ce compteur :
[[File:Compteur synchrone à incrémenteur.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules D.]]
Une simplification permet de faire disparaitre les portes XOR. Les portes XOR servent ici d'inverseur commandable, commandé par la retenue entrante. Elles inversent le contenu de la bascule quand la retenue entrante vaut 1, elles laissent la bascule inchangée si la retenue vaut 0. Or, nous avons déjà une bascule qui inverse son contenu sous certaines condition : la bascule T ! Il est donc possible de fusionner chaque bascule D avec la porte XOR associée, pour donner une bascule T. Le circuit final est celui-ci :
[[File:Compteur synchrone à bascules T.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules T.]]
Il est possible d'optimiser le circuit avec les optimisations vues dans le chapitre sur les incrémenteurs. La première est le ''carry skip'' qui fait l'incrémentation non pas bit par bit, mais par paquets de deux bits. La seconde est l'anticipation de retenue. Et ces deux optimisations se marient bien avec ce qui va suivre.
===Les compteurs en cascade===
[[File:8 Bit Counter.svg|vignette|Compteur 8 bits fabriqués avec deux compteurs 4 bits.]]
Il est possible de concevoir des compteurs à partir de compteurs plus petits, mis en cascade. Par exemple, en créant un compteur 8 bits à partir de deux compteurs 4 bits, enchainés l'un à la suite de l'autre. Idem avec 4 compteurs 4 bits, ce qui fait un compteur de 16 bits.
Les compteurs mis en cascade ont les mêmes entrées et sorties que les compteurs normaux, avec cependant un détail très important : ils ont une entrée pour la retenue entrante, et une sortie pour la retenue sortante. L'entrée pour la retenue entrante précise ce qui doit être additionné au bit de poids faible. Si elle vaut zéro, l'incrémenteur n'incrémente pas l'opérande. Si elle vaut 1, le compteur est incrémenté. L'entrée de retenue fait office d'entrée ''Count Enable'', qui active ou désactive l'incrémentation.
Le compteur a aussi une sortie de débordement, qui indique que le compteur déborde. Il se trouve que cette sortie fournit la retenue pour le compteur suivant. La retenue peut être calculée en utilisant des optimisations comme l'anticipation de retenue, ce qui veut dire qu'elle est calculée sans propager les retenues, directement à partir des bits de l'opérande. Concrètement, la retenue est calculée en faisant un ET logique entre tous les bits du décompte.
[[File:Cascadable binary up-counter.jpg|centre|vignette|upright=2|Cascadable binary up-counter]]
Les compteurs sont mis en cascade de la manière suivante : leur sortie de débordement est connectée sur l'entrée ''Enable'' du compteur suivant, celle qui déclenche l'incrémentation du compteur. La sortie de débordement est notée RCO dans les schéma qui suivent, nous verrons pourquoi dans le prochaine paragraphe.
[[File:Cascaded binary counters.jpg|centre|vignette|upright=2.5|Cascaded binary counters]]
===Les compteurs modulo===
La plupart des compteurs comptent de 0 à <math>2^n - 1</math>, avec <math>n</math> la taille du compteur. Mais d'autres compteurs ne comptent pas jusque-là. Par exemple, certains compteurs ne comptent que jusqu'à 10, 150, etc. Ils sont appelés des '''compteurs ''modulo'''''. Prenons un compteur modulo 6, par exemple : il compte de 0 à 5, et est remis immédiatement à zéro quand il atteint 6. Il compte donc comme suit : 0, 1, 2, 3, 4, 5, 0, 1, 2, ...
Les compteurs ''modulo'' sont construits à partir d'un compteur normal, couplé à un circuit comparateur qui remet à zéro le registre quand il atteint la valeur maximale. Par exemple, on peut imaginer un compteur modulo 6 est construit à partir d'un compteur 4 bits qui compte de 0 à 15 (donc un compteur modulo 16), mais qui est remis à zéro quand il atteint 6. Le circuit comparateur vérifie si la valeur maximale 6 est atteinte et met à 1 l'entrée ''Reset'' si c'est le cas. Le comparateur est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Td4bfig4.png|centre|vignette|upright=1.5|Compteur modulo N.]]
Pour une minorité de compteurs ''modulo'', la valeur maximale est configurable. Pour cela, le compteur est associé à un ''registre de configuration'' qui mémorise la valeur maximale souhaitée. A chaque cycle d'horloge, la valeur dans le compteur est comparée au registre de configuration. Si elles sont identiques, le compteur est remis à zéro. Le compteur est associé au registre de configuration et à un comparateur qui vérifie que les deux sont égaux. Pour le moment, nous ne savons pas faire de circuits comparateurs, ce qui fait qu'on ne peut pas expliquer ce circuit plus en détail.
[[File:4 Bit Counter Prog 1.svg|centre|vignette|upright=2|Compteur 4 bits à valeur maximale programmable.]]
Les compteurs ont tous une sortie de débordement, qui indique quand le compteur déborde. Pour les compteurs modulo, la sortie n'est autre que la sortie du comparateur. Pour les compteurs non-modulo, la sortie de débordement est une sortie du circuit combinatoire qui incrémente le compteur.
==Les ''timers'' : compter des durées, compter des cycles d'horloge==
Les '''''timers''''', aussi appelés ''Programmable interval timer'', sont des circuits capables de compter des durées, exprimées en cycles d'horloge. Leur fonctionnement est assez simple : ils émettent un signal quand un certain nombre de cycles est écoulé, ce nombre de cycles étant configurable. On peut ainsi générer un signal qui surviendra après 50 cycles d'horloge, ou après 100 cycles d'horloge, etc. Le signal en question est disponible sur une sortie de 1 bit, et correspond tout simplement au fait que cette sortie est mise à 1, pendant un cycle d'horloge.
Les ''timers'' sont composés d'un compteur/décompteur cadencé par un signal d'horloge. Avec un compteur, le compteur est initialisé à 0, puis est incrémenté à chaque signal d'horloge, jusqu’à atteindre la valeur limite où il génère un signal. Pour un décompteur, c'est la même chose, sauf que le décompteur est initialisé à sa valeur limite, est décrémenté à chaque cycle, et envoie un signal quand il atteint 0. Les ''timers'' basés sur des décompteurs sont nettement plus simples que les autres, ce qui fait qu'ils sont plus utilisés.
Pour que les ''timers'' soient configurables, on doit pouvoir préciser combien de cycles il faut (dé-)compter avant d'émettre un signal. On peut ainsi préciser s'il faut émettre le signal après 32 cycles d'horloge, après les 50 cycles, tous les 129 cycles, etc. Le nombre de cycles en question est envoyé sur une entrée d’initialisation du compteur. Il peut être mémorisé dans un '''registre d’intervalle''' dédié.
[[File:Programmable interval timer.jpg|centre|vignette|upright=2|''Timer'' basé sur un décompteur. La porte NOR détecte si le décompteur atteint zéro.]]
Les ''timers'' matériels peuvent compter de deux manières différentes, appelées '''mode une fois''' et '''mode périodique'''.
* En mode une fois, le ''timer'' s'arrête une fois qu'il a atteint la limite configurée. On doit le réinitialiser manuellement, par l'intermédiaire du logiciel, pour l'utiliser une nouvelle fois. Cela permet de compter une certaine durée, exprimée en nombre de cycles d'horloge.
* En mode périodique, le ''timer'' se réinitialise automatiquement avec la valeur de départ, ce qui fait qu'il reboucle à l'infini.
Un ordinateur est rempli de ''timers'' divers. Dans ce qui va suivre, nous allons voir les principaux ''timers'', qui sont actuellement intégrés dans les PC modernes. Ils se trouvent sur la carte mère ou dans le processeur, tout dépend du ''timer''.
===Le ''watchdog timer''===
Le '''''watchdog timer''''' est un ''timer'' spécifique dont le but est de redémarrer automatiquement l'ordinateur si jamais celui-ci ne répond plus ou plante. Beaucoup de PC s'en passent, mais ce ''timer'' est très fréquent dans les architectures embarquées. Le ''watchdog timer'' est un compteur/décompteur qui doit être réinitialisé régulièrement. S'il n'est pas réinitialisé, le ''watchdog timer'' déborde (revient à 0 ou atteint 0) et envoie un signal qui redémarre le système. Le système est conçu pour réinitialiser le ''watchdog timer'' régulièrement, ce qui signifie que le système n'est pas censé redémarrer. Si jamais le système dysfonctionne gravement, le système ne pourra pas réinitialiser le ''watchdog timer'' et le système est redémarré automatiquement ou mis en arrêt.
[[File:SimpleWatchdogTimer.gif|centre|vignette|upright=2|Le ''Watchdog Timer'' et l'ordinateur.]]
===Le ''Time Stamp Counter'' des processeurs x86===
Tous les processeurs des PC actuels sont des processeurs dits x86. Nous ne pouvons pas expliquer ce que cela signifie pour le moment, retenez juste ce terme. Sachez que tous les processeurs x86 contiennent un compteur de 64 bits, appelé le '''''Time Stamp Counter''''', qui mémorise le nombre de cycles d'horloge qu'a effectué le processeur depuis son démarrage. Les programmes peuvent accéder à ce registre assez facilement, ce qui est utile pour faire des mesures ou comparer les performances de deux applications. Il permet de compter combien de cycles d'horloge met un morceau de code à s’exécuter, combien de cycles prend une instruction à s’exécuter, etc. Les processeurs non-x86 ont un registre équivalent, que ce soit les processeurs ARM ou d'autres.
Malheureusement, ce compteur est tombé en désuétude pour tout un tas de raisons. La principale est que les processeurs actuels font varier leur fréquence suivant les besoins. Ils augmentent leur fréquence quand on leur demande de faire beaucoup de calculs, et se mettent en mode basse(fréquence pour économiser de l'énergie si on ne leur demande pas grand chose. Avec une fréquence variable, le ''Time Stamp Counter'' perd complétement en fiabilité. Intel a tenté de corriger ce défaut en incrémentant ce registre à une fréquence constante, différente de celle du processeur, ce qui est encore le cas sur les processeurs Intel actuels. Le comportement est un peu différent sur les processeurs AMD, qui cadencent ce ''timer'' à la fréquence du processeur mais utilisent des mécanismes de synchronisation assez complexes pour corriger l'effet de la fréquence variable.
===L'horloge temps réel===
L''''horloge temps réel''' est un ''timer'' qui génère une fréquence de 1024 Hz, soit près d'un Kilohertz. Dans ce qui suit, nous la noterons RTC, ce qui est l'acronyme du terme anglais ''Real Time Clock''. La RTC prend en entrée un signal d'horloge de 32KHz, généré par un oscillateur à Quartz, et fournit en sortie un signal de fréquence 32 fois plus faible, c'est à dire de 1 KHz. Pour cela, elle est réglée en mode répétitif et son décompteur interne est initialisé à 32. La RTC génère donc un signal toutes les millisecondes, qui est envoyé au processeur. On peut, en théorie, changer la fréquence de la RTC, mais c'est rarement une bonne idée.
En théorie, la RTC permet de compter des durées assez courtes, comme le ''ping'' (le temps de latence d'un réseau, pour simplifier), le temps de rafraichissement de l'écran, ou bien d'autres choses. Mais dans les faits, l'horloge temps réel sa fréquence n'aide pas : 1024 Hz est proche de 1000, mais pas assez pour faire des mesures à la milliseconde près, chose qui est nécessaire pour mesurer le ''ping'' ou d'autres choses utiles. A la place, l'ordinateur l'utilise pour que l'ordinateur soit toujours à l'heure. L'ordinateur sait quelle heure il est avec une précision de l'ordre de la seconde (vous pouvez regarder le bureau de Windows dans le coin inférieur droite de votre écran pour vous en convaincre).
===Le ''Programmable Interval Timer'' : l'Intel 8253===
L'Intel 8253 était un ''timer'' programmable autrefois soudé sur les cartes mères des premiers PC. Il fût suivi par l'Intel 8254, qui en était une légère amélioration. Il était cadencé par une horloge maitre, générée par un oscillateur à Quartz, dont la fréquence est de 32 768 Hertz, soit 2^15 cycles d'horloge par seconde. S'il n'est plus présent dans un boitier sur la carte mère, on trouve toujours un circuit semblable au 8253 à l'intérieur du ''chipset'' de la carte mère, voire à l'intérieur du processeur, pour des raisons de compatibilité. L'intérieur de l'Intel 8253 est illustré ci-dessous. Nous allons expliquer l'ensemble de ce schéma, rassurez-vous, mais les explications seront plus simples à comprendre si vous survolez ce schéma en premier lieu.
[[File:Intel 8253 block diagram.svg|centre|vignette|upright=2|Intel 8253, intérieur.]]
L'Intel 8253 contient trois compteurs de 16 bits, numérotés 0, 1 et 2. Pour chaque compteur, l'entrée CLOCK est celle de l'horloge de 32 MHz, l'entrée GATE active ou désactive le compteur, la sortie fournit le signal voulu et/ou la fréquence de sortie. Les trois compteurs étaient utilisés pour dériver plusieurs fréquences allant de 18,2 Hz à environ 500 KHz. Par exemple, il était utilisé par défaut pour le rafraichissement de la mémoire (D)RAM, mais il était souvent reprogrammé pour servir à générer des fréquences spécifiques par le BIOS ou la carte graphique.
[[File:Intel 8253 and 8254.svg|vignette|Intel 8253 and 8254]]
L'Intel 8253 lui-même possède plusieurs entrées et sorties. En premier lieu, on voit un port de 8 bits connecté aux trois compteurs, qui permet à l'Intel 8253 de communiquer avec le reste de l'ordinateur. La communication se fait dans les deux sens : soit de l'ordinateur vers les compteurs, soit des compteurs vers l'ordinateur. Dans le sens ordinateur -> compteurs, cela permet à l'ordinateur de programmer les compteurs, de les initialiser. Dans l'autre sens, cela permet de récupérer le contenu des compteurs, même si ce n'est pas très utilisé. Il y a aussi 5 entrées de configuration :
* Deux bits A0 et A1 pour sélectionner le compteur voulu avec son numéro.
* Un bit RD à mettre à 0 pour que l'ordinateur récupère le compteur sélectionné sur le port de 8 bits.
* Un bit WR à mettre à 0 pour que l'ordinateur modifie le compteur sélectionné, en envoyant le nombre pour l'initialisation sur le port de 8 bits.
* Un bit CS qui active ou désactive l'Intel 8253 et permet de l'allumer ou de l’éteindre.
L'Intel 8253 intégre un registre de 8 bits, le ''Control Word register'' qui mémorise la configuration de l'Intel 8253. Pour programmer les trois compteurs, il faut écrire un mot de 8 bits dans ce ''Control Word register''. Pour écrire dans le ''Control Word register'', il faut mettre le bit CS à 0 (on active l'Intel 8253), mettre le bit RDà 1 , le bit WR à 0 le bit WR (on indique qu'on fait une écriture), sélectionner le ''Control Word register'' en mettant les deux bits A0 et A1 à 1, puis envoyer la configuration du ''Control Word register'' sur le port de 8 bits.
===Le ''High Precision Event Timer'' (HPET)===
De nos jours, l'horloge temps réel et l'Intel 8253/8254 tendent à être remplacé par un autre ''timer'', le ''High Precision Event Timer'' (HPET). Il s'agit d'un compteur de 64 bits, dont la fréquence est d'au moins 10 MHz. Il s'agit bien d'un compteur et non d'un décompteur. Il gère nativement plusieurs valeurs limites à laquelle générer un signal, qui sont configurables. Pour cela, il est couplé à plusieurs comparateurs, chacun associé à un registre pour mémoriser la valeur limite. Il doit y avoir au moins trois comparateurs/registres, mais le nombre peut monter jusqu’à 256.
[[File:High Precision Event Timer.png|centre|vignette|upright=2|High Precision Event Timer]]
Il faut noter que les systèmes d'exploitation conçus avant le HPET ne peuvent pas l'utiliser, pour des raisons de compatibilité matérielle. C'est le cas de Windows XP avant le Service Pack 3. C'est la raison pour laquelle les cartes mères émulent RTC et PIT dans leurs circuits. D'ailleurs, pour économiser des circuits, les cartes mères modernes émulent le PIT et la RTC avec le HPET : le premier comparateur fournisse la fréquence de 1024 Hz de la RTC, 3 autres comparateurs remplacent l'Intel 8253.
Le HPET gère de nombreux modes de fonctionnement : ses comparateurs peuvent être configuré en mode une fois ou périodique, on peut lui demander d'émuler la RTC et le PIT, etc. Chaque comparateur doit pouvoir fonctionner en mode une fois, et au moins un comparateur doit pouvoir fonctionner en mode périodique. Aussi, il contient aussi 3 registres de configuration. Notons qu'il est aussi possible de lire ou écrire dans le compteur de 64 bits, mais ce n'est pas recommandé.
==Les compteurs en code Gray==
Les compteurs classiques encodent leur décompte en binaire normal sur <math>n</math> bits, mais il faut savoir que d'autres compteurs utilisent le BCD, d'autre le code Gray, etc. Nous allons voir dans ce qui suit ceux qui comptent en code Gray.
Pour rappel, le code Gray permet de coder des nombres d'une manière un peu différente du binaire normal. Son avantage principal est que lorsqu'on incrémente ou décrémente un nombre, seul un bit change ! Pour comparer, en binaire normal, lorsqu'on passe incrémente un nombre, il peut y avoir plusieurs bits qui changent. La moyenne est d'environ deux bits par incrémentation. Les compteurs en code Gray ont beaucoup d'avantages, qui sont tous liés à cette propriété.
Le premier l'absence d'état transitoires douteux. Le problème est que les bits modifiés par une incrémentation ne le sont pas en même temps. Les bits de poids faibles sont modifiés avant les autres. Évidemment, à la fin du calcul, on obtient le résultat final, correct. Mais pendant le temps de calcul, le compteur peut se retrouver dans un état transitoire, où seuls les bits de poids faibles ont été modifiés. C'est un problème si le contenu de ce compteur est relié à des circuits rapides, qui peuvent voir cet état transitoire, mais ne le doivent pas sous peine de dysfonctionner. L'usage de compteurs en code Gray permet d'éviter ce problème : vu que seul un bit est modifié lors d'une incrémentation/décrémentation, les états transitoires n'existent tout simplement pas.
Un exemple typique, évoqué dans les chapitres précédents, est l'échange d'informations entre deux domaines d'horloge. Pour rappel, il arrive que deux portions d'un circuit imprimé aillent à des fréquences différences : on dit que le circuit à plusieurs domaines d'horloge. S'il faut échanger des informations entre ces deux domaines d'horloge, divers problèmes surviennent. Un domaine d'horloge sera plus rapide que l'autre, et pourra voir les états transitoires invisible de l'autre circuit. Et par voir, on veut dire qu'il les prendra pour des états valides, ce qui fera dysfonctionner le circuit. Pour éviter cela, diverses techniques de croisement de domaines d'horloge existent. Et les compteurs Gray en font partie : si un domaine d'horloge utilise la valeur d'un compteur de l'autre, mieux vaut que ce compteur soit un compteur Gray. Et cette situation est assez fréquente !
Un autre avantage mineurs est que la consommation d'énergie de ces compteurs est bien plus réduite qu'avec un compteur normal. Rappelons que pour fonctionner, les circuits électroniques consomment un peu d'électricité. Et la majeure partie de cette consommation sert à faire passer un bit de 0 à 1 ou de 1 à 0. Ce qui fait que quand un compteur est incrémenté ou décrémenté, cela consomme un peu d'énergie électrique.
La moyenne pour un compteur binaire normal est de 2 bits changés par incrémentation/décrémentation, contre un seul pour un compteur Gray, on devine que ces derniers consomment deux fois moins d'énergie par incrémentation. Et cet avantage a des effets en cascade sur les circuits qui suivent ce compteur. Si l'entrée de ces circuits ne change que d'un seul bit, alors leur état changera moins que si c'était deux bits. Les circuits qui suivent vont donc moins consommer.
Un autre avantage en matière de consommation énergétique est lié auxs transitions d'état douteuses. Les circuits connectés au compteur vont voir ces transitions d'état douteuses et modifier leur état interne en réaction. Bien sur, l'état final correct sera atteint une fois que le compteur sera stabilisé, ce qui effacera ces états transitoires intermédiaires. Mais chaque état intermédiaire transitoire correspond à un changement d'état, donc à une consommation d'énergie. En supprimant ces états transitoires, on réduit fortement la consommation d'énergie du circuit. Cela vaut pour le compteur Gray lui-même, mais aussi sur tous les circuits qui ont ce compteur comme entrée !
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les registres et mémoires adressables
| prevText=Les registres et mémoires adressables
| next=Les registres à décalage et les LSFR
| nextText=Les registres à décalage et les LSFR
}}
</noinclude>
rubhwrwbvbd7upr4at6t22q393ngtf8
768641
768640
2026-06-25T16:52:34Z
Mewtow
31375
/* Les timers : compter des durées, compter des cycles d'horloge */
768641
wikitext
text/x-wiki
Les '''compteurs/décompteurs''' sont des circuits électroniques qui mémorisent un nombre et l'incrémentent à la demande. En clair, ce sont des registres améliorés afin de supporter l'incrémentation et la décrémentation. Pour donner un exemple d'utilisation, imaginez un circuit qui compte le nombre de voitures dans un parking dans la journée. Pour cela, vous allez prendre deux circuits qui détectent respectivement l'entrée ou la sortie d'une voiture, et un compteur. Le compteur est initialisé à 0 quand le parking est vide, puis est incrémenté à chaque entrée de voiture, décrémenté à chaque sortie. Les exemples de ce type sont suffisamment nombreux pour qu'on dédie un chapitre aux compteurs.
[[File:Binary counter.gif|vignette|Illustration du fonctionnement d'un compteur modulaire binaire de 4 bits, avec un pas de compteur de 1 (le contenu est augmenté de 1 à chaque mise à jour).]]
Un compteur mémorise un nombre qui est incrémenté ou décrémenté au besoin. Le nombre mémorisé sera appelé le '''décompte''' dans ce qui suit. Il est mémorisé dans un registre à l'intérieur du compteur. Au passage, le nombre de bits <math>n</math> du compteur est appelé la '''taille du compteur''', par analogie avec les registres. Il faut cependant faire la différence entre les ''compteurs'' d'un côté et les ''décompteurs'' de l'autre. Les compteurs incrémentent le décompte, les décompteurs le décrémentent, les compteurs-décompteurs peuvent faire les deux.
==L'interface d'un compteur/décompteur==
Les compteurs et décompteurs sont des circuits synchrones et ont donc une entrée d'horloge. Les compteurs les plus simples incrémentent leur contenu à chaque cycle d'horloge, mais les plus fréquents n'incrémentent le décompte que sur demande. Pour cela, ils disposent d'une entrée '''''Count Enable''''', similaire à l'entrée ''Enable'' des registres, séparée de l'entrée d'horloge. Le décompte est incrémenté/décrémenté seulement si l'entrée Enable est à 1, lors d'un front adéquat sur le signal d'horloge.
Les compteurs ont aussi une entrée '''''Reset''''' qui permet de les remettre à zéro. Il y a parfois une entrée qui permet d'initialiser le compteur à une valeur par défaut, non-nulle. Par exemple, on peut initialiser le décompte à la valeur 5, ou une autre. Pour cela, le compteur dispose de deux entrées : une entrée sur laquelle envoyer le décompte initial, une entrée pour autoriser la réinitialisation. Les entrées en question sont appelées '''''Preload Data''''' et '''''Preload Enable'''''. La seconde entrée est parfois distincte de l'entrée de réinitialisation, pour permettre de réinitialiser le compteur soit à zéro, soit à la valeur voulue.
Il peut être utile de prévenir quand un débordement d'entier a lieu, à savoir quand le compteur n'a pas assez de bits pour encoder le décompte. Le compteur est alors remis à zéro, dans la plupart des cas. Mais il faut prévenir que le compteur a débordé, ce qui est utile pour fabriquer des circuits diviseurs de fréquence et des ''timers''. Pour cela, on ajoute une '''sortie de débordement''' au compteur, qui est mise à 1 quand le compteur déborde.
Sur les compteurs/décompteurs, il y a une entrée '''''Count Direction''''' qui décide s'il faut compter ou décompter. Typiquement, elle est à 1 s'il faut compter et 0 s'il faut décompter.
[[File:Digital counter signals.jpg|centre|vignette|upright=2|Interface d'un compteur-décompteur.]]
Dans les schémas qui vont suivre, les entrées Enable ne sont pas représentées. Il est sous-entendu qu'il y a une entrée ''Enable'' pour tous les compteurs qui vont suivre. Il existe deux méthodes pour créer de tels compteurs : la première donne ce qu'on appelle des compteurs asynchrones, et l'autre des compteurs synchrones.
==L'intérieur d'un compteur==
A une exception bien précise qu'on abordera plus bas, les compteurs sont composés d'un registre, qui mémorise le décompte, couplé à un circuit '''incrémenteur'''. Nous avions déjà abordé l'incrémenteur dans un chapitre précédent, aussi je ne vais pas ré-expliquer comment il est conçu. Tout ce qu'il faut retenir est qu'il y en a plusieurs types, le plus simple étant celui à propagation de retenue.
[[File:Fonctionnement d'un compteur (décompteur), schématique.jpg|centre|vignette|upright=2|Fonctionnement d'un compteur (décompteur), schématique. Le pas du compteur précise de combien on incrémente le compteur par cycle d'horloge.]]
===Les compteurs synchrones basiques===
L'incrémenteur le plus simple, à propagation de retenue, est fabriqué en enchainant des ''demi-additionneurs'' les uns à la suite des autres. Pour rappel, un demi-additionneur additionne deux bits. Ici, il additionne un bit de l'opérande, la retenue des colonnes précédentes. Pour le bit de poids faible, la retenue est forcé à 1. Si on combine un incrémenteur à propagation de retenue avec un registre, on obtient ce compteur :
[[File:Compteur synchrone à incrémenteur.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules D.]]
Une simplification permet de faire disparaitre les portes XOR. Les portes XOR servent ici d'inverseur commandable, commandé par la retenue entrante. Elles inversent le contenu de la bascule quand la retenue entrante vaut 1, elles laissent la bascule inchangée si la retenue vaut 0. Or, nous avons déjà une bascule qui inverse son contenu sous certaines condition : la bascule T ! Il est donc possible de fusionner chaque bascule D avec la porte XOR associée, pour donner une bascule T. Le circuit final est celui-ci :
[[File:Compteur synchrone à bascules T.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules T.]]
Il est possible d'optimiser le circuit avec les optimisations vues dans le chapitre sur les incrémenteurs. La première est le ''carry skip'' qui fait l'incrémentation non pas bit par bit, mais par paquets de deux bits. La seconde est l'anticipation de retenue. Et ces deux optimisations se marient bien avec ce qui va suivre.
===Les compteurs en cascade===
[[File:8 Bit Counter.svg|vignette|Compteur 8 bits fabriqués avec deux compteurs 4 bits.]]
Il est possible de concevoir des compteurs à partir de compteurs plus petits, mis en cascade. Par exemple, en créant un compteur 8 bits à partir de deux compteurs 4 bits, enchainés l'un à la suite de l'autre. Idem avec 4 compteurs 4 bits, ce qui fait un compteur de 16 bits.
Les compteurs mis en cascade ont les mêmes entrées et sorties que les compteurs normaux, avec cependant un détail très important : ils ont une entrée pour la retenue entrante, et une sortie pour la retenue sortante. L'entrée pour la retenue entrante précise ce qui doit être additionné au bit de poids faible. Si elle vaut zéro, l'incrémenteur n'incrémente pas l'opérande. Si elle vaut 1, le compteur est incrémenté. L'entrée de retenue fait office d'entrée ''Count Enable'', qui active ou désactive l'incrémentation.
Le compteur a aussi une sortie de débordement, qui indique que le compteur déborde. Il se trouve que cette sortie fournit la retenue pour le compteur suivant. La retenue peut être calculée en utilisant des optimisations comme l'anticipation de retenue, ce qui veut dire qu'elle est calculée sans propager les retenues, directement à partir des bits de l'opérande. Concrètement, la retenue est calculée en faisant un ET logique entre tous les bits du décompte.
[[File:Cascadable binary up-counter.jpg|centre|vignette|upright=2|Cascadable binary up-counter]]
Les compteurs sont mis en cascade de la manière suivante : leur sortie de débordement est connectée sur l'entrée ''Enable'' du compteur suivant, celle qui déclenche l'incrémentation du compteur. La sortie de débordement est notée RCO dans les schéma qui suivent, nous verrons pourquoi dans le prochaine paragraphe.
[[File:Cascaded binary counters.jpg|centre|vignette|upright=2.5|Cascaded binary counters]]
===Les compteurs modulo===
La plupart des compteurs comptent de 0 à <math>2^n - 1</math>, avec <math>n</math> la taille du compteur. Mais d'autres compteurs ne comptent pas jusque-là. Par exemple, certains compteurs ne comptent que jusqu'à 10, 150, etc. Ils sont appelés des '''compteurs ''modulo'''''. Prenons un compteur modulo 6, par exemple : il compte de 0 à 5, et est remis immédiatement à zéro quand il atteint 6. Il compte donc comme suit : 0, 1, 2, 3, 4, 5, 0, 1, 2, ...
Les compteurs ''modulo'' sont construits à partir d'un compteur normal, couplé à un circuit comparateur qui remet à zéro le registre quand il atteint la valeur maximale. Par exemple, on peut imaginer un compteur modulo 6 est construit à partir d'un compteur 4 bits qui compte de 0 à 15 (donc un compteur modulo 16), mais qui est remis à zéro quand il atteint 6. Le circuit comparateur vérifie si la valeur maximale 6 est atteinte et met à 1 l'entrée ''Reset'' si c'est le cas. Le comparateur est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Td4bfig4.png|centre|vignette|upright=1.5|Compteur modulo N.]]
Pour une minorité de compteurs ''modulo'', la valeur maximale est configurable. Pour cela, le compteur est associé à un ''registre de configuration'' qui mémorise la valeur maximale souhaitée. A chaque cycle d'horloge, la valeur dans le compteur est comparée au registre de configuration. Si elles sont identiques, le compteur est remis à zéro. Le compteur est associé au registre de configuration et à un comparateur qui vérifie que les deux sont égaux. Pour le moment, nous ne savons pas faire de circuits comparateurs, ce qui fait qu'on ne peut pas expliquer ce circuit plus en détail.
[[File:4 Bit Counter Prog 1.svg|centre|vignette|upright=2|Compteur 4 bits à valeur maximale programmable.]]
Les compteurs ont tous une sortie de débordement, qui indique quand le compteur déborde. Pour les compteurs modulo, la sortie n'est autre que la sortie du comparateur. Pour les compteurs non-modulo, la sortie de débordement est une sortie du circuit combinatoire qui incrémente le compteur.
==Les ''timers'' : compter des durées, compter des cycles d'horloge==
Les '''''timers''''', aussi appelés ''Programmable interval timer'', sont des circuits capables de compter des durées, exprimées en cycles d'horloge. Leur fonctionnement est assez simple : ils émettent un signal quand un certain nombre de cycles est écoulé, ce nombre de cycles étant configurable. On peut ainsi générer un signal qui surviendra après 50 cycles d'horloge, ou après 100 cycles d'horloge, etc. Le signal en question est disponible sur une sortie de 1 bit, et correspond tout simplement au fait que cette sortie est mise à 1, pendant un cycle d'horloge.
[[File:Programmable interval timer timing diagram.jpg|centre|vignette|upright=2|Exemple d'un ''Timer'' qui émet un signal tous les 6 cycles d'horloge.]]
Les ''timers'' sont composés d'un compteur/décompteur cadencé par un signal d'horloge. Avec un compteur, le compteur est initialisé à 0, puis est incrémenté à chaque signal d'horloge, jusqu’à atteindre la valeur limite où il génère un signal. Pour un décompteur, c'est la même chose, sauf que le décompteur est initialisé à sa valeur limite, est décrémenté à chaque cycle, et envoie un signal quand il atteint 0. Les ''timers'' basés sur des décompteurs sont nettement plus simples que les autres, ce qui fait qu'ils sont plus utilisés.
Pour que les ''timers'' soient configurables, on doit pouvoir préciser combien de cycles il faut (dé-)compter avant d'émettre un signal. On peut ainsi préciser s'il faut émettre le signal après 32 cycles d'horloge, après les 50 cycles, tous les 129 cycles, etc. Le nombre de cycles en question est envoyé sur une entrée d’initialisation du compteur. Il peut être mémorisé dans un '''registre d’intervalle''' dédié.
[[File:Programmable interval timer.jpg|centre|vignette|upright=2|''Timer'' basé sur un décompteur. La porte NOR détecte si le décompteur atteint zéro.]]
Les ''timers'' matériels peuvent compter de deux manières différentes, appelées '''mode une fois''' et '''mode périodique'''.
* En mode une fois, le ''timer'' s'arrête une fois qu'il a atteint la limite configurée. On doit le réinitialiser manuellement, par l'intermédiaire du logiciel, pour l'utiliser une nouvelle fois. Cela permet de compter une certaine durée, exprimée en nombre de cycles d'horloge.
* En mode périodique, le ''timer'' se réinitialise automatiquement avec la valeur de départ, ce qui fait qu'il reboucle à l'infini.
Un ordinateur est rempli de ''timers'' divers. Dans ce qui va suivre, nous allons voir les principaux ''timers'', qui sont actuellement intégrés dans les PC modernes. Ils se trouvent sur la carte mère ou dans le processeur, tout dépend du ''timer''.
===Le ''watchdog timer''===
Le '''''watchdog timer''''' est un ''timer'' spécifique dont le but est de redémarrer automatiquement l'ordinateur si jamais celui-ci ne répond plus ou plante. Beaucoup de PC s'en passent, mais ce ''timer'' est très fréquent dans les architectures embarquées. Le ''watchdog timer'' est un compteur/décompteur qui doit être réinitialisé régulièrement. S'il n'est pas réinitialisé, le ''watchdog timer'' déborde (revient à 0 ou atteint 0) et envoie un signal qui redémarre le système. Le système est conçu pour réinitialiser le ''watchdog timer'' régulièrement, ce qui signifie que le système n'est pas censé redémarrer. Si jamais le système dysfonctionne gravement, le système ne pourra pas réinitialiser le ''watchdog timer'' et le système est redémarré automatiquement ou mis en arrêt.
[[File:SimpleWatchdogTimer.gif|centre|vignette|upright=2|Le ''Watchdog Timer'' et l'ordinateur.]]
===Le ''Time Stamp Counter'' des processeurs x86===
Tous les processeurs des PC actuels sont des processeurs dits x86. Nous ne pouvons pas expliquer ce que cela signifie pour le moment, retenez juste ce terme. Sachez que tous les processeurs x86 contiennent un compteur de 64 bits, appelé le '''''Time Stamp Counter''''', qui mémorise le nombre de cycles d'horloge qu'a effectué le processeur depuis son démarrage. Les programmes peuvent accéder à ce registre assez facilement, ce qui est utile pour faire des mesures ou comparer les performances de deux applications. Il permet de compter combien de cycles d'horloge met un morceau de code à s’exécuter, combien de cycles prend une instruction à s’exécuter, etc. Les processeurs non-x86 ont un registre équivalent, que ce soit les processeurs ARM ou d'autres.
Malheureusement, ce compteur est tombé en désuétude pour tout un tas de raisons. La principale est que les processeurs actuels font varier leur fréquence suivant les besoins. Ils augmentent leur fréquence quand on leur demande de faire beaucoup de calculs, et se mettent en mode basse(fréquence pour économiser de l'énergie si on ne leur demande pas grand chose. Avec une fréquence variable, le ''Time Stamp Counter'' perd complétement en fiabilité. Intel a tenté de corriger ce défaut en incrémentant ce registre à une fréquence constante, différente de celle du processeur, ce qui est encore le cas sur les processeurs Intel actuels. Le comportement est un peu différent sur les processeurs AMD, qui cadencent ce ''timer'' à la fréquence du processeur mais utilisent des mécanismes de synchronisation assez complexes pour corriger l'effet de la fréquence variable.
===L'horloge temps réel===
L''''horloge temps réel''' est un ''timer'' qui génère une fréquence de 1024 Hz, soit près d'un Kilohertz. Dans ce qui suit, nous la noterons RTC, ce qui est l'acronyme du terme anglais ''Real Time Clock''. La RTC prend en entrée un signal d'horloge de 32KHz, généré par un oscillateur à Quartz, et fournit en sortie un signal de fréquence 32 fois plus faible, c'est à dire de 1 KHz. Pour cela, elle est réglée en mode répétitif et son décompteur interne est initialisé à 32. La RTC génère donc un signal toutes les millisecondes, qui est envoyé au processeur. On peut, en théorie, changer la fréquence de la RTC, mais c'est rarement une bonne idée.
En théorie, la RTC permet de compter des durées assez courtes, comme le ''ping'' (le temps de latence d'un réseau, pour simplifier), le temps de rafraichissement de l'écran, ou bien d'autres choses. Mais dans les faits, l'horloge temps réel sa fréquence n'aide pas : 1024 Hz est proche de 1000, mais pas assez pour faire des mesures à la milliseconde près, chose qui est nécessaire pour mesurer le ''ping'' ou d'autres choses utiles. A la place, l'ordinateur l'utilise pour que l'ordinateur soit toujours à l'heure. L'ordinateur sait quelle heure il est avec une précision de l'ordre de la seconde (vous pouvez regarder le bureau de Windows dans le coin inférieur droite de votre écran pour vous en convaincre).
===Le ''Programmable Interval Timer'' : l'Intel 8253===
L'Intel 8253 était un ''timer'' programmable autrefois soudé sur les cartes mères des premiers PC. Il fût suivi par l'Intel 8254, qui en était une légère amélioration. Il était cadencé par une horloge maitre, générée par un oscillateur à Quartz, dont la fréquence est de 32 768 Hertz, soit 2^15 cycles d'horloge par seconde. S'il n'est plus présent dans un boitier sur la carte mère, on trouve toujours un circuit semblable au 8253 à l'intérieur du ''chipset'' de la carte mère, voire à l'intérieur du processeur, pour des raisons de compatibilité. L'intérieur de l'Intel 8253 est illustré ci-dessous. Nous allons expliquer l'ensemble de ce schéma, rassurez-vous, mais les explications seront plus simples à comprendre si vous survolez ce schéma en premier lieu.
[[File:Intel 8253 block diagram.svg|centre|vignette|upright=2|Intel 8253, intérieur.]]
L'Intel 8253 contient trois compteurs de 16 bits, numérotés 0, 1 et 2. Pour chaque compteur, l'entrée CLOCK est celle de l'horloge de 32 MHz, l'entrée GATE active ou désactive le compteur, la sortie fournit le signal voulu et/ou la fréquence de sortie. Les trois compteurs étaient utilisés pour dériver plusieurs fréquences allant de 18,2 Hz à environ 500 KHz. Par exemple, il était utilisé par défaut pour le rafraichissement de la mémoire (D)RAM, mais il était souvent reprogrammé pour servir à générer des fréquences spécifiques par le BIOS ou la carte graphique.
[[File:Intel 8253 and 8254.svg|vignette|Intel 8253 and 8254]]
L'Intel 8253 lui-même possède plusieurs entrées et sorties. En premier lieu, on voit un port de 8 bits connecté aux trois compteurs, qui permet à l'Intel 8253 de communiquer avec le reste de l'ordinateur. La communication se fait dans les deux sens : soit de l'ordinateur vers les compteurs, soit des compteurs vers l'ordinateur. Dans le sens ordinateur -> compteurs, cela permet à l'ordinateur de programmer les compteurs, de les initialiser. Dans l'autre sens, cela permet de récupérer le contenu des compteurs, même si ce n'est pas très utilisé. Il y a aussi 5 entrées de configuration :
* Deux bits A0 et A1 pour sélectionner le compteur voulu avec son numéro.
* Un bit RD à mettre à 0 pour que l'ordinateur récupère le compteur sélectionné sur le port de 8 bits.
* Un bit WR à mettre à 0 pour que l'ordinateur modifie le compteur sélectionné, en envoyant le nombre pour l'initialisation sur le port de 8 bits.
* Un bit CS qui active ou désactive l'Intel 8253 et permet de l'allumer ou de l’éteindre.
L'Intel 8253 intégre un registre de 8 bits, le ''Control Word register'' qui mémorise la configuration de l'Intel 8253. Pour programmer les trois compteurs, il faut écrire un mot de 8 bits dans ce ''Control Word register''. Pour écrire dans le ''Control Word register'', il faut mettre le bit CS à 0 (on active l'Intel 8253), mettre le bit RDà 1 , le bit WR à 0 le bit WR (on indique qu'on fait une écriture), sélectionner le ''Control Word register'' en mettant les deux bits A0 et A1 à 1, puis envoyer la configuration du ''Control Word register'' sur le port de 8 bits.
===Le ''High Precision Event Timer'' (HPET)===
De nos jours, l'horloge temps réel et l'Intel 8253/8254 tendent à être remplacé par un autre ''timer'', le ''High Precision Event Timer'' (HPET). Il s'agit d'un compteur de 64 bits, dont la fréquence est d'au moins 10 MHz. Il s'agit bien d'un compteur et non d'un décompteur. Il gère nativement plusieurs valeurs limites à laquelle générer un signal, qui sont configurables. Pour cela, il est couplé à plusieurs comparateurs, chacun associé à un registre pour mémoriser la valeur limite. Il doit y avoir au moins trois comparateurs/registres, mais le nombre peut monter jusqu’à 256.
[[File:High Precision Event Timer.png|centre|vignette|upright=2|High Precision Event Timer]]
Il faut noter que les systèmes d'exploitation conçus avant le HPET ne peuvent pas l'utiliser, pour des raisons de compatibilité matérielle. C'est le cas de Windows XP avant le Service Pack 3. C'est la raison pour laquelle les cartes mères émulent RTC et PIT dans leurs circuits. D'ailleurs, pour économiser des circuits, les cartes mères modernes émulent le PIT et la RTC avec le HPET : le premier comparateur fournisse la fréquence de 1024 Hz de la RTC, 3 autres comparateurs remplacent l'Intel 8253.
Le HPET gère de nombreux modes de fonctionnement : ses comparateurs peuvent être configuré en mode une fois ou périodique, on peut lui demander d'émuler la RTC et le PIT, etc. Chaque comparateur doit pouvoir fonctionner en mode une fois, et au moins un comparateur doit pouvoir fonctionner en mode périodique. Aussi, il contient aussi 3 registres de configuration. Notons qu'il est aussi possible de lire ou écrire dans le compteur de 64 bits, mais ce n'est pas recommandé.
==Les compteurs en code Gray==
Les compteurs classiques encodent leur décompte en binaire normal sur <math>n</math> bits, mais il faut savoir que d'autres compteurs utilisent le BCD, d'autre le code Gray, etc. Nous allons voir dans ce qui suit ceux qui comptent en code Gray.
Pour rappel, le code Gray permet de coder des nombres d'une manière un peu différente du binaire normal. Son avantage principal est que lorsqu'on incrémente ou décrémente un nombre, seul un bit change ! Pour comparer, en binaire normal, lorsqu'on passe incrémente un nombre, il peut y avoir plusieurs bits qui changent. La moyenne est d'environ deux bits par incrémentation. Les compteurs en code Gray ont beaucoup d'avantages, qui sont tous liés à cette propriété.
Le premier l'absence d'état transitoires douteux. Le problème est que les bits modifiés par une incrémentation ne le sont pas en même temps. Les bits de poids faibles sont modifiés avant les autres. Évidemment, à la fin du calcul, on obtient le résultat final, correct. Mais pendant le temps de calcul, le compteur peut se retrouver dans un état transitoire, où seuls les bits de poids faibles ont été modifiés. C'est un problème si le contenu de ce compteur est relié à des circuits rapides, qui peuvent voir cet état transitoire, mais ne le doivent pas sous peine de dysfonctionner. L'usage de compteurs en code Gray permet d'éviter ce problème : vu que seul un bit est modifié lors d'une incrémentation/décrémentation, les états transitoires n'existent tout simplement pas.
Un exemple typique, évoqué dans les chapitres précédents, est l'échange d'informations entre deux domaines d'horloge. Pour rappel, il arrive que deux portions d'un circuit imprimé aillent à des fréquences différences : on dit que le circuit à plusieurs domaines d'horloge. S'il faut échanger des informations entre ces deux domaines d'horloge, divers problèmes surviennent. Un domaine d'horloge sera plus rapide que l'autre, et pourra voir les états transitoires invisible de l'autre circuit. Et par voir, on veut dire qu'il les prendra pour des états valides, ce qui fera dysfonctionner le circuit. Pour éviter cela, diverses techniques de croisement de domaines d'horloge existent. Et les compteurs Gray en font partie : si un domaine d'horloge utilise la valeur d'un compteur de l'autre, mieux vaut que ce compteur soit un compteur Gray. Et cette situation est assez fréquente !
Un autre avantage mineurs est que la consommation d'énergie de ces compteurs est bien plus réduite qu'avec un compteur normal. Rappelons que pour fonctionner, les circuits électroniques consomment un peu d'électricité. Et la majeure partie de cette consommation sert à faire passer un bit de 0 à 1 ou de 1 à 0. Ce qui fait que quand un compteur est incrémenté ou décrémenté, cela consomme un peu d'énergie électrique.
La moyenne pour un compteur binaire normal est de 2 bits changés par incrémentation/décrémentation, contre un seul pour un compteur Gray, on devine que ces derniers consomment deux fois moins d'énergie par incrémentation. Et cet avantage a des effets en cascade sur les circuits qui suivent ce compteur. Si l'entrée de ces circuits ne change que d'un seul bit, alors leur état changera moins que si c'était deux bits. Les circuits qui suivent vont donc moins consommer.
Un autre avantage en matière de consommation énergétique est lié auxs transitions d'état douteuses. Les circuits connectés au compteur vont voir ces transitions d'état douteuses et modifier leur état interne en réaction. Bien sur, l'état final correct sera atteint une fois que le compteur sera stabilisé, ce qui effacera ces états transitoires intermédiaires. Mais chaque état intermédiaire transitoire correspond à un changement d'état, donc à une consommation d'énergie. En supprimant ces états transitoires, on réduit fortement la consommation d'énergie du circuit. Cela vaut pour le compteur Gray lui-même, mais aussi sur tous les circuits qui ont ce compteur comme entrée !
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les registres et mémoires adressables
| prevText=Les registres et mémoires adressables
| next=Les registres à décalage et les LSFR
| nextText=Les registres à décalage et les LSFR
}}
</noinclude>
q4woet86s8tg8qo55w719ujp2rtswxq
768642
768641
2026-06-25T16:54:58Z
Mewtow
31375
/* Les timers : compter des durées, compter des cycles d'horloge */
768642
wikitext
text/x-wiki
Les '''compteurs/décompteurs''' sont des circuits électroniques qui mémorisent un nombre et l'incrémentent à la demande. En clair, ce sont des registres améliorés afin de supporter l'incrémentation et la décrémentation. Pour donner un exemple d'utilisation, imaginez un circuit qui compte le nombre de voitures dans un parking dans la journée. Pour cela, vous allez prendre deux circuits qui détectent respectivement l'entrée ou la sortie d'une voiture, et un compteur. Le compteur est initialisé à 0 quand le parking est vide, puis est incrémenté à chaque entrée de voiture, décrémenté à chaque sortie. Les exemples de ce type sont suffisamment nombreux pour qu'on dédie un chapitre aux compteurs.
[[File:Binary counter.gif|vignette|Illustration du fonctionnement d'un compteur modulaire binaire de 4 bits, avec un pas de compteur de 1 (le contenu est augmenté de 1 à chaque mise à jour).]]
Un compteur mémorise un nombre qui est incrémenté ou décrémenté au besoin. Le nombre mémorisé sera appelé le '''décompte''' dans ce qui suit. Il est mémorisé dans un registre à l'intérieur du compteur. Au passage, le nombre de bits <math>n</math> du compteur est appelé la '''taille du compteur''', par analogie avec les registres. Il faut cependant faire la différence entre les ''compteurs'' d'un côté et les ''décompteurs'' de l'autre. Les compteurs incrémentent le décompte, les décompteurs le décrémentent, les compteurs-décompteurs peuvent faire les deux.
==L'interface d'un compteur/décompteur==
Les compteurs et décompteurs sont des circuits synchrones et ont donc une entrée d'horloge. Les compteurs les plus simples incrémentent leur contenu à chaque cycle d'horloge, mais les plus fréquents n'incrémentent le décompte que sur demande. Pour cela, ils disposent d'une entrée '''''Count Enable''''', similaire à l'entrée ''Enable'' des registres, séparée de l'entrée d'horloge. Le décompte est incrémenté/décrémenté seulement si l'entrée Enable est à 1, lors d'un front adéquat sur le signal d'horloge.
Les compteurs ont aussi une entrée '''''Reset''''' qui permet de les remettre à zéro. Il y a parfois une entrée qui permet d'initialiser le compteur à une valeur par défaut, non-nulle. Par exemple, on peut initialiser le décompte à la valeur 5, ou une autre. Pour cela, le compteur dispose de deux entrées : une entrée sur laquelle envoyer le décompte initial, une entrée pour autoriser la réinitialisation. Les entrées en question sont appelées '''''Preload Data''''' et '''''Preload Enable'''''. La seconde entrée est parfois distincte de l'entrée de réinitialisation, pour permettre de réinitialiser le compteur soit à zéro, soit à la valeur voulue.
Il peut être utile de prévenir quand un débordement d'entier a lieu, à savoir quand le compteur n'a pas assez de bits pour encoder le décompte. Le compteur est alors remis à zéro, dans la plupart des cas. Mais il faut prévenir que le compteur a débordé, ce qui est utile pour fabriquer des circuits diviseurs de fréquence et des ''timers''. Pour cela, on ajoute une '''sortie de débordement''' au compteur, qui est mise à 1 quand le compteur déborde.
Sur les compteurs/décompteurs, il y a une entrée '''''Count Direction''''' qui décide s'il faut compter ou décompter. Typiquement, elle est à 1 s'il faut compter et 0 s'il faut décompter.
[[File:Digital counter signals.jpg|centre|vignette|upright=2|Interface d'un compteur-décompteur.]]
Dans les schémas qui vont suivre, les entrées Enable ne sont pas représentées. Il est sous-entendu qu'il y a une entrée ''Enable'' pour tous les compteurs qui vont suivre. Il existe deux méthodes pour créer de tels compteurs : la première donne ce qu'on appelle des compteurs asynchrones, et l'autre des compteurs synchrones.
==L'intérieur d'un compteur==
A une exception bien précise qu'on abordera plus bas, les compteurs sont composés d'un registre, qui mémorise le décompte, couplé à un circuit '''incrémenteur'''. Nous avions déjà abordé l'incrémenteur dans un chapitre précédent, aussi je ne vais pas ré-expliquer comment il est conçu. Tout ce qu'il faut retenir est qu'il y en a plusieurs types, le plus simple étant celui à propagation de retenue.
[[File:Fonctionnement d'un compteur (décompteur), schématique.jpg|centre|vignette|upright=2|Fonctionnement d'un compteur (décompteur), schématique. Le pas du compteur précise de combien on incrémente le compteur par cycle d'horloge.]]
===Les compteurs synchrones basiques===
L'incrémenteur le plus simple, à propagation de retenue, est fabriqué en enchainant des ''demi-additionneurs'' les uns à la suite des autres. Pour rappel, un demi-additionneur additionne deux bits. Ici, il additionne un bit de l'opérande, la retenue des colonnes précédentes. Pour le bit de poids faible, la retenue est forcé à 1. Si on combine un incrémenteur à propagation de retenue avec un registre, on obtient ce compteur :
[[File:Compteur synchrone à incrémenteur.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules D.]]
Une simplification permet de faire disparaitre les portes XOR. Les portes XOR servent ici d'inverseur commandable, commandé par la retenue entrante. Elles inversent le contenu de la bascule quand la retenue entrante vaut 1, elles laissent la bascule inchangée si la retenue vaut 0. Or, nous avons déjà une bascule qui inverse son contenu sous certaines condition : la bascule T ! Il est donc possible de fusionner chaque bascule D avec la porte XOR associée, pour donner une bascule T. Le circuit final est celui-ci :
[[File:Compteur synchrone à bascules T.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules T.]]
Il est possible d'optimiser le circuit avec les optimisations vues dans le chapitre sur les incrémenteurs. La première est le ''carry skip'' qui fait l'incrémentation non pas bit par bit, mais par paquets de deux bits. La seconde est l'anticipation de retenue. Et ces deux optimisations se marient bien avec ce qui va suivre.
===Les compteurs en cascade===
[[File:8 Bit Counter.svg|vignette|Compteur 8 bits fabriqués avec deux compteurs 4 bits.]]
Il est possible de concevoir des compteurs à partir de compteurs plus petits, mis en cascade. Par exemple, en créant un compteur 8 bits à partir de deux compteurs 4 bits, enchainés l'un à la suite de l'autre. Idem avec 4 compteurs 4 bits, ce qui fait un compteur de 16 bits.
Les compteurs mis en cascade ont les mêmes entrées et sorties que les compteurs normaux, avec cependant un détail très important : ils ont une entrée pour la retenue entrante, et une sortie pour la retenue sortante. L'entrée pour la retenue entrante précise ce qui doit être additionné au bit de poids faible. Si elle vaut zéro, l'incrémenteur n'incrémente pas l'opérande. Si elle vaut 1, le compteur est incrémenté. L'entrée de retenue fait office d'entrée ''Count Enable'', qui active ou désactive l'incrémentation.
Le compteur a aussi une sortie de débordement, qui indique que le compteur déborde. Il se trouve que cette sortie fournit la retenue pour le compteur suivant. La retenue peut être calculée en utilisant des optimisations comme l'anticipation de retenue, ce qui veut dire qu'elle est calculée sans propager les retenues, directement à partir des bits de l'opérande. Concrètement, la retenue est calculée en faisant un ET logique entre tous les bits du décompte.
[[File:Cascadable binary up-counter.jpg|centre|vignette|upright=2|Cascadable binary up-counter]]
Les compteurs sont mis en cascade de la manière suivante : leur sortie de débordement est connectée sur l'entrée ''Enable'' du compteur suivant, celle qui déclenche l'incrémentation du compteur. La sortie de débordement est notée RCO dans les schéma qui suivent, nous verrons pourquoi dans le prochaine paragraphe.
[[File:Cascaded binary counters.jpg|centre|vignette|upright=2.5|Cascaded binary counters]]
===Les compteurs modulo===
La plupart des compteurs comptent de 0 à <math>2^n - 1</math>, avec <math>n</math> la taille du compteur. Mais d'autres compteurs ne comptent pas jusque-là. Par exemple, certains compteurs ne comptent que jusqu'à 10, 150, etc. Ils sont appelés des '''compteurs ''modulo'''''. Prenons un compteur modulo 6, par exemple : il compte de 0 à 5, et est remis immédiatement à zéro quand il atteint 6. Il compte donc comme suit : 0, 1, 2, 3, 4, 5, 0, 1, 2, ...
Les compteurs ''modulo'' sont construits à partir d'un compteur normal, couplé à un circuit comparateur qui remet à zéro le registre quand il atteint la valeur maximale. Par exemple, on peut imaginer un compteur modulo 6 est construit à partir d'un compteur 4 bits qui compte de 0 à 15 (donc un compteur modulo 16), mais qui est remis à zéro quand il atteint 6. Le circuit comparateur vérifie si la valeur maximale 6 est atteinte et met à 1 l'entrée ''Reset'' si c'est le cas. Le comparateur est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Td4bfig4.png|centre|vignette|upright=1.5|Compteur modulo N.]]
Pour une minorité de compteurs ''modulo'', la valeur maximale est configurable. Pour cela, le compteur est associé à un ''registre de configuration'' qui mémorise la valeur maximale souhaitée. A chaque cycle d'horloge, la valeur dans le compteur est comparée au registre de configuration. Si elles sont identiques, le compteur est remis à zéro. Le compteur est associé au registre de configuration et à un comparateur qui vérifie que les deux sont égaux. Pour le moment, nous ne savons pas faire de circuits comparateurs, ce qui fait qu'on ne peut pas expliquer ce circuit plus en détail.
[[File:4 Bit Counter Prog 1.svg|centre|vignette|upright=2|Compteur 4 bits à valeur maximale programmable.]]
Les compteurs ont tous une sortie de débordement, qui indique quand le compteur déborde. Pour les compteurs modulo, la sortie n'est autre que la sortie du comparateur. Pour les compteurs non-modulo, la sortie de débordement est une sortie du circuit combinatoire qui incrémente le compteur.
==Les ''timers'' : compter des durées, compter des cycles d'horloge==
Les '''''timers''''', aussi appelés ''Programmable interval timer'', sont des circuits capables de compter des durées, exprimées en cycles d'horloge. Leur fonctionnement est assez simple : ils émettent un signal quand un certain nombre de cycles est écoulé, ce nombre de cycles étant configurable. On peut ainsi générer un signal qui surviendra après 50 cycles d'horloge, ou après 100 cycles d'horloge, etc. Le signal en question est disponible sur une sortie de 1 bit, et correspond tout simplement au fait que cette sortie est mise à 1, pendant un cycle d'horloge.
[[File:Programmable interval timer timing diagram.jpg|centre|vignette|upright=2|Exemple d'un ''Timer'' qui émet un signal tous les 6 cycles d'horloge.]]
Les ''timers'' peuvent compter de deux manières différentes, appelées '''mode une fois''' et '''mode périodique'''.
* En mode une fois, le ''timer'' s'arrête une fois qu'il a atteint la limite configurée. On doit le réinitialiser manuellement, par l'intermédiaire du logiciel, pour l'utiliser une nouvelle fois. Cela permet de compter une certaine durée, exprimée en nombre de cycles d'horloge.
* En mode périodique, le ''timer'' se réinitialise automatiquement avec la valeur de départ, ce qui fait qu'il reboucle à l'infini.
Les ''timers'' sont composés d'un compteur/décompteur cadencé par un signal d'horloge. Avec un compteur, le compteur est initialisé à 0, puis est incrémenté à chaque signal d'horloge, jusqu’à atteindre la valeur limite où il génère un signal. Pour un décompteur, c'est la même chose, sauf que le décompteur est initialisé à sa valeur limite, est décrémenté à chaque cycle, et envoie un signal quand il atteint 0. Les ''timers'' basés sur des décompteurs sont nettement plus simples que les autres, ce qui fait qu'ils sont plus utilisés.
Pour que les ''timers'' soient configurables, on doit pouvoir préciser combien de cycles il faut (dé-)compter avant d'émettre un signal. On peut ainsi préciser s'il faut émettre le signal après 32 cycles d'horloge, après les 50 cycles, tous les 129 cycles, etc. Le nombre de cycles en question est envoyé sur une entrée d’initialisation du compteur. Il peut être mémorisé dans un '''registre d’intervalle''' dédié.
[[File:Programmable interval timer.jpg|centre|vignette|upright=2|''Timer'' périodique basé sur un décompteur. La porte NOR détecte si le décompteur atteint zéro.]]
Un ordinateur est rempli de ''timers'' divers. Dans ce qui va suivre, nous allons voir les principaux ''timers'', qui sont actuellement intégrés dans les PC modernes. Ils se trouvent sur la carte mère ou dans le processeur, tout dépend du ''timer''.
===Le ''watchdog timer''===
Le '''''watchdog timer''''' est un ''timer'' spécifique dont le but est de redémarrer automatiquement l'ordinateur si jamais celui-ci ne répond plus ou plante. Beaucoup de PC s'en passent, mais ce ''timer'' est très fréquent dans les architectures embarquées. Le ''watchdog timer'' est un compteur/décompteur qui doit être réinitialisé régulièrement. S'il n'est pas réinitialisé, le ''watchdog timer'' déborde (revient à 0 ou atteint 0) et envoie un signal qui redémarre le système. Le système est conçu pour réinitialiser le ''watchdog timer'' régulièrement, ce qui signifie que le système n'est pas censé redémarrer. Si jamais le système dysfonctionne gravement, le système ne pourra pas réinitialiser le ''watchdog timer'' et le système est redémarré automatiquement ou mis en arrêt.
[[File:SimpleWatchdogTimer.gif|centre|vignette|upright=2|Le ''Watchdog Timer'' et l'ordinateur.]]
===Le ''Time Stamp Counter'' des processeurs x86===
Tous les processeurs des PC actuels sont des processeurs dits x86. Nous ne pouvons pas expliquer ce que cela signifie pour le moment, retenez juste ce terme. Sachez que tous les processeurs x86 contiennent un compteur de 64 bits, appelé le '''''Time Stamp Counter''''', qui mémorise le nombre de cycles d'horloge qu'a effectué le processeur depuis son démarrage. Les programmes peuvent accéder à ce registre assez facilement, ce qui est utile pour faire des mesures ou comparer les performances de deux applications. Il permet de compter combien de cycles d'horloge met un morceau de code à s’exécuter, combien de cycles prend une instruction à s’exécuter, etc. Les processeurs non-x86 ont un registre équivalent, que ce soit les processeurs ARM ou d'autres.
Malheureusement, ce compteur est tombé en désuétude pour tout un tas de raisons. La principale est que les processeurs actuels font varier leur fréquence suivant les besoins. Ils augmentent leur fréquence quand on leur demande de faire beaucoup de calculs, et se mettent en mode basse(fréquence pour économiser de l'énergie si on ne leur demande pas grand chose. Avec une fréquence variable, le ''Time Stamp Counter'' perd complétement en fiabilité. Intel a tenté de corriger ce défaut en incrémentant ce registre à une fréquence constante, différente de celle du processeur, ce qui est encore le cas sur les processeurs Intel actuels. Le comportement est un peu différent sur les processeurs AMD, qui cadencent ce ''timer'' à la fréquence du processeur mais utilisent des mécanismes de synchronisation assez complexes pour corriger l'effet de la fréquence variable.
===L'horloge temps réel===
L''''horloge temps réel''' est un ''timer'' qui génère une fréquence de 1024 Hz, soit près d'un Kilohertz. Dans ce qui suit, nous la noterons RTC, ce qui est l'acronyme du terme anglais ''Real Time Clock''. La RTC prend en entrée un signal d'horloge de 32KHz, généré par un oscillateur à Quartz, et fournit en sortie un signal de fréquence 32 fois plus faible, c'est à dire de 1 KHz. Pour cela, elle est réglée en mode répétitif et son décompteur interne est initialisé à 32. La RTC génère donc un signal toutes les millisecondes, qui est envoyé au processeur. On peut, en théorie, changer la fréquence de la RTC, mais c'est rarement une bonne idée.
En théorie, la RTC permet de compter des durées assez courtes, comme le ''ping'' (le temps de latence d'un réseau, pour simplifier), le temps de rafraichissement de l'écran, ou bien d'autres choses. Mais dans les faits, l'horloge temps réel sa fréquence n'aide pas : 1024 Hz est proche de 1000, mais pas assez pour faire des mesures à la milliseconde près, chose qui est nécessaire pour mesurer le ''ping'' ou d'autres choses utiles. A la place, l'ordinateur l'utilise pour que l'ordinateur soit toujours à l'heure. L'ordinateur sait quelle heure il est avec une précision de l'ordre de la seconde (vous pouvez regarder le bureau de Windows dans le coin inférieur droite de votre écran pour vous en convaincre).
===Le ''Programmable Interval Timer'' : l'Intel 8253===
L'Intel 8253 était un ''timer'' programmable autrefois soudé sur les cartes mères des premiers PC. Il fût suivi par l'Intel 8254, qui en était une légère amélioration. Il était cadencé par une horloge maitre, générée par un oscillateur à Quartz, dont la fréquence est de 32 768 Hertz, soit 2^15 cycles d'horloge par seconde. S'il n'est plus présent dans un boitier sur la carte mère, on trouve toujours un circuit semblable au 8253 à l'intérieur du ''chipset'' de la carte mère, voire à l'intérieur du processeur, pour des raisons de compatibilité. L'intérieur de l'Intel 8253 est illustré ci-dessous. Nous allons expliquer l'ensemble de ce schéma, rassurez-vous, mais les explications seront plus simples à comprendre si vous survolez ce schéma en premier lieu.
[[File:Intel 8253 block diagram.svg|centre|vignette|upright=2|Intel 8253, intérieur.]]
L'Intel 8253 contient trois compteurs de 16 bits, numérotés 0, 1 et 2. Pour chaque compteur, l'entrée CLOCK est celle de l'horloge de 32 MHz, l'entrée GATE active ou désactive le compteur, la sortie fournit le signal voulu et/ou la fréquence de sortie. Les trois compteurs étaient utilisés pour dériver plusieurs fréquences allant de 18,2 Hz à environ 500 KHz. Par exemple, il était utilisé par défaut pour le rafraichissement de la mémoire (D)RAM, mais il était souvent reprogrammé pour servir à générer des fréquences spécifiques par le BIOS ou la carte graphique.
[[File:Intel 8253 and 8254.svg|vignette|Intel 8253 and 8254]]
L'Intel 8253 lui-même possède plusieurs entrées et sorties. En premier lieu, on voit un port de 8 bits connecté aux trois compteurs, qui permet à l'Intel 8253 de communiquer avec le reste de l'ordinateur. La communication se fait dans les deux sens : soit de l'ordinateur vers les compteurs, soit des compteurs vers l'ordinateur. Dans le sens ordinateur -> compteurs, cela permet à l'ordinateur de programmer les compteurs, de les initialiser. Dans l'autre sens, cela permet de récupérer le contenu des compteurs, même si ce n'est pas très utilisé. Il y a aussi 5 entrées de configuration :
* Deux bits A0 et A1 pour sélectionner le compteur voulu avec son numéro.
* Un bit RD à mettre à 0 pour que l'ordinateur récupère le compteur sélectionné sur le port de 8 bits.
* Un bit WR à mettre à 0 pour que l'ordinateur modifie le compteur sélectionné, en envoyant le nombre pour l'initialisation sur le port de 8 bits.
* Un bit CS qui active ou désactive l'Intel 8253 et permet de l'allumer ou de l’éteindre.
L'Intel 8253 intégre un registre de 8 bits, le ''Control Word register'' qui mémorise la configuration de l'Intel 8253. Pour programmer les trois compteurs, il faut écrire un mot de 8 bits dans ce ''Control Word register''. Pour écrire dans le ''Control Word register'', il faut mettre le bit CS à 0 (on active l'Intel 8253), mettre le bit RDà 1 , le bit WR à 0 le bit WR (on indique qu'on fait une écriture), sélectionner le ''Control Word register'' en mettant les deux bits A0 et A1 à 1, puis envoyer la configuration du ''Control Word register'' sur le port de 8 bits.
===Le ''High Precision Event Timer'' (HPET)===
De nos jours, l'horloge temps réel et l'Intel 8253/8254 tendent à être remplacé par un autre ''timer'', le ''High Precision Event Timer'' (HPET). Il s'agit d'un compteur de 64 bits, dont la fréquence est d'au moins 10 MHz. Il s'agit bien d'un compteur et non d'un décompteur. Il gère nativement plusieurs valeurs limites à laquelle générer un signal, qui sont configurables. Pour cela, il est couplé à plusieurs comparateurs, chacun associé à un registre pour mémoriser la valeur limite. Il doit y avoir au moins trois comparateurs/registres, mais le nombre peut monter jusqu’à 256.
[[File:High Precision Event Timer.png|centre|vignette|upright=2|High Precision Event Timer]]
Il faut noter que les systèmes d'exploitation conçus avant le HPET ne peuvent pas l'utiliser, pour des raisons de compatibilité matérielle. C'est le cas de Windows XP avant le Service Pack 3. C'est la raison pour laquelle les cartes mères émulent RTC et PIT dans leurs circuits. D'ailleurs, pour économiser des circuits, les cartes mères modernes émulent le PIT et la RTC avec le HPET : le premier comparateur fournisse la fréquence de 1024 Hz de la RTC, 3 autres comparateurs remplacent l'Intel 8253.
Le HPET gère de nombreux modes de fonctionnement : ses comparateurs peuvent être configuré en mode une fois ou périodique, on peut lui demander d'émuler la RTC et le PIT, etc. Chaque comparateur doit pouvoir fonctionner en mode une fois, et au moins un comparateur doit pouvoir fonctionner en mode périodique. Aussi, il contient aussi 3 registres de configuration. Notons qu'il est aussi possible de lire ou écrire dans le compteur de 64 bits, mais ce n'est pas recommandé.
==Les compteurs en code Gray==
Les compteurs classiques encodent leur décompte en binaire normal sur <math>n</math> bits, mais il faut savoir que d'autres compteurs utilisent le BCD, d'autre le code Gray, etc. Nous allons voir dans ce qui suit ceux qui comptent en code Gray.
Pour rappel, le code Gray permet de coder des nombres d'une manière un peu différente du binaire normal. Son avantage principal est que lorsqu'on incrémente ou décrémente un nombre, seul un bit change ! Pour comparer, en binaire normal, lorsqu'on passe incrémente un nombre, il peut y avoir plusieurs bits qui changent. La moyenne est d'environ deux bits par incrémentation. Les compteurs en code Gray ont beaucoup d'avantages, qui sont tous liés à cette propriété.
Le premier l'absence d'état transitoires douteux. Le problème est que les bits modifiés par une incrémentation ne le sont pas en même temps. Les bits de poids faibles sont modifiés avant les autres. Évidemment, à la fin du calcul, on obtient le résultat final, correct. Mais pendant le temps de calcul, le compteur peut se retrouver dans un état transitoire, où seuls les bits de poids faibles ont été modifiés. C'est un problème si le contenu de ce compteur est relié à des circuits rapides, qui peuvent voir cet état transitoire, mais ne le doivent pas sous peine de dysfonctionner. L'usage de compteurs en code Gray permet d'éviter ce problème : vu que seul un bit est modifié lors d'une incrémentation/décrémentation, les états transitoires n'existent tout simplement pas.
Un exemple typique, évoqué dans les chapitres précédents, est l'échange d'informations entre deux domaines d'horloge. Pour rappel, il arrive que deux portions d'un circuit imprimé aillent à des fréquences différences : on dit que le circuit à plusieurs domaines d'horloge. S'il faut échanger des informations entre ces deux domaines d'horloge, divers problèmes surviennent. Un domaine d'horloge sera plus rapide que l'autre, et pourra voir les états transitoires invisible de l'autre circuit. Et par voir, on veut dire qu'il les prendra pour des états valides, ce qui fera dysfonctionner le circuit. Pour éviter cela, diverses techniques de croisement de domaines d'horloge existent. Et les compteurs Gray en font partie : si un domaine d'horloge utilise la valeur d'un compteur de l'autre, mieux vaut que ce compteur soit un compteur Gray. Et cette situation est assez fréquente !
Un autre avantage mineurs est que la consommation d'énergie de ces compteurs est bien plus réduite qu'avec un compteur normal. Rappelons que pour fonctionner, les circuits électroniques consomment un peu d'électricité. Et la majeure partie de cette consommation sert à faire passer un bit de 0 à 1 ou de 1 à 0. Ce qui fait que quand un compteur est incrémenté ou décrémenté, cela consomme un peu d'énergie électrique.
La moyenne pour un compteur binaire normal est de 2 bits changés par incrémentation/décrémentation, contre un seul pour un compteur Gray, on devine que ces derniers consomment deux fois moins d'énergie par incrémentation. Et cet avantage a des effets en cascade sur les circuits qui suivent ce compteur. Si l'entrée de ces circuits ne change que d'un seul bit, alors leur état changera moins que si c'était deux bits. Les circuits qui suivent vont donc moins consommer.
Un autre avantage en matière de consommation énergétique est lié auxs transitions d'état douteuses. Les circuits connectés au compteur vont voir ces transitions d'état douteuses et modifier leur état interne en réaction. Bien sur, l'état final correct sera atteint une fois que le compteur sera stabilisé, ce qui effacera ces états transitoires intermédiaires. Mais chaque état intermédiaire transitoire correspond à un changement d'état, donc à une consommation d'énergie. En supprimant ces états transitoires, on réduit fortement la consommation d'énergie du circuit. Cela vaut pour le compteur Gray lui-même, mais aussi sur tous les circuits qui ont ce compteur comme entrée !
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les registres et mémoires adressables
| prevText=Les registres et mémoires adressables
| next=Les registres à décalage et les LSFR
| nextText=Les registres à décalage et les LSFR
}}
</noinclude>
89w07iek54wgemncs8llfxwksu5u4np
768643
768642
2026-06-25T16:55:41Z
Mewtow
31375
/* Les timers : compter des durées, compter des cycles d'horloge */
768643
wikitext
text/x-wiki
Les '''compteurs/décompteurs''' sont des circuits électroniques qui mémorisent un nombre et l'incrémentent à la demande. En clair, ce sont des registres améliorés afin de supporter l'incrémentation et la décrémentation. Pour donner un exemple d'utilisation, imaginez un circuit qui compte le nombre de voitures dans un parking dans la journée. Pour cela, vous allez prendre deux circuits qui détectent respectivement l'entrée ou la sortie d'une voiture, et un compteur. Le compteur est initialisé à 0 quand le parking est vide, puis est incrémenté à chaque entrée de voiture, décrémenté à chaque sortie. Les exemples de ce type sont suffisamment nombreux pour qu'on dédie un chapitre aux compteurs.
[[File:Binary counter.gif|vignette|Illustration du fonctionnement d'un compteur modulaire binaire de 4 bits, avec un pas de compteur de 1 (le contenu est augmenté de 1 à chaque mise à jour).]]
Un compteur mémorise un nombre qui est incrémenté ou décrémenté au besoin. Le nombre mémorisé sera appelé le '''décompte''' dans ce qui suit. Il est mémorisé dans un registre à l'intérieur du compteur. Au passage, le nombre de bits <math>n</math> du compteur est appelé la '''taille du compteur''', par analogie avec les registres. Il faut cependant faire la différence entre les ''compteurs'' d'un côté et les ''décompteurs'' de l'autre. Les compteurs incrémentent le décompte, les décompteurs le décrémentent, les compteurs-décompteurs peuvent faire les deux.
==L'interface d'un compteur/décompteur==
Les compteurs et décompteurs sont des circuits synchrones et ont donc une entrée d'horloge. Les compteurs les plus simples incrémentent leur contenu à chaque cycle d'horloge, mais les plus fréquents n'incrémentent le décompte que sur demande. Pour cela, ils disposent d'une entrée '''''Count Enable''''', similaire à l'entrée ''Enable'' des registres, séparée de l'entrée d'horloge. Le décompte est incrémenté/décrémenté seulement si l'entrée Enable est à 1, lors d'un front adéquat sur le signal d'horloge.
Les compteurs ont aussi une entrée '''''Reset''''' qui permet de les remettre à zéro. Il y a parfois une entrée qui permet d'initialiser le compteur à une valeur par défaut, non-nulle. Par exemple, on peut initialiser le décompte à la valeur 5, ou une autre. Pour cela, le compteur dispose de deux entrées : une entrée sur laquelle envoyer le décompte initial, une entrée pour autoriser la réinitialisation. Les entrées en question sont appelées '''''Preload Data''''' et '''''Preload Enable'''''. La seconde entrée est parfois distincte de l'entrée de réinitialisation, pour permettre de réinitialiser le compteur soit à zéro, soit à la valeur voulue.
Il peut être utile de prévenir quand un débordement d'entier a lieu, à savoir quand le compteur n'a pas assez de bits pour encoder le décompte. Le compteur est alors remis à zéro, dans la plupart des cas. Mais il faut prévenir que le compteur a débordé, ce qui est utile pour fabriquer des circuits diviseurs de fréquence et des ''timers''. Pour cela, on ajoute une '''sortie de débordement''' au compteur, qui est mise à 1 quand le compteur déborde.
Sur les compteurs/décompteurs, il y a une entrée '''''Count Direction''''' qui décide s'il faut compter ou décompter. Typiquement, elle est à 1 s'il faut compter et 0 s'il faut décompter.
[[File:Digital counter signals.jpg|centre|vignette|upright=2|Interface d'un compteur-décompteur.]]
Dans les schémas qui vont suivre, les entrées Enable ne sont pas représentées. Il est sous-entendu qu'il y a une entrée ''Enable'' pour tous les compteurs qui vont suivre. Il existe deux méthodes pour créer de tels compteurs : la première donne ce qu'on appelle des compteurs asynchrones, et l'autre des compteurs synchrones.
==L'intérieur d'un compteur==
A une exception bien précise qu'on abordera plus bas, les compteurs sont composés d'un registre, qui mémorise le décompte, couplé à un circuit '''incrémenteur'''. Nous avions déjà abordé l'incrémenteur dans un chapitre précédent, aussi je ne vais pas ré-expliquer comment il est conçu. Tout ce qu'il faut retenir est qu'il y en a plusieurs types, le plus simple étant celui à propagation de retenue.
[[File:Fonctionnement d'un compteur (décompteur), schématique.jpg|centre|vignette|upright=2|Fonctionnement d'un compteur (décompteur), schématique. Le pas du compteur précise de combien on incrémente le compteur par cycle d'horloge.]]
===Les compteurs synchrones basiques===
L'incrémenteur le plus simple, à propagation de retenue, est fabriqué en enchainant des ''demi-additionneurs'' les uns à la suite des autres. Pour rappel, un demi-additionneur additionne deux bits. Ici, il additionne un bit de l'opérande, la retenue des colonnes précédentes. Pour le bit de poids faible, la retenue est forcé à 1. Si on combine un incrémenteur à propagation de retenue avec un registre, on obtient ce compteur :
[[File:Compteur synchrone à incrémenteur.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules D.]]
Une simplification permet de faire disparaitre les portes XOR. Les portes XOR servent ici d'inverseur commandable, commandé par la retenue entrante. Elles inversent le contenu de la bascule quand la retenue entrante vaut 1, elles laissent la bascule inchangée si la retenue vaut 0. Or, nous avons déjà une bascule qui inverse son contenu sous certaines condition : la bascule T ! Il est donc possible de fusionner chaque bascule D avec la porte XOR associée, pour donner une bascule T. Le circuit final est celui-ci :
[[File:Compteur synchrone à bascules T.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules T.]]
Il est possible d'optimiser le circuit avec les optimisations vues dans le chapitre sur les incrémenteurs. La première est le ''carry skip'' qui fait l'incrémentation non pas bit par bit, mais par paquets de deux bits. La seconde est l'anticipation de retenue. Et ces deux optimisations se marient bien avec ce qui va suivre.
===Les compteurs en cascade===
[[File:8 Bit Counter.svg|vignette|Compteur 8 bits fabriqués avec deux compteurs 4 bits.]]
Il est possible de concevoir des compteurs à partir de compteurs plus petits, mis en cascade. Par exemple, en créant un compteur 8 bits à partir de deux compteurs 4 bits, enchainés l'un à la suite de l'autre. Idem avec 4 compteurs 4 bits, ce qui fait un compteur de 16 bits.
Les compteurs mis en cascade ont les mêmes entrées et sorties que les compteurs normaux, avec cependant un détail très important : ils ont une entrée pour la retenue entrante, et une sortie pour la retenue sortante. L'entrée pour la retenue entrante précise ce qui doit être additionné au bit de poids faible. Si elle vaut zéro, l'incrémenteur n'incrémente pas l'opérande. Si elle vaut 1, le compteur est incrémenté. L'entrée de retenue fait office d'entrée ''Count Enable'', qui active ou désactive l'incrémentation.
Le compteur a aussi une sortie de débordement, qui indique que le compteur déborde. Il se trouve que cette sortie fournit la retenue pour le compteur suivant. La retenue peut être calculée en utilisant des optimisations comme l'anticipation de retenue, ce qui veut dire qu'elle est calculée sans propager les retenues, directement à partir des bits de l'opérande. Concrètement, la retenue est calculée en faisant un ET logique entre tous les bits du décompte.
[[File:Cascadable binary up-counter.jpg|centre|vignette|upright=2|Cascadable binary up-counter]]
Les compteurs sont mis en cascade de la manière suivante : leur sortie de débordement est connectée sur l'entrée ''Enable'' du compteur suivant, celle qui déclenche l'incrémentation du compteur. La sortie de débordement est notée RCO dans les schéma qui suivent, nous verrons pourquoi dans le prochaine paragraphe.
[[File:Cascaded binary counters.jpg|centre|vignette|upright=2.5|Cascaded binary counters]]
===Les compteurs modulo===
La plupart des compteurs comptent de 0 à <math>2^n - 1</math>, avec <math>n</math> la taille du compteur. Mais d'autres compteurs ne comptent pas jusque-là. Par exemple, certains compteurs ne comptent que jusqu'à 10, 150, etc. Ils sont appelés des '''compteurs ''modulo'''''. Prenons un compteur modulo 6, par exemple : il compte de 0 à 5, et est remis immédiatement à zéro quand il atteint 6. Il compte donc comme suit : 0, 1, 2, 3, 4, 5, 0, 1, 2, ...
Les compteurs ''modulo'' sont construits à partir d'un compteur normal, couplé à un circuit comparateur qui remet à zéro le registre quand il atteint la valeur maximale. Par exemple, on peut imaginer un compteur modulo 6 est construit à partir d'un compteur 4 bits qui compte de 0 à 15 (donc un compteur modulo 16), mais qui est remis à zéro quand il atteint 6. Le circuit comparateur vérifie si la valeur maximale 6 est atteinte et met à 1 l'entrée ''Reset'' si c'est le cas. Le comparateur est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Td4bfig4.png|centre|vignette|upright=1.5|Compteur modulo N.]]
Pour une minorité de compteurs ''modulo'', la valeur maximale est configurable. Pour cela, le compteur est associé à un ''registre de configuration'' qui mémorise la valeur maximale souhaitée. A chaque cycle d'horloge, la valeur dans le compteur est comparée au registre de configuration. Si elles sont identiques, le compteur est remis à zéro. Le compteur est associé au registre de configuration et à un comparateur qui vérifie que les deux sont égaux. Pour le moment, nous ne savons pas faire de circuits comparateurs, ce qui fait qu'on ne peut pas expliquer ce circuit plus en détail.
[[File:4 Bit Counter Prog 1.svg|centre|vignette|upright=2|Compteur 4 bits à valeur maximale programmable.]]
Les compteurs ont tous une sortie de débordement, qui indique quand le compteur déborde. Pour les compteurs modulo, la sortie n'est autre que la sortie du comparateur. Pour les compteurs non-modulo, la sortie de débordement est une sortie du circuit combinatoire qui incrémente le compteur.
==Les ''timers'' : compter des durées, compter des cycles d'horloge==
Les '''''timers''''', aussi appelés ''Programmable interval timer'', sont des circuits capables de compter des durées, exprimées en cycles d'horloge. Leur fonctionnement est assez simple : ils émettent un signal quand un certain nombre de cycles est écoulé, ce nombre de cycles étant configurable. On peut ainsi générer un signal qui surviendra après 50 cycles d'horloge, ou après 100 cycles d'horloge, etc. Le signal en question est disponible sur une sortie de 1 bit, et correspond tout simplement au fait que cette sortie est mise à 1, pendant un cycle d'horloge.
[[File:Programmable interval timer timing diagram.jpg|centre|vignette|upright=2.5|Exemple d'un ''Timer'' qui émet un signal tous les 6 cycles d'horloge.]]
Les ''timers'' peuvent compter de deux manières différentes, appelées '''mode une fois''' et '''mode périodique'''.
* En mode une fois, le ''timer'' s'arrête une fois qu'il a atteint la limite configurée. On doit le réinitialiser manuellement, par l'intermédiaire du logiciel, pour l'utiliser une nouvelle fois. Cela permet de compter une certaine durée, exprimée en nombre de cycles d'horloge.
* En mode périodique, le ''timer'' se réinitialise automatiquement avec la valeur de départ, ce qui fait qu'il reboucle à l'infini.
Les ''timers'' sont composés d'un compteur/décompteur cadencé par un signal d'horloge. Avec un compteur, le compteur est initialisé à 0, puis est incrémenté à chaque signal d'horloge, jusqu’à atteindre la valeur limite où il génère un signal. Pour un décompteur, c'est la même chose, sauf que le décompteur est initialisé à sa valeur limite, est décrémenté à chaque cycle, et envoie un signal quand il atteint 0. Les ''timers'' basés sur des décompteurs sont nettement plus simples que les autres, ce qui fait qu'ils sont plus utilisés.
Pour que les ''timers'' soient configurables, on doit pouvoir préciser combien de cycles il faut (dé-)compter avant d'émettre un signal. On peut ainsi préciser s'il faut émettre le signal après 32 cycles d'horloge, après les 50 cycles, tous les 129 cycles, etc. Le nombre de cycles en question est envoyé sur une entrée d’initialisation du compteur. Il peut être mémorisé dans un '''registre d’intervalle''' dédié.
[[File:Programmable interval timer.jpg|centre|vignette|upright=2|''Timer'' périodique basé sur un décompteur. La porte NOR détecte si le décompteur atteint zéro.]]
Un ordinateur est rempli de ''timers'' divers. Dans ce qui va suivre, nous allons voir les principaux ''timers'', qui sont actuellement intégrés dans les PC modernes. Ils se trouvent sur la carte mère ou dans le processeur, tout dépend du ''timer''.
===Le ''watchdog timer''===
Le '''''watchdog timer''''' est un ''timer'' spécifique dont le but est de redémarrer automatiquement l'ordinateur si jamais celui-ci ne répond plus ou plante. Beaucoup de PC s'en passent, mais ce ''timer'' est très fréquent dans les architectures embarquées. Le ''watchdog timer'' est un compteur/décompteur qui doit être réinitialisé régulièrement. S'il n'est pas réinitialisé, le ''watchdog timer'' déborde (revient à 0 ou atteint 0) et envoie un signal qui redémarre le système. Le système est conçu pour réinitialiser le ''watchdog timer'' régulièrement, ce qui signifie que le système n'est pas censé redémarrer. Si jamais le système dysfonctionne gravement, le système ne pourra pas réinitialiser le ''watchdog timer'' et le système est redémarré automatiquement ou mis en arrêt.
[[File:SimpleWatchdogTimer.gif|centre|vignette|upright=2|Le ''Watchdog Timer'' et l'ordinateur.]]
===Le ''Time Stamp Counter'' des processeurs x86===
Tous les processeurs des PC actuels sont des processeurs dits x86. Nous ne pouvons pas expliquer ce que cela signifie pour le moment, retenez juste ce terme. Sachez que tous les processeurs x86 contiennent un compteur de 64 bits, appelé le '''''Time Stamp Counter''''', qui mémorise le nombre de cycles d'horloge qu'a effectué le processeur depuis son démarrage. Les programmes peuvent accéder à ce registre assez facilement, ce qui est utile pour faire des mesures ou comparer les performances de deux applications. Il permet de compter combien de cycles d'horloge met un morceau de code à s’exécuter, combien de cycles prend une instruction à s’exécuter, etc. Les processeurs non-x86 ont un registre équivalent, que ce soit les processeurs ARM ou d'autres.
Malheureusement, ce compteur est tombé en désuétude pour tout un tas de raisons. La principale est que les processeurs actuels font varier leur fréquence suivant les besoins. Ils augmentent leur fréquence quand on leur demande de faire beaucoup de calculs, et se mettent en mode basse(fréquence pour économiser de l'énergie si on ne leur demande pas grand chose. Avec une fréquence variable, le ''Time Stamp Counter'' perd complétement en fiabilité. Intel a tenté de corriger ce défaut en incrémentant ce registre à une fréquence constante, différente de celle du processeur, ce qui est encore le cas sur les processeurs Intel actuels. Le comportement est un peu différent sur les processeurs AMD, qui cadencent ce ''timer'' à la fréquence du processeur mais utilisent des mécanismes de synchronisation assez complexes pour corriger l'effet de la fréquence variable.
===L'horloge temps réel===
L''''horloge temps réel''' est un ''timer'' qui génère une fréquence de 1024 Hz, soit près d'un Kilohertz. Dans ce qui suit, nous la noterons RTC, ce qui est l'acronyme du terme anglais ''Real Time Clock''. La RTC prend en entrée un signal d'horloge de 32KHz, généré par un oscillateur à Quartz, et fournit en sortie un signal de fréquence 32 fois plus faible, c'est à dire de 1 KHz. Pour cela, elle est réglée en mode répétitif et son décompteur interne est initialisé à 32. La RTC génère donc un signal toutes les millisecondes, qui est envoyé au processeur. On peut, en théorie, changer la fréquence de la RTC, mais c'est rarement une bonne idée.
En théorie, la RTC permet de compter des durées assez courtes, comme le ''ping'' (le temps de latence d'un réseau, pour simplifier), le temps de rafraichissement de l'écran, ou bien d'autres choses. Mais dans les faits, l'horloge temps réel sa fréquence n'aide pas : 1024 Hz est proche de 1000, mais pas assez pour faire des mesures à la milliseconde près, chose qui est nécessaire pour mesurer le ''ping'' ou d'autres choses utiles. A la place, l'ordinateur l'utilise pour que l'ordinateur soit toujours à l'heure. L'ordinateur sait quelle heure il est avec une précision de l'ordre de la seconde (vous pouvez regarder le bureau de Windows dans le coin inférieur droite de votre écran pour vous en convaincre).
===Le ''Programmable Interval Timer'' : l'Intel 8253===
L'Intel 8253 était un ''timer'' programmable autrefois soudé sur les cartes mères des premiers PC. Il fût suivi par l'Intel 8254, qui en était une légère amélioration. Il était cadencé par une horloge maitre, générée par un oscillateur à Quartz, dont la fréquence est de 32 768 Hertz, soit 2^15 cycles d'horloge par seconde. S'il n'est plus présent dans un boitier sur la carte mère, on trouve toujours un circuit semblable au 8253 à l'intérieur du ''chipset'' de la carte mère, voire à l'intérieur du processeur, pour des raisons de compatibilité. L'intérieur de l'Intel 8253 est illustré ci-dessous. Nous allons expliquer l'ensemble de ce schéma, rassurez-vous, mais les explications seront plus simples à comprendre si vous survolez ce schéma en premier lieu.
[[File:Intel 8253 block diagram.svg|centre|vignette|upright=2|Intel 8253, intérieur.]]
L'Intel 8253 contient trois compteurs de 16 bits, numérotés 0, 1 et 2. Pour chaque compteur, l'entrée CLOCK est celle de l'horloge de 32 MHz, l'entrée GATE active ou désactive le compteur, la sortie fournit le signal voulu et/ou la fréquence de sortie. Les trois compteurs étaient utilisés pour dériver plusieurs fréquences allant de 18,2 Hz à environ 500 KHz. Par exemple, il était utilisé par défaut pour le rafraichissement de la mémoire (D)RAM, mais il était souvent reprogrammé pour servir à générer des fréquences spécifiques par le BIOS ou la carte graphique.
[[File:Intel 8253 and 8254.svg|vignette|Intel 8253 and 8254]]
L'Intel 8253 lui-même possède plusieurs entrées et sorties. En premier lieu, on voit un port de 8 bits connecté aux trois compteurs, qui permet à l'Intel 8253 de communiquer avec le reste de l'ordinateur. La communication se fait dans les deux sens : soit de l'ordinateur vers les compteurs, soit des compteurs vers l'ordinateur. Dans le sens ordinateur -> compteurs, cela permet à l'ordinateur de programmer les compteurs, de les initialiser. Dans l'autre sens, cela permet de récupérer le contenu des compteurs, même si ce n'est pas très utilisé. Il y a aussi 5 entrées de configuration :
* Deux bits A0 et A1 pour sélectionner le compteur voulu avec son numéro.
* Un bit RD à mettre à 0 pour que l'ordinateur récupère le compteur sélectionné sur le port de 8 bits.
* Un bit WR à mettre à 0 pour que l'ordinateur modifie le compteur sélectionné, en envoyant le nombre pour l'initialisation sur le port de 8 bits.
* Un bit CS qui active ou désactive l'Intel 8253 et permet de l'allumer ou de l’éteindre.
L'Intel 8253 intégre un registre de 8 bits, le ''Control Word register'' qui mémorise la configuration de l'Intel 8253. Pour programmer les trois compteurs, il faut écrire un mot de 8 bits dans ce ''Control Word register''. Pour écrire dans le ''Control Word register'', il faut mettre le bit CS à 0 (on active l'Intel 8253), mettre le bit RDà 1 , le bit WR à 0 le bit WR (on indique qu'on fait une écriture), sélectionner le ''Control Word register'' en mettant les deux bits A0 et A1 à 1, puis envoyer la configuration du ''Control Word register'' sur le port de 8 bits.
===Le ''High Precision Event Timer'' (HPET)===
De nos jours, l'horloge temps réel et l'Intel 8253/8254 tendent à être remplacé par un autre ''timer'', le ''High Precision Event Timer'' (HPET). Il s'agit d'un compteur de 64 bits, dont la fréquence est d'au moins 10 MHz. Il s'agit bien d'un compteur et non d'un décompteur. Il gère nativement plusieurs valeurs limites à laquelle générer un signal, qui sont configurables. Pour cela, il est couplé à plusieurs comparateurs, chacun associé à un registre pour mémoriser la valeur limite. Il doit y avoir au moins trois comparateurs/registres, mais le nombre peut monter jusqu’à 256.
[[File:High Precision Event Timer.png|centre|vignette|upright=2|High Precision Event Timer]]
Il faut noter que les systèmes d'exploitation conçus avant le HPET ne peuvent pas l'utiliser, pour des raisons de compatibilité matérielle. C'est le cas de Windows XP avant le Service Pack 3. C'est la raison pour laquelle les cartes mères émulent RTC et PIT dans leurs circuits. D'ailleurs, pour économiser des circuits, les cartes mères modernes émulent le PIT et la RTC avec le HPET : le premier comparateur fournisse la fréquence de 1024 Hz de la RTC, 3 autres comparateurs remplacent l'Intel 8253.
Le HPET gère de nombreux modes de fonctionnement : ses comparateurs peuvent être configuré en mode une fois ou périodique, on peut lui demander d'émuler la RTC et le PIT, etc. Chaque comparateur doit pouvoir fonctionner en mode une fois, et au moins un comparateur doit pouvoir fonctionner en mode périodique. Aussi, il contient aussi 3 registres de configuration. Notons qu'il est aussi possible de lire ou écrire dans le compteur de 64 bits, mais ce n'est pas recommandé.
==Les compteurs en code Gray==
Les compteurs classiques encodent leur décompte en binaire normal sur <math>n</math> bits, mais il faut savoir que d'autres compteurs utilisent le BCD, d'autre le code Gray, etc. Nous allons voir dans ce qui suit ceux qui comptent en code Gray.
Pour rappel, le code Gray permet de coder des nombres d'une manière un peu différente du binaire normal. Son avantage principal est que lorsqu'on incrémente ou décrémente un nombre, seul un bit change ! Pour comparer, en binaire normal, lorsqu'on passe incrémente un nombre, il peut y avoir plusieurs bits qui changent. La moyenne est d'environ deux bits par incrémentation. Les compteurs en code Gray ont beaucoup d'avantages, qui sont tous liés à cette propriété.
Le premier l'absence d'état transitoires douteux. Le problème est que les bits modifiés par une incrémentation ne le sont pas en même temps. Les bits de poids faibles sont modifiés avant les autres. Évidemment, à la fin du calcul, on obtient le résultat final, correct. Mais pendant le temps de calcul, le compteur peut se retrouver dans un état transitoire, où seuls les bits de poids faibles ont été modifiés. C'est un problème si le contenu de ce compteur est relié à des circuits rapides, qui peuvent voir cet état transitoire, mais ne le doivent pas sous peine de dysfonctionner. L'usage de compteurs en code Gray permet d'éviter ce problème : vu que seul un bit est modifié lors d'une incrémentation/décrémentation, les états transitoires n'existent tout simplement pas.
Un exemple typique, évoqué dans les chapitres précédents, est l'échange d'informations entre deux domaines d'horloge. Pour rappel, il arrive que deux portions d'un circuit imprimé aillent à des fréquences différences : on dit que le circuit à plusieurs domaines d'horloge. S'il faut échanger des informations entre ces deux domaines d'horloge, divers problèmes surviennent. Un domaine d'horloge sera plus rapide que l'autre, et pourra voir les états transitoires invisible de l'autre circuit. Et par voir, on veut dire qu'il les prendra pour des états valides, ce qui fera dysfonctionner le circuit. Pour éviter cela, diverses techniques de croisement de domaines d'horloge existent. Et les compteurs Gray en font partie : si un domaine d'horloge utilise la valeur d'un compteur de l'autre, mieux vaut que ce compteur soit un compteur Gray. Et cette situation est assez fréquente !
Un autre avantage mineurs est que la consommation d'énergie de ces compteurs est bien plus réduite qu'avec un compteur normal. Rappelons que pour fonctionner, les circuits électroniques consomment un peu d'électricité. Et la majeure partie de cette consommation sert à faire passer un bit de 0 à 1 ou de 1 à 0. Ce qui fait que quand un compteur est incrémenté ou décrémenté, cela consomme un peu d'énergie électrique.
La moyenne pour un compteur binaire normal est de 2 bits changés par incrémentation/décrémentation, contre un seul pour un compteur Gray, on devine que ces derniers consomment deux fois moins d'énergie par incrémentation. Et cet avantage a des effets en cascade sur les circuits qui suivent ce compteur. Si l'entrée de ces circuits ne change que d'un seul bit, alors leur état changera moins que si c'était deux bits. Les circuits qui suivent vont donc moins consommer.
Un autre avantage en matière de consommation énergétique est lié auxs transitions d'état douteuses. Les circuits connectés au compteur vont voir ces transitions d'état douteuses et modifier leur état interne en réaction. Bien sur, l'état final correct sera atteint une fois que le compteur sera stabilisé, ce qui effacera ces états transitoires intermédiaires. Mais chaque état intermédiaire transitoire correspond à un changement d'état, donc à une consommation d'énergie. En supprimant ces états transitoires, on réduit fortement la consommation d'énergie du circuit. Cela vaut pour le compteur Gray lui-même, mais aussi sur tous les circuits qui ont ce compteur comme entrée !
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les registres et mémoires adressables
| prevText=Les registres et mémoires adressables
| next=Les registres à décalage et les LSFR
| nextText=Les registres à décalage et les LSFR
}}
</noinclude>
6eut69vgkxf5cytm5nawp9n7o07bsgs
768644
768643
2026-06-25T16:56:01Z
Mewtow
31375
/* Les timers : compter des durées, compter des cycles d'horloge */
768644
wikitext
text/x-wiki
Les '''compteurs/décompteurs''' sont des circuits électroniques qui mémorisent un nombre et l'incrémentent à la demande. En clair, ce sont des registres améliorés afin de supporter l'incrémentation et la décrémentation. Pour donner un exemple d'utilisation, imaginez un circuit qui compte le nombre de voitures dans un parking dans la journée. Pour cela, vous allez prendre deux circuits qui détectent respectivement l'entrée ou la sortie d'une voiture, et un compteur. Le compteur est initialisé à 0 quand le parking est vide, puis est incrémenté à chaque entrée de voiture, décrémenté à chaque sortie. Les exemples de ce type sont suffisamment nombreux pour qu'on dédie un chapitre aux compteurs.
[[File:Binary counter.gif|vignette|Illustration du fonctionnement d'un compteur modulaire binaire de 4 bits, avec un pas de compteur de 1 (le contenu est augmenté de 1 à chaque mise à jour).]]
Un compteur mémorise un nombre qui est incrémenté ou décrémenté au besoin. Le nombre mémorisé sera appelé le '''décompte''' dans ce qui suit. Il est mémorisé dans un registre à l'intérieur du compteur. Au passage, le nombre de bits <math>n</math> du compteur est appelé la '''taille du compteur''', par analogie avec les registres. Il faut cependant faire la différence entre les ''compteurs'' d'un côté et les ''décompteurs'' de l'autre. Les compteurs incrémentent le décompte, les décompteurs le décrémentent, les compteurs-décompteurs peuvent faire les deux.
==L'interface d'un compteur/décompteur==
Les compteurs et décompteurs sont des circuits synchrones et ont donc une entrée d'horloge. Les compteurs les plus simples incrémentent leur contenu à chaque cycle d'horloge, mais les plus fréquents n'incrémentent le décompte que sur demande. Pour cela, ils disposent d'une entrée '''''Count Enable''''', similaire à l'entrée ''Enable'' des registres, séparée de l'entrée d'horloge. Le décompte est incrémenté/décrémenté seulement si l'entrée Enable est à 1, lors d'un front adéquat sur le signal d'horloge.
Les compteurs ont aussi une entrée '''''Reset''''' qui permet de les remettre à zéro. Il y a parfois une entrée qui permet d'initialiser le compteur à une valeur par défaut, non-nulle. Par exemple, on peut initialiser le décompte à la valeur 5, ou une autre. Pour cela, le compteur dispose de deux entrées : une entrée sur laquelle envoyer le décompte initial, une entrée pour autoriser la réinitialisation. Les entrées en question sont appelées '''''Preload Data''''' et '''''Preload Enable'''''. La seconde entrée est parfois distincte de l'entrée de réinitialisation, pour permettre de réinitialiser le compteur soit à zéro, soit à la valeur voulue.
Il peut être utile de prévenir quand un débordement d'entier a lieu, à savoir quand le compteur n'a pas assez de bits pour encoder le décompte. Le compteur est alors remis à zéro, dans la plupart des cas. Mais il faut prévenir que le compteur a débordé, ce qui est utile pour fabriquer des circuits diviseurs de fréquence et des ''timers''. Pour cela, on ajoute une '''sortie de débordement''' au compteur, qui est mise à 1 quand le compteur déborde.
Sur les compteurs/décompteurs, il y a une entrée '''''Count Direction''''' qui décide s'il faut compter ou décompter. Typiquement, elle est à 1 s'il faut compter et 0 s'il faut décompter.
[[File:Digital counter signals.jpg|centre|vignette|upright=2|Interface d'un compteur-décompteur.]]
Dans les schémas qui vont suivre, les entrées Enable ne sont pas représentées. Il est sous-entendu qu'il y a une entrée ''Enable'' pour tous les compteurs qui vont suivre. Il existe deux méthodes pour créer de tels compteurs : la première donne ce qu'on appelle des compteurs asynchrones, et l'autre des compteurs synchrones.
==L'intérieur d'un compteur==
A une exception bien précise qu'on abordera plus bas, les compteurs sont composés d'un registre, qui mémorise le décompte, couplé à un circuit '''incrémenteur'''. Nous avions déjà abordé l'incrémenteur dans un chapitre précédent, aussi je ne vais pas ré-expliquer comment il est conçu. Tout ce qu'il faut retenir est qu'il y en a plusieurs types, le plus simple étant celui à propagation de retenue.
[[File:Fonctionnement d'un compteur (décompteur), schématique.jpg|centre|vignette|upright=2|Fonctionnement d'un compteur (décompteur), schématique. Le pas du compteur précise de combien on incrémente le compteur par cycle d'horloge.]]
===Les compteurs synchrones basiques===
L'incrémenteur le plus simple, à propagation de retenue, est fabriqué en enchainant des ''demi-additionneurs'' les uns à la suite des autres. Pour rappel, un demi-additionneur additionne deux bits. Ici, il additionne un bit de l'opérande, la retenue des colonnes précédentes. Pour le bit de poids faible, la retenue est forcé à 1. Si on combine un incrémenteur à propagation de retenue avec un registre, on obtient ce compteur :
[[File:Compteur synchrone à incrémenteur.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules D.]]
Une simplification permet de faire disparaitre les portes XOR. Les portes XOR servent ici d'inverseur commandable, commandé par la retenue entrante. Elles inversent le contenu de la bascule quand la retenue entrante vaut 1, elles laissent la bascule inchangée si la retenue vaut 0. Or, nous avons déjà une bascule qui inverse son contenu sous certaines condition : la bascule T ! Il est donc possible de fusionner chaque bascule D avec la porte XOR associée, pour donner une bascule T. Le circuit final est celui-ci :
[[File:Compteur synchrone à bascules T.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules T.]]
Il est possible d'optimiser le circuit avec les optimisations vues dans le chapitre sur les incrémenteurs. La première est le ''carry skip'' qui fait l'incrémentation non pas bit par bit, mais par paquets de deux bits. La seconde est l'anticipation de retenue. Et ces deux optimisations se marient bien avec ce qui va suivre.
===Les compteurs en cascade===
[[File:8 Bit Counter.svg|vignette|Compteur 8 bits fabriqués avec deux compteurs 4 bits.]]
Il est possible de concevoir des compteurs à partir de compteurs plus petits, mis en cascade. Par exemple, en créant un compteur 8 bits à partir de deux compteurs 4 bits, enchainés l'un à la suite de l'autre. Idem avec 4 compteurs 4 bits, ce qui fait un compteur de 16 bits.
Les compteurs mis en cascade ont les mêmes entrées et sorties que les compteurs normaux, avec cependant un détail très important : ils ont une entrée pour la retenue entrante, et une sortie pour la retenue sortante. L'entrée pour la retenue entrante précise ce qui doit être additionné au bit de poids faible. Si elle vaut zéro, l'incrémenteur n'incrémente pas l'opérande. Si elle vaut 1, le compteur est incrémenté. L'entrée de retenue fait office d'entrée ''Count Enable'', qui active ou désactive l'incrémentation.
Le compteur a aussi une sortie de débordement, qui indique que le compteur déborde. Il se trouve que cette sortie fournit la retenue pour le compteur suivant. La retenue peut être calculée en utilisant des optimisations comme l'anticipation de retenue, ce qui veut dire qu'elle est calculée sans propager les retenues, directement à partir des bits de l'opérande. Concrètement, la retenue est calculée en faisant un ET logique entre tous les bits du décompte.
[[File:Cascadable binary up-counter.jpg|centre|vignette|upright=2|Cascadable binary up-counter]]
Les compteurs sont mis en cascade de la manière suivante : leur sortie de débordement est connectée sur l'entrée ''Enable'' du compteur suivant, celle qui déclenche l'incrémentation du compteur. La sortie de débordement est notée RCO dans les schéma qui suivent, nous verrons pourquoi dans le prochaine paragraphe.
[[File:Cascaded binary counters.jpg|centre|vignette|upright=2.5|Cascaded binary counters]]
===Les compteurs modulo===
La plupart des compteurs comptent de 0 à <math>2^n - 1</math>, avec <math>n</math> la taille du compteur. Mais d'autres compteurs ne comptent pas jusque-là. Par exemple, certains compteurs ne comptent que jusqu'à 10, 150, etc. Ils sont appelés des '''compteurs ''modulo'''''. Prenons un compteur modulo 6, par exemple : il compte de 0 à 5, et est remis immédiatement à zéro quand il atteint 6. Il compte donc comme suit : 0, 1, 2, 3, 4, 5, 0, 1, 2, ...
Les compteurs ''modulo'' sont construits à partir d'un compteur normal, couplé à un circuit comparateur qui remet à zéro le registre quand il atteint la valeur maximale. Par exemple, on peut imaginer un compteur modulo 6 est construit à partir d'un compteur 4 bits qui compte de 0 à 15 (donc un compteur modulo 16), mais qui est remis à zéro quand il atteint 6. Le circuit comparateur vérifie si la valeur maximale 6 est atteinte et met à 1 l'entrée ''Reset'' si c'est le cas. Le comparateur est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Td4bfig4.png|centre|vignette|upright=1.5|Compteur modulo N.]]
Pour une minorité de compteurs ''modulo'', la valeur maximale est configurable. Pour cela, le compteur est associé à un ''registre de configuration'' qui mémorise la valeur maximale souhaitée. A chaque cycle d'horloge, la valeur dans le compteur est comparée au registre de configuration. Si elles sont identiques, le compteur est remis à zéro. Le compteur est associé au registre de configuration et à un comparateur qui vérifie que les deux sont égaux. Pour le moment, nous ne savons pas faire de circuits comparateurs, ce qui fait qu'on ne peut pas expliquer ce circuit plus en détail.
[[File:4 Bit Counter Prog 1.svg|centre|vignette|upright=2|Compteur 4 bits à valeur maximale programmable.]]
Les compteurs ont tous une sortie de débordement, qui indique quand le compteur déborde. Pour les compteurs modulo, la sortie n'est autre que la sortie du comparateur. Pour les compteurs non-modulo, la sortie de débordement est une sortie du circuit combinatoire qui incrémente le compteur.
==Les ''timers'' : compter des durées, compter des cycles d'horloge==
Les '''''timers''''', aussi appelés ''Programmable interval timer'', sont des circuits capables de compter des durées, exprimées en cycles d'horloge. Leur fonctionnement est assez simple : ils émettent un signal quand un certain nombre de cycles est écoulé, ce nombre de cycles étant configurable. On peut ainsi générer un signal qui surviendra après 50 cycles d'horloge, ou après 100 cycles d'horloge, etc. Le signal en question est disponible sur une sortie de 1 bit, et correspond tout simplement au fait que cette sortie est mise à 1, pendant un cycle d'horloge.
Les ''timers'' peuvent compter de deux manières différentes, appelées '''mode une fois''' et '''mode périodique'''.
* En mode une fois, le ''timer'' s'arrête une fois qu'il a atteint la limite configurée. On doit le réinitialiser manuellement, par l'intermédiaire du logiciel, pour l'utiliser une nouvelle fois. Cela permet de compter une certaine durée, exprimée en nombre de cycles d'horloge.
* En mode périodique, le ''timer'' se réinitialise automatiquement avec la valeur de départ, ce qui fait qu'il reboucle à l'infini.
[[File:Programmable interval timer timing diagram.jpg|centre|vignette|upright=2.5|Exemple d'un ''Timer'' périodique, qui émet un signal tous les 6 cycles d'horloge.]]
Les ''timers'' sont composés d'un compteur/décompteur cadencé par un signal d'horloge. Avec un compteur, le compteur est initialisé à 0, puis est incrémenté à chaque signal d'horloge, jusqu’à atteindre la valeur limite où il génère un signal. Pour un décompteur, c'est la même chose, sauf que le décompteur est initialisé à sa valeur limite, est décrémenté à chaque cycle, et envoie un signal quand il atteint 0. Les ''timers'' basés sur des décompteurs sont nettement plus simples que les autres, ce qui fait qu'ils sont plus utilisés.
Pour que les ''timers'' soient configurables, on doit pouvoir préciser combien de cycles il faut (dé-)compter avant d'émettre un signal. On peut ainsi préciser s'il faut émettre le signal après 32 cycles d'horloge, après les 50 cycles, tous les 129 cycles, etc. Le nombre de cycles en question est envoyé sur une entrée d’initialisation du compteur. Il peut être mémorisé dans un '''registre d’intervalle''' dédié.
[[File:Programmable interval timer.jpg|centre|vignette|upright=2|''Timer'' périodique basé sur un décompteur. La porte NOR détecte si le décompteur atteint zéro.]]
Un ordinateur est rempli de ''timers'' divers. Dans ce qui va suivre, nous allons voir les principaux ''timers'', qui sont actuellement intégrés dans les PC modernes. Ils se trouvent sur la carte mère ou dans le processeur, tout dépend du ''timer''.
===Le ''watchdog timer''===
Le '''''watchdog timer''''' est un ''timer'' spécifique dont le but est de redémarrer automatiquement l'ordinateur si jamais celui-ci ne répond plus ou plante. Beaucoup de PC s'en passent, mais ce ''timer'' est très fréquent dans les architectures embarquées. Le ''watchdog timer'' est un compteur/décompteur qui doit être réinitialisé régulièrement. S'il n'est pas réinitialisé, le ''watchdog timer'' déborde (revient à 0 ou atteint 0) et envoie un signal qui redémarre le système. Le système est conçu pour réinitialiser le ''watchdog timer'' régulièrement, ce qui signifie que le système n'est pas censé redémarrer. Si jamais le système dysfonctionne gravement, le système ne pourra pas réinitialiser le ''watchdog timer'' et le système est redémarré automatiquement ou mis en arrêt.
[[File:SimpleWatchdogTimer.gif|centre|vignette|upright=2|Le ''Watchdog Timer'' et l'ordinateur.]]
===Le ''Time Stamp Counter'' des processeurs x86===
Tous les processeurs des PC actuels sont des processeurs dits x86. Nous ne pouvons pas expliquer ce que cela signifie pour le moment, retenez juste ce terme. Sachez que tous les processeurs x86 contiennent un compteur de 64 bits, appelé le '''''Time Stamp Counter''''', qui mémorise le nombre de cycles d'horloge qu'a effectué le processeur depuis son démarrage. Les programmes peuvent accéder à ce registre assez facilement, ce qui est utile pour faire des mesures ou comparer les performances de deux applications. Il permet de compter combien de cycles d'horloge met un morceau de code à s’exécuter, combien de cycles prend une instruction à s’exécuter, etc. Les processeurs non-x86 ont un registre équivalent, que ce soit les processeurs ARM ou d'autres.
Malheureusement, ce compteur est tombé en désuétude pour tout un tas de raisons. La principale est que les processeurs actuels font varier leur fréquence suivant les besoins. Ils augmentent leur fréquence quand on leur demande de faire beaucoup de calculs, et se mettent en mode basse(fréquence pour économiser de l'énergie si on ne leur demande pas grand chose. Avec une fréquence variable, le ''Time Stamp Counter'' perd complétement en fiabilité. Intel a tenté de corriger ce défaut en incrémentant ce registre à une fréquence constante, différente de celle du processeur, ce qui est encore le cas sur les processeurs Intel actuels. Le comportement est un peu différent sur les processeurs AMD, qui cadencent ce ''timer'' à la fréquence du processeur mais utilisent des mécanismes de synchronisation assez complexes pour corriger l'effet de la fréquence variable.
===L'horloge temps réel===
L''''horloge temps réel''' est un ''timer'' qui génère une fréquence de 1024 Hz, soit près d'un Kilohertz. Dans ce qui suit, nous la noterons RTC, ce qui est l'acronyme du terme anglais ''Real Time Clock''. La RTC prend en entrée un signal d'horloge de 32KHz, généré par un oscillateur à Quartz, et fournit en sortie un signal de fréquence 32 fois plus faible, c'est à dire de 1 KHz. Pour cela, elle est réglée en mode répétitif et son décompteur interne est initialisé à 32. La RTC génère donc un signal toutes les millisecondes, qui est envoyé au processeur. On peut, en théorie, changer la fréquence de la RTC, mais c'est rarement une bonne idée.
En théorie, la RTC permet de compter des durées assez courtes, comme le ''ping'' (le temps de latence d'un réseau, pour simplifier), le temps de rafraichissement de l'écran, ou bien d'autres choses. Mais dans les faits, l'horloge temps réel sa fréquence n'aide pas : 1024 Hz est proche de 1000, mais pas assez pour faire des mesures à la milliseconde près, chose qui est nécessaire pour mesurer le ''ping'' ou d'autres choses utiles. A la place, l'ordinateur l'utilise pour que l'ordinateur soit toujours à l'heure. L'ordinateur sait quelle heure il est avec une précision de l'ordre de la seconde (vous pouvez regarder le bureau de Windows dans le coin inférieur droite de votre écran pour vous en convaincre).
===Le ''Programmable Interval Timer'' : l'Intel 8253===
L'Intel 8253 était un ''timer'' programmable autrefois soudé sur les cartes mères des premiers PC. Il fût suivi par l'Intel 8254, qui en était une légère amélioration. Il était cadencé par une horloge maitre, générée par un oscillateur à Quartz, dont la fréquence est de 32 768 Hertz, soit 2^15 cycles d'horloge par seconde. S'il n'est plus présent dans un boitier sur la carte mère, on trouve toujours un circuit semblable au 8253 à l'intérieur du ''chipset'' de la carte mère, voire à l'intérieur du processeur, pour des raisons de compatibilité. L'intérieur de l'Intel 8253 est illustré ci-dessous. Nous allons expliquer l'ensemble de ce schéma, rassurez-vous, mais les explications seront plus simples à comprendre si vous survolez ce schéma en premier lieu.
[[File:Intel 8253 block diagram.svg|centre|vignette|upright=2|Intel 8253, intérieur.]]
L'Intel 8253 contient trois compteurs de 16 bits, numérotés 0, 1 et 2. Pour chaque compteur, l'entrée CLOCK est celle de l'horloge de 32 MHz, l'entrée GATE active ou désactive le compteur, la sortie fournit le signal voulu et/ou la fréquence de sortie. Les trois compteurs étaient utilisés pour dériver plusieurs fréquences allant de 18,2 Hz à environ 500 KHz. Par exemple, il était utilisé par défaut pour le rafraichissement de la mémoire (D)RAM, mais il était souvent reprogrammé pour servir à générer des fréquences spécifiques par le BIOS ou la carte graphique.
[[File:Intel 8253 and 8254.svg|vignette|Intel 8253 and 8254]]
L'Intel 8253 lui-même possède plusieurs entrées et sorties. En premier lieu, on voit un port de 8 bits connecté aux trois compteurs, qui permet à l'Intel 8253 de communiquer avec le reste de l'ordinateur. La communication se fait dans les deux sens : soit de l'ordinateur vers les compteurs, soit des compteurs vers l'ordinateur. Dans le sens ordinateur -> compteurs, cela permet à l'ordinateur de programmer les compteurs, de les initialiser. Dans l'autre sens, cela permet de récupérer le contenu des compteurs, même si ce n'est pas très utilisé. Il y a aussi 5 entrées de configuration :
* Deux bits A0 et A1 pour sélectionner le compteur voulu avec son numéro.
* Un bit RD à mettre à 0 pour que l'ordinateur récupère le compteur sélectionné sur le port de 8 bits.
* Un bit WR à mettre à 0 pour que l'ordinateur modifie le compteur sélectionné, en envoyant le nombre pour l'initialisation sur le port de 8 bits.
* Un bit CS qui active ou désactive l'Intel 8253 et permet de l'allumer ou de l’éteindre.
L'Intel 8253 intégre un registre de 8 bits, le ''Control Word register'' qui mémorise la configuration de l'Intel 8253. Pour programmer les trois compteurs, il faut écrire un mot de 8 bits dans ce ''Control Word register''. Pour écrire dans le ''Control Word register'', il faut mettre le bit CS à 0 (on active l'Intel 8253), mettre le bit RDà 1 , le bit WR à 0 le bit WR (on indique qu'on fait une écriture), sélectionner le ''Control Word register'' en mettant les deux bits A0 et A1 à 1, puis envoyer la configuration du ''Control Word register'' sur le port de 8 bits.
===Le ''High Precision Event Timer'' (HPET)===
De nos jours, l'horloge temps réel et l'Intel 8253/8254 tendent à être remplacé par un autre ''timer'', le ''High Precision Event Timer'' (HPET). Il s'agit d'un compteur de 64 bits, dont la fréquence est d'au moins 10 MHz. Il s'agit bien d'un compteur et non d'un décompteur. Il gère nativement plusieurs valeurs limites à laquelle générer un signal, qui sont configurables. Pour cela, il est couplé à plusieurs comparateurs, chacun associé à un registre pour mémoriser la valeur limite. Il doit y avoir au moins trois comparateurs/registres, mais le nombre peut monter jusqu’à 256.
[[File:High Precision Event Timer.png|centre|vignette|upright=2|High Precision Event Timer]]
Il faut noter que les systèmes d'exploitation conçus avant le HPET ne peuvent pas l'utiliser, pour des raisons de compatibilité matérielle. C'est le cas de Windows XP avant le Service Pack 3. C'est la raison pour laquelle les cartes mères émulent RTC et PIT dans leurs circuits. D'ailleurs, pour économiser des circuits, les cartes mères modernes émulent le PIT et la RTC avec le HPET : le premier comparateur fournisse la fréquence de 1024 Hz de la RTC, 3 autres comparateurs remplacent l'Intel 8253.
Le HPET gère de nombreux modes de fonctionnement : ses comparateurs peuvent être configuré en mode une fois ou périodique, on peut lui demander d'émuler la RTC et le PIT, etc. Chaque comparateur doit pouvoir fonctionner en mode une fois, et au moins un comparateur doit pouvoir fonctionner en mode périodique. Aussi, il contient aussi 3 registres de configuration. Notons qu'il est aussi possible de lire ou écrire dans le compteur de 64 bits, mais ce n'est pas recommandé.
==Les compteurs en code Gray==
Les compteurs classiques encodent leur décompte en binaire normal sur <math>n</math> bits, mais il faut savoir que d'autres compteurs utilisent le BCD, d'autre le code Gray, etc. Nous allons voir dans ce qui suit ceux qui comptent en code Gray.
Pour rappel, le code Gray permet de coder des nombres d'une manière un peu différente du binaire normal. Son avantage principal est que lorsqu'on incrémente ou décrémente un nombre, seul un bit change ! Pour comparer, en binaire normal, lorsqu'on passe incrémente un nombre, il peut y avoir plusieurs bits qui changent. La moyenne est d'environ deux bits par incrémentation. Les compteurs en code Gray ont beaucoup d'avantages, qui sont tous liés à cette propriété.
Le premier l'absence d'état transitoires douteux. Le problème est que les bits modifiés par une incrémentation ne le sont pas en même temps. Les bits de poids faibles sont modifiés avant les autres. Évidemment, à la fin du calcul, on obtient le résultat final, correct. Mais pendant le temps de calcul, le compteur peut se retrouver dans un état transitoire, où seuls les bits de poids faibles ont été modifiés. C'est un problème si le contenu de ce compteur est relié à des circuits rapides, qui peuvent voir cet état transitoire, mais ne le doivent pas sous peine de dysfonctionner. L'usage de compteurs en code Gray permet d'éviter ce problème : vu que seul un bit est modifié lors d'une incrémentation/décrémentation, les états transitoires n'existent tout simplement pas.
Un exemple typique, évoqué dans les chapitres précédents, est l'échange d'informations entre deux domaines d'horloge. Pour rappel, il arrive que deux portions d'un circuit imprimé aillent à des fréquences différences : on dit que le circuit à plusieurs domaines d'horloge. S'il faut échanger des informations entre ces deux domaines d'horloge, divers problèmes surviennent. Un domaine d'horloge sera plus rapide que l'autre, et pourra voir les états transitoires invisible de l'autre circuit. Et par voir, on veut dire qu'il les prendra pour des états valides, ce qui fera dysfonctionner le circuit. Pour éviter cela, diverses techniques de croisement de domaines d'horloge existent. Et les compteurs Gray en font partie : si un domaine d'horloge utilise la valeur d'un compteur de l'autre, mieux vaut que ce compteur soit un compteur Gray. Et cette situation est assez fréquente !
Un autre avantage mineurs est que la consommation d'énergie de ces compteurs est bien plus réduite qu'avec un compteur normal. Rappelons que pour fonctionner, les circuits électroniques consomment un peu d'électricité. Et la majeure partie de cette consommation sert à faire passer un bit de 0 à 1 ou de 1 à 0. Ce qui fait que quand un compteur est incrémenté ou décrémenté, cela consomme un peu d'énergie électrique.
La moyenne pour un compteur binaire normal est de 2 bits changés par incrémentation/décrémentation, contre un seul pour un compteur Gray, on devine que ces derniers consomment deux fois moins d'énergie par incrémentation. Et cet avantage a des effets en cascade sur les circuits qui suivent ce compteur. Si l'entrée de ces circuits ne change que d'un seul bit, alors leur état changera moins que si c'était deux bits. Les circuits qui suivent vont donc moins consommer.
Un autre avantage en matière de consommation énergétique est lié auxs transitions d'état douteuses. Les circuits connectés au compteur vont voir ces transitions d'état douteuses et modifier leur état interne en réaction. Bien sur, l'état final correct sera atteint une fois que le compteur sera stabilisé, ce qui effacera ces états transitoires intermédiaires. Mais chaque état intermédiaire transitoire correspond à un changement d'état, donc à une consommation d'énergie. En supprimant ces états transitoires, on réduit fortement la consommation d'énergie du circuit. Cela vaut pour le compteur Gray lui-même, mais aussi sur tous les circuits qui ont ce compteur comme entrée !
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les registres et mémoires adressables
| prevText=Les registres et mémoires adressables
| next=Les registres à décalage et les LSFR
| nextText=Les registres à décalage et les LSFR
}}
</noinclude>
oo5i2ylh94l6crt3ozzhsd09dk25583
768645
768644
2026-06-25T16:56:32Z
Mewtow
31375
/* Les timers : compter des durées, compter des cycles d'horloge */
768645
wikitext
text/x-wiki
Les '''compteurs/décompteurs''' sont des circuits électroniques qui mémorisent un nombre et l'incrémentent à la demande. En clair, ce sont des registres améliorés afin de supporter l'incrémentation et la décrémentation. Pour donner un exemple d'utilisation, imaginez un circuit qui compte le nombre de voitures dans un parking dans la journée. Pour cela, vous allez prendre deux circuits qui détectent respectivement l'entrée ou la sortie d'une voiture, et un compteur. Le compteur est initialisé à 0 quand le parking est vide, puis est incrémenté à chaque entrée de voiture, décrémenté à chaque sortie. Les exemples de ce type sont suffisamment nombreux pour qu'on dédie un chapitre aux compteurs.
[[File:Binary counter.gif|vignette|Illustration du fonctionnement d'un compteur modulaire binaire de 4 bits, avec un pas de compteur de 1 (le contenu est augmenté de 1 à chaque mise à jour).]]
Un compteur mémorise un nombre qui est incrémenté ou décrémenté au besoin. Le nombre mémorisé sera appelé le '''décompte''' dans ce qui suit. Il est mémorisé dans un registre à l'intérieur du compteur. Au passage, le nombre de bits <math>n</math> du compteur est appelé la '''taille du compteur''', par analogie avec les registres. Il faut cependant faire la différence entre les ''compteurs'' d'un côté et les ''décompteurs'' de l'autre. Les compteurs incrémentent le décompte, les décompteurs le décrémentent, les compteurs-décompteurs peuvent faire les deux.
==L'interface d'un compteur/décompteur==
Les compteurs et décompteurs sont des circuits synchrones et ont donc une entrée d'horloge. Les compteurs les plus simples incrémentent leur contenu à chaque cycle d'horloge, mais les plus fréquents n'incrémentent le décompte que sur demande. Pour cela, ils disposent d'une entrée '''''Count Enable''''', similaire à l'entrée ''Enable'' des registres, séparée de l'entrée d'horloge. Le décompte est incrémenté/décrémenté seulement si l'entrée Enable est à 1, lors d'un front adéquat sur le signal d'horloge.
Les compteurs ont aussi une entrée '''''Reset''''' qui permet de les remettre à zéro. Il y a parfois une entrée qui permet d'initialiser le compteur à une valeur par défaut, non-nulle. Par exemple, on peut initialiser le décompte à la valeur 5, ou une autre. Pour cela, le compteur dispose de deux entrées : une entrée sur laquelle envoyer le décompte initial, une entrée pour autoriser la réinitialisation. Les entrées en question sont appelées '''''Preload Data''''' et '''''Preload Enable'''''. La seconde entrée est parfois distincte de l'entrée de réinitialisation, pour permettre de réinitialiser le compteur soit à zéro, soit à la valeur voulue.
Il peut être utile de prévenir quand un débordement d'entier a lieu, à savoir quand le compteur n'a pas assez de bits pour encoder le décompte. Le compteur est alors remis à zéro, dans la plupart des cas. Mais il faut prévenir que le compteur a débordé, ce qui est utile pour fabriquer des circuits diviseurs de fréquence et des ''timers''. Pour cela, on ajoute une '''sortie de débordement''' au compteur, qui est mise à 1 quand le compteur déborde.
Sur les compteurs/décompteurs, il y a une entrée '''''Count Direction''''' qui décide s'il faut compter ou décompter. Typiquement, elle est à 1 s'il faut compter et 0 s'il faut décompter.
[[File:Digital counter signals.jpg|centre|vignette|upright=2|Interface d'un compteur-décompteur.]]
Dans les schémas qui vont suivre, les entrées Enable ne sont pas représentées. Il est sous-entendu qu'il y a une entrée ''Enable'' pour tous les compteurs qui vont suivre. Il existe deux méthodes pour créer de tels compteurs : la première donne ce qu'on appelle des compteurs asynchrones, et l'autre des compteurs synchrones.
==L'intérieur d'un compteur==
A une exception bien précise qu'on abordera plus bas, les compteurs sont composés d'un registre, qui mémorise le décompte, couplé à un circuit '''incrémenteur'''. Nous avions déjà abordé l'incrémenteur dans un chapitre précédent, aussi je ne vais pas ré-expliquer comment il est conçu. Tout ce qu'il faut retenir est qu'il y en a plusieurs types, le plus simple étant celui à propagation de retenue.
[[File:Fonctionnement d'un compteur (décompteur), schématique.jpg|centre|vignette|upright=2|Fonctionnement d'un compteur (décompteur), schématique. Le pas du compteur précise de combien on incrémente le compteur par cycle d'horloge.]]
===Les compteurs synchrones basiques===
L'incrémenteur le plus simple, à propagation de retenue, est fabriqué en enchainant des ''demi-additionneurs'' les uns à la suite des autres. Pour rappel, un demi-additionneur additionne deux bits. Ici, il additionne un bit de l'opérande, la retenue des colonnes précédentes. Pour le bit de poids faible, la retenue est forcé à 1. Si on combine un incrémenteur à propagation de retenue avec un registre, on obtient ce compteur :
[[File:Compteur synchrone à incrémenteur.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules D.]]
Une simplification permet de faire disparaitre les portes XOR. Les portes XOR servent ici d'inverseur commandable, commandé par la retenue entrante. Elles inversent le contenu de la bascule quand la retenue entrante vaut 1, elles laissent la bascule inchangée si la retenue vaut 0. Or, nous avons déjà une bascule qui inverse son contenu sous certaines condition : la bascule T ! Il est donc possible de fusionner chaque bascule D avec la porte XOR associée, pour donner une bascule T. Le circuit final est celui-ci :
[[File:Compteur synchrone à bascules T.jpg|centre|vignette|upright=2|Compteur synchrone utilisant des bascules T.]]
Il est possible d'optimiser le circuit avec les optimisations vues dans le chapitre sur les incrémenteurs. La première est le ''carry skip'' qui fait l'incrémentation non pas bit par bit, mais par paquets de deux bits. La seconde est l'anticipation de retenue. Et ces deux optimisations se marient bien avec ce qui va suivre.
===Les compteurs en cascade===
[[File:8 Bit Counter.svg|vignette|Compteur 8 bits fabriqués avec deux compteurs 4 bits.]]
Il est possible de concevoir des compteurs à partir de compteurs plus petits, mis en cascade. Par exemple, en créant un compteur 8 bits à partir de deux compteurs 4 bits, enchainés l'un à la suite de l'autre. Idem avec 4 compteurs 4 bits, ce qui fait un compteur de 16 bits.
Les compteurs mis en cascade ont les mêmes entrées et sorties que les compteurs normaux, avec cependant un détail très important : ils ont une entrée pour la retenue entrante, et une sortie pour la retenue sortante. L'entrée pour la retenue entrante précise ce qui doit être additionné au bit de poids faible. Si elle vaut zéro, l'incrémenteur n'incrémente pas l'opérande. Si elle vaut 1, le compteur est incrémenté. L'entrée de retenue fait office d'entrée ''Count Enable'', qui active ou désactive l'incrémentation.
Le compteur a aussi une sortie de débordement, qui indique que le compteur déborde. Il se trouve que cette sortie fournit la retenue pour le compteur suivant. La retenue peut être calculée en utilisant des optimisations comme l'anticipation de retenue, ce qui veut dire qu'elle est calculée sans propager les retenues, directement à partir des bits de l'opérande. Concrètement, la retenue est calculée en faisant un ET logique entre tous les bits du décompte.
[[File:Cascadable binary up-counter.jpg|centre|vignette|upright=2|Cascadable binary up-counter]]
Les compteurs sont mis en cascade de la manière suivante : leur sortie de débordement est connectée sur l'entrée ''Enable'' du compteur suivant, celle qui déclenche l'incrémentation du compteur. La sortie de débordement est notée RCO dans les schéma qui suivent, nous verrons pourquoi dans le prochaine paragraphe.
[[File:Cascaded binary counters.jpg|centre|vignette|upright=2.5|Cascaded binary counters]]
===Les compteurs modulo===
La plupart des compteurs comptent de 0 à <math>2^n - 1</math>, avec <math>n</math> la taille du compteur. Mais d'autres compteurs ne comptent pas jusque-là. Par exemple, certains compteurs ne comptent que jusqu'à 10, 150, etc. Ils sont appelés des '''compteurs ''modulo'''''. Prenons un compteur modulo 6, par exemple : il compte de 0 à 5, et est remis immédiatement à zéro quand il atteint 6. Il compte donc comme suit : 0, 1, 2, 3, 4, 5, 0, 1, 2, ...
Les compteurs ''modulo'' sont construits à partir d'un compteur normal, couplé à un circuit comparateur qui remet à zéro le registre quand il atteint la valeur maximale. Par exemple, on peut imaginer un compteur modulo 6 est construit à partir d'un compteur 4 bits qui compte de 0 à 15 (donc un compteur modulo 16), mais qui est remis à zéro quand il atteint 6. Le circuit comparateur vérifie si la valeur maximale 6 est atteinte et met à 1 l'entrée ''Reset'' si c'est le cas. Le comparateur est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Td4bfig4.png|centre|vignette|upright=1.5|Compteur modulo N.]]
Pour une minorité de compteurs ''modulo'', la valeur maximale est configurable. Pour cela, le compteur est associé à un ''registre de configuration'' qui mémorise la valeur maximale souhaitée. A chaque cycle d'horloge, la valeur dans le compteur est comparée au registre de configuration. Si elles sont identiques, le compteur est remis à zéro. Le compteur est associé au registre de configuration et à un comparateur qui vérifie que les deux sont égaux. Pour le moment, nous ne savons pas faire de circuits comparateurs, ce qui fait qu'on ne peut pas expliquer ce circuit plus en détail.
[[File:4 Bit Counter Prog 1.svg|centre|vignette|upright=2|Compteur 4 bits à valeur maximale programmable.]]
Les compteurs ont tous une sortie de débordement, qui indique quand le compteur déborde. Pour les compteurs modulo, la sortie n'est autre que la sortie du comparateur. Pour les compteurs non-modulo, la sortie de débordement est une sortie du circuit combinatoire qui incrémente le compteur.
==Les ''timers'' : compter des durées, compter des cycles d'horloge==
Les '''''timers''''', aussi appelés ''Programmable interval timer'', sont des circuits capables de compter des durées, exprimées en cycles d'horloge. Leur fonctionnement est assez simple : ils émettent un signal quand un certain nombre de cycles est écoulé, ce nombre de cycles étant configurable. On peut ainsi générer un signal qui surviendra après 50 cycles d'horloge, ou après 100 cycles d'horloge, etc. Le signal en question est disponible sur une sortie de 1 bit, et correspond tout simplement au fait que cette sortie est mise à 1, pendant un cycle d'horloge.
Les ''timers'' peuvent compter de deux manières différentes, appelées ''mode une fois'' et ''mode périodique''.
* En '''mode une fois''', le ''timer'' s'arrête une fois qu'il a atteint la limite configurée. On doit le réinitialiser manuellement, par l'intermédiaire du logiciel, pour l'utiliser une nouvelle fois. Cela permet de compter une certaine durée, exprimée en nombre de cycles d'horloge.
* En '''mode périodique''', le ''timer'' se réinitialise automatiquement avec la valeur de départ, ce qui fait qu'il reboucle à l'infini.
[[File:Programmable interval timer timing diagram.jpg|centre|vignette|upright=2.5|Exemple d'un ''Timer'' périodique, qui émet un signal tous les 6 cycles d'horloge.]]
Les ''timers'' sont composés d'un compteur/décompteur cadencé par un signal d'horloge. Avec un compteur, le compteur est initialisé à 0, puis est incrémenté à chaque signal d'horloge, jusqu’à atteindre la valeur limite où il génère un signal. Pour un décompteur, c'est la même chose, sauf que le décompteur est initialisé à sa valeur limite, est décrémenté à chaque cycle, et envoie un signal quand il atteint 0. Les ''timers'' basés sur des décompteurs sont nettement plus simples que les autres, ce qui fait qu'ils sont plus utilisés.
Pour que les ''timers'' soient configurables, on doit pouvoir préciser combien de cycles il faut (dé-)compter avant d'émettre un signal. On peut ainsi préciser s'il faut émettre le signal après 32 cycles d'horloge, après les 50 cycles, tous les 129 cycles, etc. Le nombre de cycles en question est envoyé sur une entrée d’initialisation du compteur. Il peut être mémorisé dans un '''registre d’intervalle''' dédié.
[[File:Programmable interval timer.jpg|centre|vignette|upright=2|''Timer'' périodique basé sur un décompteur. La porte NOR détecte si le décompteur atteint zéro.]]
Un ordinateur est rempli de ''timers'' divers. Dans ce qui va suivre, nous allons voir les principaux ''timers'', qui sont actuellement intégrés dans les PC modernes. Ils se trouvent sur la carte mère ou dans le processeur, tout dépend du ''timer''.
===Le ''watchdog timer''===
Le '''''watchdog timer''''' est un ''timer'' spécifique dont le but est de redémarrer automatiquement l'ordinateur si jamais celui-ci ne répond plus ou plante. Beaucoup de PC s'en passent, mais ce ''timer'' est très fréquent dans les architectures embarquées. Le ''watchdog timer'' est un compteur/décompteur qui doit être réinitialisé régulièrement. S'il n'est pas réinitialisé, le ''watchdog timer'' déborde (revient à 0 ou atteint 0) et envoie un signal qui redémarre le système. Le système est conçu pour réinitialiser le ''watchdog timer'' régulièrement, ce qui signifie que le système n'est pas censé redémarrer. Si jamais le système dysfonctionne gravement, le système ne pourra pas réinitialiser le ''watchdog timer'' et le système est redémarré automatiquement ou mis en arrêt.
[[File:SimpleWatchdogTimer.gif|centre|vignette|upright=2|Le ''Watchdog Timer'' et l'ordinateur.]]
===Le ''Time Stamp Counter'' des processeurs x86===
Tous les processeurs des PC actuels sont des processeurs dits x86. Nous ne pouvons pas expliquer ce que cela signifie pour le moment, retenez juste ce terme. Sachez que tous les processeurs x86 contiennent un compteur de 64 bits, appelé le '''''Time Stamp Counter''''', qui mémorise le nombre de cycles d'horloge qu'a effectué le processeur depuis son démarrage. Les programmes peuvent accéder à ce registre assez facilement, ce qui est utile pour faire des mesures ou comparer les performances de deux applications. Il permet de compter combien de cycles d'horloge met un morceau de code à s’exécuter, combien de cycles prend une instruction à s’exécuter, etc. Les processeurs non-x86 ont un registre équivalent, que ce soit les processeurs ARM ou d'autres.
Malheureusement, ce compteur est tombé en désuétude pour tout un tas de raisons. La principale est que les processeurs actuels font varier leur fréquence suivant les besoins. Ils augmentent leur fréquence quand on leur demande de faire beaucoup de calculs, et se mettent en mode basse(fréquence pour économiser de l'énergie si on ne leur demande pas grand chose. Avec une fréquence variable, le ''Time Stamp Counter'' perd complétement en fiabilité. Intel a tenté de corriger ce défaut en incrémentant ce registre à une fréquence constante, différente de celle du processeur, ce qui est encore le cas sur les processeurs Intel actuels. Le comportement est un peu différent sur les processeurs AMD, qui cadencent ce ''timer'' à la fréquence du processeur mais utilisent des mécanismes de synchronisation assez complexes pour corriger l'effet de la fréquence variable.
===L'horloge temps réel===
L''''horloge temps réel''' est un ''timer'' qui génère une fréquence de 1024 Hz, soit près d'un Kilohertz. Dans ce qui suit, nous la noterons RTC, ce qui est l'acronyme du terme anglais ''Real Time Clock''. La RTC prend en entrée un signal d'horloge de 32KHz, généré par un oscillateur à Quartz, et fournit en sortie un signal de fréquence 32 fois plus faible, c'est à dire de 1 KHz. Pour cela, elle est réglée en mode répétitif et son décompteur interne est initialisé à 32. La RTC génère donc un signal toutes les millisecondes, qui est envoyé au processeur. On peut, en théorie, changer la fréquence de la RTC, mais c'est rarement une bonne idée.
En théorie, la RTC permet de compter des durées assez courtes, comme le ''ping'' (le temps de latence d'un réseau, pour simplifier), le temps de rafraichissement de l'écran, ou bien d'autres choses. Mais dans les faits, l'horloge temps réel sa fréquence n'aide pas : 1024 Hz est proche de 1000, mais pas assez pour faire des mesures à la milliseconde près, chose qui est nécessaire pour mesurer le ''ping'' ou d'autres choses utiles. A la place, l'ordinateur l'utilise pour que l'ordinateur soit toujours à l'heure. L'ordinateur sait quelle heure il est avec une précision de l'ordre de la seconde (vous pouvez regarder le bureau de Windows dans le coin inférieur droite de votre écran pour vous en convaincre).
===Le ''Programmable Interval Timer'' : l'Intel 8253===
L'Intel 8253 était un ''timer'' programmable autrefois soudé sur les cartes mères des premiers PC. Il fût suivi par l'Intel 8254, qui en était une légère amélioration. Il était cadencé par une horloge maitre, générée par un oscillateur à Quartz, dont la fréquence est de 32 768 Hertz, soit 2^15 cycles d'horloge par seconde. S'il n'est plus présent dans un boitier sur la carte mère, on trouve toujours un circuit semblable au 8253 à l'intérieur du ''chipset'' de la carte mère, voire à l'intérieur du processeur, pour des raisons de compatibilité. L'intérieur de l'Intel 8253 est illustré ci-dessous. Nous allons expliquer l'ensemble de ce schéma, rassurez-vous, mais les explications seront plus simples à comprendre si vous survolez ce schéma en premier lieu.
[[File:Intel 8253 block diagram.svg|centre|vignette|upright=2|Intel 8253, intérieur.]]
L'Intel 8253 contient trois compteurs de 16 bits, numérotés 0, 1 et 2. Pour chaque compteur, l'entrée CLOCK est celle de l'horloge de 32 MHz, l'entrée GATE active ou désactive le compteur, la sortie fournit le signal voulu et/ou la fréquence de sortie. Les trois compteurs étaient utilisés pour dériver plusieurs fréquences allant de 18,2 Hz à environ 500 KHz. Par exemple, il était utilisé par défaut pour le rafraichissement de la mémoire (D)RAM, mais il était souvent reprogrammé pour servir à générer des fréquences spécifiques par le BIOS ou la carte graphique.
[[File:Intel 8253 and 8254.svg|vignette|Intel 8253 and 8254]]
L'Intel 8253 lui-même possède plusieurs entrées et sorties. En premier lieu, on voit un port de 8 bits connecté aux trois compteurs, qui permet à l'Intel 8253 de communiquer avec le reste de l'ordinateur. La communication se fait dans les deux sens : soit de l'ordinateur vers les compteurs, soit des compteurs vers l'ordinateur. Dans le sens ordinateur -> compteurs, cela permet à l'ordinateur de programmer les compteurs, de les initialiser. Dans l'autre sens, cela permet de récupérer le contenu des compteurs, même si ce n'est pas très utilisé. Il y a aussi 5 entrées de configuration :
* Deux bits A0 et A1 pour sélectionner le compteur voulu avec son numéro.
* Un bit RD à mettre à 0 pour que l'ordinateur récupère le compteur sélectionné sur le port de 8 bits.
* Un bit WR à mettre à 0 pour que l'ordinateur modifie le compteur sélectionné, en envoyant le nombre pour l'initialisation sur le port de 8 bits.
* Un bit CS qui active ou désactive l'Intel 8253 et permet de l'allumer ou de l’éteindre.
L'Intel 8253 intégre un registre de 8 bits, le ''Control Word register'' qui mémorise la configuration de l'Intel 8253. Pour programmer les trois compteurs, il faut écrire un mot de 8 bits dans ce ''Control Word register''. Pour écrire dans le ''Control Word register'', il faut mettre le bit CS à 0 (on active l'Intel 8253), mettre le bit RDà 1 , le bit WR à 0 le bit WR (on indique qu'on fait une écriture), sélectionner le ''Control Word register'' en mettant les deux bits A0 et A1 à 1, puis envoyer la configuration du ''Control Word register'' sur le port de 8 bits.
===Le ''High Precision Event Timer'' (HPET)===
De nos jours, l'horloge temps réel et l'Intel 8253/8254 tendent à être remplacé par un autre ''timer'', le ''High Precision Event Timer'' (HPET). Il s'agit d'un compteur de 64 bits, dont la fréquence est d'au moins 10 MHz. Il s'agit bien d'un compteur et non d'un décompteur. Il gère nativement plusieurs valeurs limites à laquelle générer un signal, qui sont configurables. Pour cela, il est couplé à plusieurs comparateurs, chacun associé à un registre pour mémoriser la valeur limite. Il doit y avoir au moins trois comparateurs/registres, mais le nombre peut monter jusqu’à 256.
[[File:High Precision Event Timer.png|centre|vignette|upright=2|High Precision Event Timer]]
Il faut noter que les systèmes d'exploitation conçus avant le HPET ne peuvent pas l'utiliser, pour des raisons de compatibilité matérielle. C'est le cas de Windows XP avant le Service Pack 3. C'est la raison pour laquelle les cartes mères émulent RTC et PIT dans leurs circuits. D'ailleurs, pour économiser des circuits, les cartes mères modernes émulent le PIT et la RTC avec le HPET : le premier comparateur fournisse la fréquence de 1024 Hz de la RTC, 3 autres comparateurs remplacent l'Intel 8253.
Le HPET gère de nombreux modes de fonctionnement : ses comparateurs peuvent être configuré en mode une fois ou périodique, on peut lui demander d'émuler la RTC et le PIT, etc. Chaque comparateur doit pouvoir fonctionner en mode une fois, et au moins un comparateur doit pouvoir fonctionner en mode périodique. Aussi, il contient aussi 3 registres de configuration. Notons qu'il est aussi possible de lire ou écrire dans le compteur de 64 bits, mais ce n'est pas recommandé.
==Les compteurs en code Gray==
Les compteurs classiques encodent leur décompte en binaire normal sur <math>n</math> bits, mais il faut savoir que d'autres compteurs utilisent le BCD, d'autre le code Gray, etc. Nous allons voir dans ce qui suit ceux qui comptent en code Gray.
Pour rappel, le code Gray permet de coder des nombres d'une manière un peu différente du binaire normal. Son avantage principal est que lorsqu'on incrémente ou décrémente un nombre, seul un bit change ! Pour comparer, en binaire normal, lorsqu'on passe incrémente un nombre, il peut y avoir plusieurs bits qui changent. La moyenne est d'environ deux bits par incrémentation. Les compteurs en code Gray ont beaucoup d'avantages, qui sont tous liés à cette propriété.
Le premier l'absence d'état transitoires douteux. Le problème est que les bits modifiés par une incrémentation ne le sont pas en même temps. Les bits de poids faibles sont modifiés avant les autres. Évidemment, à la fin du calcul, on obtient le résultat final, correct. Mais pendant le temps de calcul, le compteur peut se retrouver dans un état transitoire, où seuls les bits de poids faibles ont été modifiés. C'est un problème si le contenu de ce compteur est relié à des circuits rapides, qui peuvent voir cet état transitoire, mais ne le doivent pas sous peine de dysfonctionner. L'usage de compteurs en code Gray permet d'éviter ce problème : vu que seul un bit est modifié lors d'une incrémentation/décrémentation, les états transitoires n'existent tout simplement pas.
Un exemple typique, évoqué dans les chapitres précédents, est l'échange d'informations entre deux domaines d'horloge. Pour rappel, il arrive que deux portions d'un circuit imprimé aillent à des fréquences différences : on dit que le circuit à plusieurs domaines d'horloge. S'il faut échanger des informations entre ces deux domaines d'horloge, divers problèmes surviennent. Un domaine d'horloge sera plus rapide que l'autre, et pourra voir les états transitoires invisible de l'autre circuit. Et par voir, on veut dire qu'il les prendra pour des états valides, ce qui fera dysfonctionner le circuit. Pour éviter cela, diverses techniques de croisement de domaines d'horloge existent. Et les compteurs Gray en font partie : si un domaine d'horloge utilise la valeur d'un compteur de l'autre, mieux vaut que ce compteur soit un compteur Gray. Et cette situation est assez fréquente !
Un autre avantage mineurs est que la consommation d'énergie de ces compteurs est bien plus réduite qu'avec un compteur normal. Rappelons que pour fonctionner, les circuits électroniques consomment un peu d'électricité. Et la majeure partie de cette consommation sert à faire passer un bit de 0 à 1 ou de 1 à 0. Ce qui fait que quand un compteur est incrémenté ou décrémenté, cela consomme un peu d'énergie électrique.
La moyenne pour un compteur binaire normal est de 2 bits changés par incrémentation/décrémentation, contre un seul pour un compteur Gray, on devine que ces derniers consomment deux fois moins d'énergie par incrémentation. Et cet avantage a des effets en cascade sur les circuits qui suivent ce compteur. Si l'entrée de ces circuits ne change que d'un seul bit, alors leur état changera moins que si c'était deux bits. Les circuits qui suivent vont donc moins consommer.
Un autre avantage en matière de consommation énergétique est lié auxs transitions d'état douteuses. Les circuits connectés au compteur vont voir ces transitions d'état douteuses et modifier leur état interne en réaction. Bien sur, l'état final correct sera atteint une fois que le compteur sera stabilisé, ce qui effacera ces états transitoires intermédiaires. Mais chaque état intermédiaire transitoire correspond à un changement d'état, donc à une consommation d'énergie. En supprimant ces états transitoires, on réduit fortement la consommation d'énergie du circuit. Cela vaut pour le compteur Gray lui-même, mais aussi sur tous les circuits qui ont ce compteur comme entrée !
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les registres et mémoires adressables
| prevText=Les registres et mémoires adressables
| next=Les registres à décalage et les LSFR
| nextText=Les registres à décalage et les LSFR
}}
</noinclude>
icq5ze2msavwtw1qeku57a7pq5d466i
Fonctionnement d'un ordinateur/Les registres et mémoires adressables
0
78078
768609
768125
2026-06-25T15:12:06Z
Mewtow
31375
/* L'intérieur d'un registre simple */
768609
wikitext
text/x-wiki
Dans les chapitres précédents, nous avons vu comment mémoriser un bit, dans une bascule. Mais les bascules en elles-mêmes sont rarement utiles seules, car les données à mémoriser font généralement plusieurs bits, pas un seul. Stocker plusieurs bits est la raison d'être des '''registres''', des composants qui mémorisent des plusieurs bits, que l'on peut modifier et/ou récupérer plus tard. Il existe plusieurs types de registres, et nous allons faire la distinction entre les registres simples et les registres à décalage. Les registres simples sont capables de mémoriser un nombre, de taille fixe, rien de plus. Les registres à décalage sont des registres simples améliorés, capables de faire quelques petites opérations sur leur contenu.
==Les registres simples==
Les '''registres simples''' sont capables de mémoriser un nombre, codé sur une quantité fixe de bits. On peut à tout moment récupérer le nombre mémorisé dans le registre : on dit alors qu'on effectue une lecture. On peut aussi mettre à jour le nombre mémorisé dans le registre, le remplacer par un autre : on dit qu'on effectue une écriture. Les seules opérations possibles sur ces registres sont la lecture (récupérer le nombre mémorisé dans le registre) et l'écriture (mettre à jour le nombre mémorisé dans le registre, le remplacer par un autre).
===L'interface d'un registre simple===
[[File:4 Bit Data Register.svg|vignette|Registre de 4 Bits. On voit que celui-ci contient 4 entrées (à gauche), et 4 sorties (à droite). On peut aussi remarquer une entrée CLK, qui joue le rôle d'entrée d'autorisation.]]
Niveau entrées et sorties, les registres possèdent des entrées-sorties pour les données mémorisées, mais aussi des entrées-sorties de commande. Les entrées-sorties pour les données permettent de lire le contenu du registre ou d'y écrire. Les entrées de commande permettent de configurer le registre pour lui ordonner de faire une écriture, pour le remettre à zéro, ou toute autre opération.
Les entrées de données sont utilisées pour l'écriture, alors que les sorties de données servent pour la lecture. Le nombre mémorisé dans le registre est disponible sur les sorties du registre. Pour utiliser les entrées d'écriture, on envoie le nombre à mémoriser (celui qui remplacera le contenu du registre) sur les entrées d'écriture et on configure les entrées de commande adéquates.
Les entrées de commande varient suivant le registre, mais on trouve au moins une entrée Enable, qui a le même rôle que pour une bascule, à savoir autoriser une écriture. Si l'entrée Enable est à 1, le registre mémorise ce qu'il y a sur l'entrée de donnée. Mais si l'entrée Enable est à 0, le registre n'est pas mis à jour : on peut mettre n'importe quelle valeur sur les entrées, le registre n'en tiendra pas compte et ne remplacera pas son contenu par ce qu'il y a sur l'entrée. Pour résumer, l'entrée Enable sert donc à indiquer au registre si son contenu doit être mis à jour, quand une écriture a lieu.
D'autres entrées de commandes sont parfois présentes, la plus commune étant une entrée permettant de remettre à zéro le registre. La présence d'un 1 sur cette entrée remet à zéro le contenu du registre, à savoir que celui-ci contient la valeur zéro.
Enfin, il faut distinguer les registres synchrones des registres asynchrones. Les '''registres synchrones''' sont reliés au signal d’horloge. Pour cela, ils disposent d'une entrée d'horloge sur laquelle on envoie le signal d'horloge. Ils ne sont mis à jour que si on présente un front montant sur l'entrée d'horloge. Les '''registres asynchrones''' ne sont pas reliés au signal d'horloge et sont mis à jour quand on envoie ce qu'il faut sur leur entré Enable, rien de plus.
===L'intérieur d'un registre simple===
[[File:Basic n-bit register.svg|vignette|Registre.]]
Un registre est composé de plusieurs bascules D qui sont toutes mises à jour en même temps. Cela vaut aussi bien pour les registres asynchrones que les registres synchrones. Pour cela, toutes les entrées E des bascules sont reliées à l'entrée de commande Enable. De plus, les registre synchrones envoient le signal d'horloge sur toutes les bascules. Avec un registre synchrone, toutes les bascules sont des bascules synchrones, qui ont toutes une entrée d'horloge, relié au signal d'horloge.
[[File:Register.svg|centre|vignette|upright=3|Registre.]]
==Les registres à décalage==
Les '''registres à décalage''' sont des registres dont le contenu est décalé d'un cran vers la gauche ou la droite sur commande. Nous aurons à les réutiliser plus tard dans ce cours, notamment dans la section sur les circuits de génération de nombres aléatoires, ou dans certains circuits liés au cache. Les registres à décalage sont presque tous synchrones et ce chapitre ne parlera que ce ces derniers. L'animation suivante illustre le fonctionnement d'un registre à décalage qui décale son contenu d'un cran vers la droite à chaque cycle d'horloge.
[[File:4-Bit PISO Shift Register Seq.gif|centre|vignette|upright=2|Registre à décalage.]]
===La classification des registres===
On peut classer les registres selon le caractère de l'entrée et de la sortie, qui peut être parallèle (entrée de plusieurs bits) ou série (entrée d'un seul bit).
* Sur les registres simples, les entrées et sorties pour les données sont toujours parallèles. Pour un registre de N bits, il y a une entrée d'écriture de N bits et une sortie de N bits. C'est la raison pour laquelle ils sont appelés des '''registres à entrées et sorties parallèles'''.
* Sur les registres à '''entrée et sortie série''', on peut mettre à jour un bit à la fois, de même qu'on ne peut en récupérer qu'un à la fois. Ces registres servent essentiellement à mettre en attente des bits tout en gardant leur ordre : un bit envoyé en entrée ressortira sur la sortie après plusieurs commandes de mise à jour sur l'entrée Enable.
* Les registres à décalage à '''entrée série et sortie parallèle''' sont similaires aux précédents : on peut ajouter un nouveau bit en commandant l'entrée Enable et les anciens bits sont alors décalés d'un cran. Par contre, on peut récupérer (lire) tous les bits en une seule fois. Ils permettent notamment de reconstituer un nombre qui est envoyé bit par bit sur un fil (un bus série).
* Enfin, il reste les registres à '''entrée parallèle et sortie série'''. Ces registres sont utiles quand on veut transmettre un nombre sur un fil : on peut ainsi envoyer les bits un par un.
[[File:Td5bfig1.png|centre|vignette|upright=2|Classification des registres à décalage.]]
Pour résumer, on distingue quatre types de registres (à décalage ou non), qui portent les noms de PIPO, PISO, SIPO et SISO. Les noms peuvent sembler barbares, mais il y a une logique derrière ces termes.La lettre P est pour parallèle, la lettre S est pour série. La lettre I signifie ''Input'', ce qui veut dire entrée en anglais, la lettre O est pour ''Output'', la sortie en anglais.
{|class="wikitable"
|+ Classification des registres
|-
!
! Entrée parallèle
! Entrée série
|-
! Sortie parallèle
| PIPO (registre simple)
| SIPO
|-
! Sortie série
| PISO
| SISO
|}
===L'intérieur d'un registre à décalage===
Tous les registres sont conçus en plaçant plusieurs bascules les unes à la suite des autres, que ce soit pour les registres simples ou les registres à décalage. La seule différence tient dans la manière dont les bascules sont reliées. Toutes les bascules sont reliées à l'entrée d'horloge, l'entrée Enable, l'entrée Reset, ou aux autres entrées de commandes. Mais c'est une autre paire de manche pour les entrées/sorties de données.
Dans un registre simple, les bascules sont indépendantes et ne sont pas reliées entre elles.
[[File:Register.svg|centre|vignette|upright=3|Registre simple.]]
À l'inverse, dans les registres à décalage, il existe des connexions entre bascules. Plus précisément, les bascules sont reliées les unes à la suite des autres, elles forment une chaîne de bascules reliées deux à deux. Et les connexions entre bascules sont les mêmes que l'on parle d'un registre à décalage de type SIPO, PISO ou SISO.
[[File:Shift-Register.svg|centre|vignette|upright=3|Exemple de registre à décalage]]
Outre le fait que les bascules sont reliées de la même manière, les autres connexions sont les mêmes dans tous les registres. L'entrée d'horloge (non-représentée dans les schémas qui vont suivre) est envoyée à toutes les bascules. Même chose pour l'entrée Enable, qui est reliée aux entrées E de toutes les bascules. La différence entre ces registres tient dans les endroits où se trouvent les entrées et les sorties du registre.
{|
|+ Implémentation des registres avec des bascules.
|-
|[[File:Registre à entrée parallèle et sortie série.png|vignette|upright=2|Registre à entrée et sortie série.]]
|[[File:Registre à lecture et écriture parallèle.png|vignette|upright=2|Registre à entrée et sortie parallèle.]]
|-
|[[File:Registre à entrée et sortie série.png|vignette|upright=2|Registre à entrée série et sortie parallèle.]]
|[[File:Registre à entrée série et sortie parallèle.png|vignette|upright=2|Registre à entrée parallèle et sortie série.]]
|}
==Une utilisation des registres : les bancs de registre==
Maintenant que nous avons les registres, il est temps d'en montrer une utilisation assez intéressante. Nous allons combiner les registres avec des multiplexeurs/démultiplexeurs pour former une '''mémoire adressable'''. Plus précisément, nous allons voir les '''bancs de registre''', qui peuvent être vu comme un rassemblement de plusieurs registres. Mais ces registres ne sont pas assemblés pour obtenir un registre plus gros : par exemple, on peut fabriquer un registre de 32 bits à partir de 2 registres de 16 bits, ou de 4 registres de 8 bits. Ce n'est pas ce qui est fait sur les mémoires adressables, où les registres sont regroupés de manière à ce qu'il soit possible de sélectionner le registre qu'on veut consulter ou modifier.
Pour préciser le registre à sélectionner, chacun d'entre eux se voit attribuer un nombre : l''''adresse'''. On peut comparer une adresse à un numéro de téléphone (ou à une adresse d'appartement) : chacun de vos correspondants a un numéro de téléphone et vous savez que pour appeler telle personne, vous devez composer tel numéro. Les adresses mémoires en sont l'équivalent pour les registres d'une mémoire adressable. Il existe des mémoires qui ne fonctionnent pas sur ce principe, mais passons : ce sera pour la suite.
[[File:Adressage mémoire.png|centre|vignette|upright=2|Exemple : on demande à la mémoire de sélectionner le byte d'adresse 1002 et on récupère son contenu (ici, 17).]]
===L'intérieur d'un banc de registre===
Un banc de registre peut se fabriquer assez simplement : il suffit d'un ou de plusieurs multiplexeurs et de registres. Quand on présente l'adresse sur l'entrée de sélection du multiplexeur, celui-ci va connecter le registre demandé à la sortie (ou à l'entrée).
[[File:Intérieur d'une mémoire RAM.png|centre|vignette|upright=2|Intérieur d'une RAM fabriquée avec des registres et des multiplexeurs.]]
Voici ce que cela donne avec une RAM reliée à un bus de 1 bit, à savoir que chaque case mémoire ne contient que 1 bit, il y a un bit par adresse. Il s'agit d'un exemple bien trop simple pour avoir la moindre application pratique, mais c'est un exemple clairement pédagogique. L'entrée d'écriture est reliée à toutes les bascules, mais seule celle sélectionnée est écrite. Lors d'une lecture, l'adresse est envoyée au multiplexeur et la donnée lue sur sa sortie. Lors d'une écriture, c'est le démultiplexeur/décodeur qui est utilisé. Le décodeur active la bascule voulue, via son entrée d'horloge ou Enable. Le bit R/W précise qu'il faut effectuer une écriture. L'entrée d'écriture est alors recopiée dans la bascule sélectionnée.
[[File:MemWrite.png|centre|vignette|upright=2|Intérieur d'une RAM de 4 bits, reliée à un bus de 1 bit, fabriquée avec des registres et des multiplexeurs.]]
===L'interface d'un banc de registre===
[[File:RAM 8x4 symbol.svg|vignette|Interface d'une SRAM.]]
Niveau entrées et sorties, un banc de registre contient des entrées-sorties dédiées aux transferts de données et plusieurs entrées de commande.
Les entrées de commande permettent de configurer la mémoire pour effectuer une lecture ou écriture, la mettre en veille, ou autre. Parmi les entrées de commande, on trouve une entrée de plusieurs bits, sur laquelle on peut envoyer l'adresse, appelée l'entrée d'adressage. On trouve aussi une entrée R/W d'un bit, qui permet de préciser si on veut faire une lecture ou une écriture. On trouve aussi parfois une entrée ''Enable'' Ou ''Chip Select'', qui indique si la RAM est activée ou mise en veille, qui ressemble à l'entrée ''Enable'' des bascules.
Pour les données, tout dépend du banc de registre considéré. Sur certains bancs de registre, on trouve une sortie sur laquelle on peut récupérer le registre sélectionné (on dit qu'on lit le registre) et une entrée sur laquelle on peut envoyer une donnée destinée à être écrite dans le registre sélectionné (on dit qu'on écrit le registre). On a donc une sortie pour la lecture et une entrée pour l'écriture.
Mais sur d'autres bancs de registre, l'entrée et la sortie sont fusionnées en une seule entrée-sortie. Et d'autres bancs de registre ont au contraire plusieurs sorties de lecture. Rien de bien compliqué à cela : il suffit d'ajouter plusieurs multiplexeurs, au lieu d'un seul. Il suffit d'un multiplexeur par sortie de lecture.
==Les mémoire mortes et mémoires vives==
Les bancs de registres sont un sous-type de '''mémoire SRAM'''. Formellement, les mémoires SRAM sont des mémoires similaires aux bancs de registre, la différence étant que les bascules ne sont pas fabriquées avec des portes logiques, mais avec des transistors. Mais pour le reste, la conception reste sensiblement la même. Les SRAM sont composées de bascules de 1 bit, assemblées en "pseudo-registres", elles mêmes reliées à des multiplexeurs/démultiplexeurs. Elles sont très utilisées, surtout dans les processeurs. Les mémoires sont très diverses et les mémoires SRAM ne sont qu'un type de mémoires parmi tant d'autres.
Les mémoires SRAM font elles-mêmes partie de la catégorie des mémoires vives, aussi appelées '''mémoires RAM''' (bien que ce soit un abus de langage, comme on le verra dans plusieurs chapitres). De telles mémoires sont des mémoires électroniques, qui sont adressables, dans lesquelles on peut lire et écrire. Nous verrons les différents types de RAM dans les chapitres sur les mémoires, aussi nous allons mettre cela de côté pour le moment.
Outre les mémoires RAM, il existe des mémoires qui sont elles aussi électroniques, adressables, mais dans lesquelles on ne peut pas écrire : ce sont les '''mémoires ROM'''. En général, les mémoires ROM conservent leur contenu quand on coupe l’alimentation électrique. Si on éteint l'ordinateur, le contenu de la ROM n'est pas perdu, il reste le même. C'est l'exact inverse de ce qu'on a avec les registres, mémoires SRAM, bascules et autres : tout est effacé quand on coupe le courant. Les mémoires RAM sont dites volatiles, alors que les mémoires ROM sont dites non-volatiles.
===Les mémoires ROM===
Il existe deux types de mémoires ROM : les ROM non-programmables et les ROM programmables. La différence est que les premières sont fournies telles quelle et qu'on ne peut pas changer leur contenu, alors que ce n'est pas le cas pour les secondes.
Les ROM programmables sont des ROM dans lesquelles on ne peut évidemment pas écrire, mais qui permettent cependant de réécrire intégralement leur contenu : on dit qu'on reprogramme la ROM. Insistons sur la différence entre reprogrammation et écriture : l'écriture permet de modifier un byte sélectionné/adressé, alors que la reprogrammation efface toute la mémoire et la réécrit en totalité. Ce terme de programmation vient du fait que les mémoires ROM sont souvent utilisées pour stocker des programmes sur certains ordinateurs assez simples.
Les mémoires non-programmables sont aussi appelées des '''''mask'' ROM'''. Elles sont utilisées dans quelques applications particulières, pour lesquelles on n'a pas besoin de changer leur contenu. Par exemple, elles étaient utilisées sur les vieilles consoles de jeux, pour stocker le jeu vidéo dans les cartouches. Elles servent aussi pour les ''firmware'' divers et variés, comme le ''firmware'' d'une imprimante ou d'une clé USB. De telles mémoires seront utiles dans les chapitres qui vont suivre. La raison en est que tout circuit combinatoire peut être remplacé par une mémoire adressable ! Imaginons que l'on souhaite créer un circuit combinatoire qui pour toute entrée A fournisse la sortie B. Celui-ci est équivalent à une ROM dont la lecture de l'adresse A renvoie B sur la sortie. Cette logique est notamment utilisée dans certains circuits programmables, les FPGA, comme on le verra plus tard.
===L'implémentation des mémoires ROM===
Les mémoires ROM sont conçues, sur le même principe que les mémoires SRAM : on combine des registres avec des multiplexeurs. Il y a cependant des différences importantes, liées au fait que les écritures sont interdites. Et il y a une grosse différence suivant que la mémoire soit reprogrammable ou non.
Si la mémoire est reprogrammable, la différence principale est que les registres sont conçus de manière à ne pas être effacés quand on coupe le courant. Ils ne sont pas fabriqués avec des bascules, mais avec d'autres circuits plus complexes, à base de transistors à grille flottante. Les bascules sont remplacés par un équivalent qui se comporte de la même manière, sauf qu'on ne peut pas changer leur contenu facilement (interdiction des écritures), et que leur contenu ne s'efface pas quand on coupe le courant. Il peut y avoir d'autres différences, mais nous verrons cela dans le chapitre dédié aux mémoires ROM.
Quant aux ''mask'' ROM, leur implémentation est beaucoup plus simple. Ils sont conçus sur le même principe que les SRAM. Sauf que vu que l'écriture et la reprogrammation sont interdites, on peut retirer les démultiplexeurs utilisés pour les écritures (et la reprogrammation). Quand aux registres, ils sont remplacés en connectant directement la tension d'alimentation ou la masse sur les entrées des multiplexeurs de lecture. Là où on veut mettre un 0, on connecte la masse. Là où on veut mettre un 1, on connecte la tension d'alimentation. Le circuit obtenu se simplifie alors et peut se remplacer par un circuit composé d'un décodeur connecté à un paquet de portes OU.
[[File:Rom simpel.png|centre|vignette|upright=2|Mémoire ROM simple.]]
L'implémentation d'une ''mask'' ROM est en réalité plus complexe sur certains points, notamment l'implémentation des portes OU, qui sont en réalité des OU câblés comme vu dans le chapitre sur les circuits imprimés. Mais nous reverrons cela dans quelques chapitres. L'important est que vous reteniez ce qu'est une mémoire ROM, qui n'est qu'un cas particulier de circuit combinatoire. Nous aurons à utiliser des mémoires ROM dans les chapitres suivants, à quelques endroits bien précis.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits synchrones et asynchrones
| prevText=Les circuits synchrones et asynchrones
| next=Les compteurs et timers
| nextText=Les compteurs et timers
}}
</noinclude>
5gsyu9metycnqomf2goyk9whoeqjr30
768610
768609
2026-06-25T15:14:41Z
Mewtow
31375
/* Une utilisation des registres : les bancs de registre */
768610
wikitext
text/x-wiki
Dans les chapitres précédents, nous avons vu comment mémoriser un bit, dans une bascule. Mais les bascules en elles-mêmes sont rarement utiles seules, car les données à mémoriser font généralement plusieurs bits, pas un seul. Stocker plusieurs bits est la raison d'être des '''registres''', des composants qui mémorisent des plusieurs bits, que l'on peut modifier et/ou récupérer plus tard. Il existe plusieurs types de registres, et nous allons faire la distinction entre les registres simples et les registres à décalage. Les registres simples sont capables de mémoriser un nombre, de taille fixe, rien de plus. Les registres à décalage sont des registres simples améliorés, capables de faire quelques petites opérations sur leur contenu.
==Les registres simples==
Les '''registres simples''' sont capables de mémoriser un nombre, codé sur une quantité fixe de bits. On peut à tout moment récupérer le nombre mémorisé dans le registre : on dit alors qu'on effectue une lecture. On peut aussi mettre à jour le nombre mémorisé dans le registre, le remplacer par un autre : on dit qu'on effectue une écriture. Les seules opérations possibles sur ces registres sont la lecture (récupérer le nombre mémorisé dans le registre) et l'écriture (mettre à jour le nombre mémorisé dans le registre, le remplacer par un autre).
===L'interface d'un registre simple===
[[File:4 Bit Data Register.svg|vignette|Registre de 4 Bits. On voit que celui-ci contient 4 entrées (à gauche), et 4 sorties (à droite). On peut aussi remarquer une entrée CLK, qui joue le rôle d'entrée d'autorisation.]]
Niveau entrées et sorties, les registres possèdent des entrées-sorties pour les données mémorisées, mais aussi des entrées-sorties de commande. Les entrées-sorties pour les données permettent de lire le contenu du registre ou d'y écrire. Les entrées de commande permettent de configurer le registre pour lui ordonner de faire une écriture, pour le remettre à zéro, ou toute autre opération.
Les entrées de données sont utilisées pour l'écriture, alors que les sorties de données servent pour la lecture. Le nombre mémorisé dans le registre est disponible sur les sorties du registre. Pour utiliser les entrées d'écriture, on envoie le nombre à mémoriser (celui qui remplacera le contenu du registre) sur les entrées d'écriture et on configure les entrées de commande adéquates.
Les entrées de commande varient suivant le registre, mais on trouve au moins une entrée Enable, qui a le même rôle que pour une bascule, à savoir autoriser une écriture. Si l'entrée Enable est à 1, le registre mémorise ce qu'il y a sur l'entrée de donnée. Mais si l'entrée Enable est à 0, le registre n'est pas mis à jour : on peut mettre n'importe quelle valeur sur les entrées, le registre n'en tiendra pas compte et ne remplacera pas son contenu par ce qu'il y a sur l'entrée. Pour résumer, l'entrée Enable sert donc à indiquer au registre si son contenu doit être mis à jour, quand une écriture a lieu.
D'autres entrées de commandes sont parfois présentes, la plus commune étant une entrée permettant de remettre à zéro le registre. La présence d'un 1 sur cette entrée remet à zéro le contenu du registre, à savoir que celui-ci contient la valeur zéro.
Enfin, il faut distinguer les registres synchrones des registres asynchrones. Les '''registres synchrones''' sont reliés au signal d’horloge. Pour cela, ils disposent d'une entrée d'horloge sur laquelle on envoie le signal d'horloge. Ils ne sont mis à jour que si on présente un front montant sur l'entrée d'horloge. Les '''registres asynchrones''' ne sont pas reliés au signal d'horloge et sont mis à jour quand on envoie ce qu'il faut sur leur entré Enable, rien de plus.
===L'intérieur d'un registre simple===
[[File:Basic n-bit register.svg|vignette|Registre.]]
Un registre est composé de plusieurs bascules D qui sont toutes mises à jour en même temps. Cela vaut aussi bien pour les registres asynchrones que les registres synchrones. Pour cela, toutes les entrées E des bascules sont reliées à l'entrée de commande Enable. De plus, les registre synchrones envoient le signal d'horloge sur toutes les bascules. Avec un registre synchrone, toutes les bascules sont des bascules synchrones, qui ont toutes une entrée d'horloge, relié au signal d'horloge.
[[File:Register.svg|centre|vignette|upright=3|Registre.]]
==Les registres à décalage==
Les '''registres à décalage''' sont des registres dont le contenu est décalé d'un cran vers la gauche ou la droite sur commande. Nous aurons à les réutiliser plus tard dans ce cours, notamment dans la section sur les circuits de génération de nombres aléatoires, ou dans certains circuits liés au cache. Les registres à décalage sont presque tous synchrones et ce chapitre ne parlera que ce ces derniers. L'animation suivante illustre le fonctionnement d'un registre à décalage qui décale son contenu d'un cran vers la droite à chaque cycle d'horloge.
[[File:4-Bit PISO Shift Register Seq.gif|centre|vignette|upright=2|Registre à décalage.]]
===La classification des registres===
On peut classer les registres selon le caractère de l'entrée et de la sortie, qui peut être parallèle (entrée de plusieurs bits) ou série (entrée d'un seul bit).
* Sur les registres simples, les entrées et sorties pour les données sont toujours parallèles. Pour un registre de N bits, il y a une entrée d'écriture de N bits et une sortie de N bits. C'est la raison pour laquelle ils sont appelés des '''registres à entrées et sorties parallèles'''.
* Sur les registres à '''entrée et sortie série''', on peut mettre à jour un bit à la fois, de même qu'on ne peut en récupérer qu'un à la fois. Ces registres servent essentiellement à mettre en attente des bits tout en gardant leur ordre : un bit envoyé en entrée ressortira sur la sortie après plusieurs commandes de mise à jour sur l'entrée Enable.
* Les registres à décalage à '''entrée série et sortie parallèle''' sont similaires aux précédents : on peut ajouter un nouveau bit en commandant l'entrée Enable et les anciens bits sont alors décalés d'un cran. Par contre, on peut récupérer (lire) tous les bits en une seule fois. Ils permettent notamment de reconstituer un nombre qui est envoyé bit par bit sur un fil (un bus série).
* Enfin, il reste les registres à '''entrée parallèle et sortie série'''. Ces registres sont utiles quand on veut transmettre un nombre sur un fil : on peut ainsi envoyer les bits un par un.
[[File:Td5bfig1.png|centre|vignette|upright=2|Classification des registres à décalage.]]
Pour résumer, on distingue quatre types de registres (à décalage ou non), qui portent les noms de PIPO, PISO, SIPO et SISO. Les noms peuvent sembler barbares, mais il y a une logique derrière ces termes.La lettre P est pour parallèle, la lettre S est pour série. La lettre I signifie ''Input'', ce qui veut dire entrée en anglais, la lettre O est pour ''Output'', la sortie en anglais.
{|class="wikitable"
|+ Classification des registres
|-
!
! Entrée parallèle
! Entrée série
|-
! Sortie parallèle
| PIPO (registre simple)
| SIPO
|-
! Sortie série
| PISO
| SISO
|}
===L'intérieur d'un registre à décalage===
Tous les registres sont conçus en plaçant plusieurs bascules les unes à la suite des autres, que ce soit pour les registres simples ou les registres à décalage. La seule différence tient dans la manière dont les bascules sont reliées. Toutes les bascules sont reliées à l'entrée d'horloge, l'entrée Enable, l'entrée Reset, ou aux autres entrées de commandes. Mais c'est une autre paire de manche pour les entrées/sorties de données.
Dans un registre simple, les bascules sont indépendantes et ne sont pas reliées entre elles.
[[File:Register.svg|centre|vignette|upright=3|Registre simple.]]
À l'inverse, dans les registres à décalage, il existe des connexions entre bascules. Plus précisément, les bascules sont reliées les unes à la suite des autres, elles forment une chaîne de bascules reliées deux à deux. Et les connexions entre bascules sont les mêmes que l'on parle d'un registre à décalage de type SIPO, PISO ou SISO.
[[File:Shift-Register.svg|centre|vignette|upright=3|Exemple de registre à décalage]]
Outre le fait que les bascules sont reliées de la même manière, les autres connexions sont les mêmes dans tous les registres. L'entrée d'horloge (non-représentée dans les schémas qui vont suivre) est envoyée à toutes les bascules. Même chose pour l'entrée Enable, qui est reliée aux entrées E de toutes les bascules. La différence entre ces registres tient dans les endroits où se trouvent les entrées et les sorties du registre.
{|
|+ Implémentation des registres avec des bascules.
|-
|[[File:Registre à entrée parallèle et sortie série.png|vignette|upright=2|Registre à entrée et sortie série.]]
|[[File:Registre à lecture et écriture parallèle.png|vignette|upright=2|Registre à entrée et sortie parallèle.]]
|-
|[[File:Registre à entrée et sortie série.png|vignette|upright=2|Registre à entrée série et sortie parallèle.]]
|[[File:Registre à entrée série et sortie parallèle.png|vignette|upright=2|Registre à entrée parallèle et sortie série.]]
|}
==Les bancs de registre==
Maintenant que nous avons les registres, il est temps d'en montrer une utilisation assez intéressante. Nous allons combiner les registres avec des multiplexeurs/démultiplexeurs pour former une '''mémoire adressable'''. Plus précisément, nous allons voir les '''bancs de registre''', qui peuvent être vu comme un rassemblement de plusieurs registres. Mais ces registres ne sont pas assemblés pour obtenir un registre plus gros : par exemple, on peut fabriquer un registre de 32 bits à partir de 2 registres de 16 bits, ou de 4 registres de 8 bits. Ce n'est pas ce qui est fait sur les mémoires adressables, où les registres sont regroupés de manière à ce qu'il soit possible de sélectionner le registre qu'on veut consulter ou modifier.
Pour préciser le registre à sélectionner, chacun d'entre eux se voit attribuer un nombre : l''''adresse'''. On peut comparer une adresse à un numéro de téléphone (ou à une adresse d'appartement) : chacun de vos correspondants a un numéro de téléphone et vous savez que pour appeler telle personne, vous devez composer tel numéro. Les adresses mémoires en sont l'équivalent pour les registres d'une mémoire adressable. Il existe des mémoires qui ne fonctionnent pas sur ce principe, mais passons : ce sera pour la suite.
[[File:Adressage mémoire.png|centre|vignette|upright=2|Exemple : on demande à la mémoire de sélectionner le byte d'adresse 1002 et on récupère son contenu (ici, 17).]]
===L'intérieur d'un banc de registre===
Un banc de registre peut se fabriquer assez simplement : il suffit d'un ou de plusieurs multiplexeurs et de registres. Quand on présente l'adresse sur l'entrée de sélection du multiplexeur, celui-ci va connecter le registre demandé à la sortie (ou à l'entrée).
[[File:Intérieur d'une mémoire RAM.png|centre|vignette|upright=2|Intérieur d'une RAM fabriquée avec des registres et des multiplexeurs.]]
Voici ce que cela donne avec une RAM reliée à un bus de 1 bit, à savoir que chaque case mémoire ne contient que 1 bit, il y a un bit par adresse. Il s'agit d'un exemple bien trop simple pour avoir la moindre application pratique, mais c'est un exemple clairement pédagogique. L'entrée d'écriture est reliée à toutes les bascules, mais seule celle sélectionnée est écrite. Lors d'une lecture, l'adresse est envoyée au multiplexeur et la donnée lue sur sa sortie. Lors d'une écriture, c'est le démultiplexeur/décodeur qui est utilisé. Le décodeur active la bascule voulue, via son entrée d'horloge ou Enable. Le bit R/W précise qu'il faut effectuer une écriture. L'entrée d'écriture est alors recopiée dans la bascule sélectionnée.
[[File:MemWrite.png|centre|vignette|upright=2|Intérieur d'une RAM de 4 bits, reliée à un bus de 1 bit, fabriquée avec des registres et des multiplexeurs.]]
===L'interface d'un banc de registre===
[[File:RAM 8x4 symbol.svg|vignette|Interface d'une SRAM.]]
Niveau entrées et sorties, un banc de registre contient des entrées-sorties dédiées aux transferts de données et plusieurs entrées de commande.
Les entrées de commande permettent de configurer la mémoire pour effectuer une lecture ou écriture, la mettre en veille, ou autre. Parmi les entrées de commande, on trouve une entrée de plusieurs bits, sur laquelle on peut envoyer l'adresse, appelée l'entrée d'adressage. On trouve aussi une entrée R/W d'un bit, qui permet de préciser si on veut faire une lecture ou une écriture. On trouve aussi parfois une entrée ''Enable'' Ou ''Chip Select'', qui indique si la RAM est activée ou mise en veille, qui ressemble à l'entrée ''Enable'' des bascules.
Pour les données, tout dépend du banc de registre considéré. Sur certains bancs de registre, on trouve une sortie sur laquelle on peut récupérer le registre sélectionné (on dit qu'on lit le registre) et une entrée sur laquelle on peut envoyer une donnée destinée à être écrite dans le registre sélectionné (on dit qu'on écrit le registre). On a donc une sortie pour la lecture et une entrée pour l'écriture.
Mais sur d'autres bancs de registre, l'entrée et la sortie sont fusionnées en une seule entrée-sortie. Et d'autres bancs de registre ont au contraire plusieurs sorties de lecture. Rien de bien compliqué à cela : il suffit d'ajouter plusieurs multiplexeurs, au lieu d'un seul. Il suffit d'un multiplexeur par sortie de lecture.
==Les mémoire mortes et mémoires vives==
Les bancs de registres sont un sous-type de '''mémoire SRAM'''. Formellement, les mémoires SRAM sont des mémoires similaires aux bancs de registre, la différence étant que les bascules ne sont pas fabriquées avec des portes logiques, mais avec des transistors. Mais pour le reste, la conception reste sensiblement la même. Les SRAM sont composées de bascules de 1 bit, assemblées en "pseudo-registres", elles mêmes reliées à des multiplexeurs/démultiplexeurs. Elles sont très utilisées, surtout dans les processeurs. Les mémoires sont très diverses et les mémoires SRAM ne sont qu'un type de mémoires parmi tant d'autres.
Les mémoires SRAM font elles-mêmes partie de la catégorie des mémoires vives, aussi appelées '''mémoires RAM''' (bien que ce soit un abus de langage, comme on le verra dans plusieurs chapitres). De telles mémoires sont des mémoires électroniques, qui sont adressables, dans lesquelles on peut lire et écrire. Nous verrons les différents types de RAM dans les chapitres sur les mémoires, aussi nous allons mettre cela de côté pour le moment.
Outre les mémoires RAM, il existe des mémoires qui sont elles aussi électroniques, adressables, mais dans lesquelles on ne peut pas écrire : ce sont les '''mémoires ROM'''. En général, les mémoires ROM conservent leur contenu quand on coupe l’alimentation électrique. Si on éteint l'ordinateur, le contenu de la ROM n'est pas perdu, il reste le même. C'est l'exact inverse de ce qu'on a avec les registres, mémoires SRAM, bascules et autres : tout est effacé quand on coupe le courant. Les mémoires RAM sont dites volatiles, alors que les mémoires ROM sont dites non-volatiles.
===Les mémoires ROM===
Il existe deux types de mémoires ROM : les ROM non-programmables et les ROM programmables. La différence est que les premières sont fournies telles quelle et qu'on ne peut pas changer leur contenu, alors que ce n'est pas le cas pour les secondes.
Les ROM programmables sont des ROM dans lesquelles on ne peut évidemment pas écrire, mais qui permettent cependant de réécrire intégralement leur contenu : on dit qu'on reprogramme la ROM. Insistons sur la différence entre reprogrammation et écriture : l'écriture permet de modifier un byte sélectionné/adressé, alors que la reprogrammation efface toute la mémoire et la réécrit en totalité. Ce terme de programmation vient du fait que les mémoires ROM sont souvent utilisées pour stocker des programmes sur certains ordinateurs assez simples.
Les mémoires non-programmables sont aussi appelées des '''''mask'' ROM'''. Elles sont utilisées dans quelques applications particulières, pour lesquelles on n'a pas besoin de changer leur contenu. Par exemple, elles étaient utilisées sur les vieilles consoles de jeux, pour stocker le jeu vidéo dans les cartouches. Elles servent aussi pour les ''firmware'' divers et variés, comme le ''firmware'' d'une imprimante ou d'une clé USB. De telles mémoires seront utiles dans les chapitres qui vont suivre. La raison en est que tout circuit combinatoire peut être remplacé par une mémoire adressable ! Imaginons que l'on souhaite créer un circuit combinatoire qui pour toute entrée A fournisse la sortie B. Celui-ci est équivalent à une ROM dont la lecture de l'adresse A renvoie B sur la sortie. Cette logique est notamment utilisée dans certains circuits programmables, les FPGA, comme on le verra plus tard.
===L'implémentation des mémoires ROM===
Les mémoires ROM sont conçues, sur le même principe que les mémoires SRAM : on combine des registres avec des multiplexeurs. Il y a cependant des différences importantes, liées au fait que les écritures sont interdites. Et il y a une grosse différence suivant que la mémoire soit reprogrammable ou non.
Si la mémoire est reprogrammable, la différence principale est que les registres sont conçus de manière à ne pas être effacés quand on coupe le courant. Ils ne sont pas fabriqués avec des bascules, mais avec d'autres circuits plus complexes, à base de transistors à grille flottante. Les bascules sont remplacés par un équivalent qui se comporte de la même manière, sauf qu'on ne peut pas changer leur contenu facilement (interdiction des écritures), et que leur contenu ne s'efface pas quand on coupe le courant. Il peut y avoir d'autres différences, mais nous verrons cela dans le chapitre dédié aux mémoires ROM.
Quant aux ''mask'' ROM, leur implémentation est beaucoup plus simple. Ils sont conçus sur le même principe que les SRAM. Sauf que vu que l'écriture et la reprogrammation sont interdites, on peut retirer les démultiplexeurs utilisés pour les écritures (et la reprogrammation). Quand aux registres, ils sont remplacés en connectant directement la tension d'alimentation ou la masse sur les entrées des multiplexeurs de lecture. Là où on veut mettre un 0, on connecte la masse. Là où on veut mettre un 1, on connecte la tension d'alimentation. Le circuit obtenu se simplifie alors et peut se remplacer par un circuit composé d'un décodeur connecté à un paquet de portes OU.
[[File:Rom simpel.png|centre|vignette|upright=2|Mémoire ROM simple.]]
L'implémentation d'une ''mask'' ROM est en réalité plus complexe sur certains points, notamment l'implémentation des portes OU, qui sont en réalité des OU câblés comme vu dans le chapitre sur les circuits imprimés. Mais nous reverrons cela dans quelques chapitres. L'important est que vous reteniez ce qu'est une mémoire ROM, qui n'est qu'un cas particulier de circuit combinatoire. Nous aurons à utiliser des mémoires ROM dans les chapitres suivants, à quelques endroits bien précis.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits synchrones et asynchrones
| prevText=Les circuits synchrones et asynchrones
| next=Les compteurs et timers
| nextText=Les compteurs et timers
}}
</noinclude>
bfcd710asrzqnosokspn45g5d31t5jl
768613
768610
2026-06-25T15:19:55Z
Mewtow
31375
/* Les registres à décalage */ Déplacement autre chapitre
768613
wikitext
text/x-wiki
Dans les chapitres précédents, nous avons vu comment mémoriser un bit, dans une bascule. Mais les bascules en elles-mêmes sont rarement utiles seules, car les données à mémoriser font généralement plusieurs bits, pas un seul. Stocker plusieurs bits est la raison d'être des '''registres''', des composants qui mémorisent des plusieurs bits, que l'on peut modifier et/ou récupérer plus tard. Il existe plusieurs types de registres, et nous allons faire la distinction entre les registres simples et les registres à décalage. Les registres simples sont capables de mémoriser un nombre, de taille fixe, rien de plus. Les registres à décalage sont des registres simples améliorés, capables de faire quelques petites opérations sur leur contenu.
==Les registres simples==
Les '''registres simples''' sont capables de mémoriser un nombre, codé sur une quantité fixe de bits. On peut à tout moment récupérer le nombre mémorisé dans le registre : on dit alors qu'on effectue une lecture. On peut aussi mettre à jour le nombre mémorisé dans le registre, le remplacer par un autre : on dit qu'on effectue une écriture. Les seules opérations possibles sur ces registres sont la lecture (récupérer le nombre mémorisé dans le registre) et l'écriture (mettre à jour le nombre mémorisé dans le registre, le remplacer par un autre).
===L'interface d'un registre simple===
[[File:4 Bit Data Register.svg|vignette|Registre de 4 Bits. On voit que celui-ci contient 4 entrées (à gauche), et 4 sorties (à droite). On peut aussi remarquer une entrée CLK, qui joue le rôle d'entrée d'autorisation.]]
Niveau entrées et sorties, les registres possèdent des entrées-sorties pour les données mémorisées, mais aussi des entrées-sorties de commande. Les entrées-sorties pour les données permettent de lire le contenu du registre ou d'y écrire. Les entrées de commande permettent de configurer le registre pour lui ordonner de faire une écriture, pour le remettre à zéro, ou toute autre opération.
Les entrées de données sont utilisées pour l'écriture, alors que les sorties de données servent pour la lecture. Le nombre mémorisé dans le registre est disponible sur les sorties du registre. Pour utiliser les entrées d'écriture, on envoie le nombre à mémoriser (celui qui remplacera le contenu du registre) sur les entrées d'écriture et on configure les entrées de commande adéquates.
Les entrées de commande varient suivant le registre, mais on trouve au moins une entrée Enable, qui a le même rôle que pour une bascule, à savoir autoriser une écriture. Si l'entrée Enable est à 1, le registre mémorise ce qu'il y a sur l'entrée de donnée. Mais si l'entrée Enable est à 0, le registre n'est pas mis à jour : on peut mettre n'importe quelle valeur sur les entrées, le registre n'en tiendra pas compte et ne remplacera pas son contenu par ce qu'il y a sur l'entrée. Pour résumer, l'entrée Enable sert donc à indiquer au registre si son contenu doit être mis à jour, quand une écriture a lieu.
D'autres entrées de commandes sont parfois présentes, la plus commune étant une entrée permettant de remettre à zéro le registre. La présence d'un 1 sur cette entrée remet à zéro le contenu du registre, à savoir que celui-ci contient la valeur zéro.
Enfin, il faut distinguer les registres synchrones des registres asynchrones. Les '''registres synchrones''' sont reliés au signal d’horloge. Pour cela, ils disposent d'une entrée d'horloge sur laquelle on envoie le signal d'horloge. Ils ne sont mis à jour que si on présente un front montant sur l'entrée d'horloge. Les '''registres asynchrones''' ne sont pas reliés au signal d'horloge et sont mis à jour quand on envoie ce qu'il faut sur leur entré Enable, rien de plus.
===L'intérieur d'un registre simple===
[[File:Basic n-bit register.svg|vignette|Registre.]]
Un registre est composé de plusieurs bascules D qui sont toutes mises à jour en même temps. Cela vaut aussi bien pour les registres asynchrones que les registres synchrones. Pour cela, toutes les entrées E des bascules sont reliées à l'entrée de commande Enable. De plus, les registre synchrones envoient le signal d'horloge sur toutes les bascules. Avec un registre synchrone, toutes les bascules sont des bascules synchrones, qui ont toutes une entrée d'horloge, relié au signal d'horloge.
[[File:Register.svg|centre|vignette|upright=3|Registre.]]
==Les bancs de registre==
Maintenant que nous avons les registres, il est temps d'en montrer une utilisation assez intéressante. Nous allons combiner les registres avec des multiplexeurs/démultiplexeurs pour former une '''mémoire adressable'''. Plus précisément, nous allons voir les '''bancs de registre''', qui peuvent être vu comme un rassemblement de plusieurs registres. Mais ces registres ne sont pas assemblés pour obtenir un registre plus gros : par exemple, on peut fabriquer un registre de 32 bits à partir de 2 registres de 16 bits, ou de 4 registres de 8 bits. Ce n'est pas ce qui est fait sur les mémoires adressables, où les registres sont regroupés de manière à ce qu'il soit possible de sélectionner le registre qu'on veut consulter ou modifier.
Pour préciser le registre à sélectionner, chacun d'entre eux se voit attribuer un nombre : l''''adresse'''. On peut comparer une adresse à un numéro de téléphone (ou à une adresse d'appartement) : chacun de vos correspondants a un numéro de téléphone et vous savez que pour appeler telle personne, vous devez composer tel numéro. Les adresses mémoires en sont l'équivalent pour les registres d'une mémoire adressable. Il existe des mémoires qui ne fonctionnent pas sur ce principe, mais passons : ce sera pour la suite.
[[File:Adressage mémoire.png|centre|vignette|upright=2|Exemple : on demande à la mémoire de sélectionner le byte d'adresse 1002 et on récupère son contenu (ici, 17).]]
===L'intérieur d'un banc de registre===
Un banc de registre peut se fabriquer assez simplement : il suffit d'un ou de plusieurs multiplexeurs et de registres. Quand on présente l'adresse sur l'entrée de sélection du multiplexeur, celui-ci va connecter le registre demandé à la sortie (ou à l'entrée).
[[File:Intérieur d'une mémoire RAM.png|centre|vignette|upright=2|Intérieur d'une RAM fabriquée avec des registres et des multiplexeurs.]]
Voici ce que cela donne avec une RAM reliée à un bus de 1 bit, à savoir que chaque case mémoire ne contient que 1 bit, il y a un bit par adresse. Il s'agit d'un exemple bien trop simple pour avoir la moindre application pratique, mais c'est un exemple clairement pédagogique. L'entrée d'écriture est reliée à toutes les bascules, mais seule celle sélectionnée est écrite. Lors d'une lecture, l'adresse est envoyée au multiplexeur et la donnée lue sur sa sortie. Lors d'une écriture, c'est le démultiplexeur/décodeur qui est utilisé. Le décodeur active la bascule voulue, via son entrée d'horloge ou Enable. Le bit R/W précise qu'il faut effectuer une écriture. L'entrée d'écriture est alors recopiée dans la bascule sélectionnée.
[[File:MemWrite.png|centre|vignette|upright=2|Intérieur d'une RAM de 4 bits, reliée à un bus de 1 bit, fabriquée avec des registres et des multiplexeurs.]]
===L'interface d'un banc de registre===
[[File:RAM 8x4 symbol.svg|vignette|Interface d'une SRAM.]]
Niveau entrées et sorties, un banc de registre contient des entrées-sorties dédiées aux transferts de données et plusieurs entrées de commande.
Les entrées de commande permettent de configurer la mémoire pour effectuer une lecture ou écriture, la mettre en veille, ou autre. Parmi les entrées de commande, on trouve une entrée de plusieurs bits, sur laquelle on peut envoyer l'adresse, appelée l'entrée d'adressage. On trouve aussi une entrée R/W d'un bit, qui permet de préciser si on veut faire une lecture ou une écriture. On trouve aussi parfois une entrée ''Enable'' Ou ''Chip Select'', qui indique si la RAM est activée ou mise en veille, qui ressemble à l'entrée ''Enable'' des bascules.
Pour les données, tout dépend du banc de registre considéré. Sur certains bancs de registre, on trouve une sortie sur laquelle on peut récupérer le registre sélectionné (on dit qu'on lit le registre) et une entrée sur laquelle on peut envoyer une donnée destinée à être écrite dans le registre sélectionné (on dit qu'on écrit le registre). On a donc une sortie pour la lecture et une entrée pour l'écriture.
Mais sur d'autres bancs de registre, l'entrée et la sortie sont fusionnées en une seule entrée-sortie. Et d'autres bancs de registre ont au contraire plusieurs sorties de lecture. Rien de bien compliqué à cela : il suffit d'ajouter plusieurs multiplexeurs, au lieu d'un seul. Il suffit d'un multiplexeur par sortie de lecture.
==Les mémoire mortes et mémoires vives==
Les bancs de registres sont un sous-type de '''mémoire SRAM'''. Formellement, les mémoires SRAM sont des mémoires similaires aux bancs de registre, la différence étant que les bascules ne sont pas fabriquées avec des portes logiques, mais avec des transistors. Mais pour le reste, la conception reste sensiblement la même. Les SRAM sont composées de bascules de 1 bit, assemblées en "pseudo-registres", elles mêmes reliées à des multiplexeurs/démultiplexeurs. Elles sont très utilisées, surtout dans les processeurs. Les mémoires sont très diverses et les mémoires SRAM ne sont qu'un type de mémoires parmi tant d'autres.
Les mémoires SRAM font elles-mêmes partie de la catégorie des mémoires vives, aussi appelées '''mémoires RAM''' (bien que ce soit un abus de langage, comme on le verra dans plusieurs chapitres). De telles mémoires sont des mémoires électroniques, qui sont adressables, dans lesquelles on peut lire et écrire. Nous verrons les différents types de RAM dans les chapitres sur les mémoires, aussi nous allons mettre cela de côté pour le moment.
Outre les mémoires RAM, il existe des mémoires qui sont elles aussi électroniques, adressables, mais dans lesquelles on ne peut pas écrire : ce sont les '''mémoires ROM'''. En général, les mémoires ROM conservent leur contenu quand on coupe l’alimentation électrique. Si on éteint l'ordinateur, le contenu de la ROM n'est pas perdu, il reste le même. C'est l'exact inverse de ce qu'on a avec les registres, mémoires SRAM, bascules et autres : tout est effacé quand on coupe le courant. Les mémoires RAM sont dites volatiles, alors que les mémoires ROM sont dites non-volatiles.
===Les mémoires ROM===
Il existe deux types de mémoires ROM : les ROM non-programmables et les ROM programmables. La différence est que les premières sont fournies telles quelle et qu'on ne peut pas changer leur contenu, alors que ce n'est pas le cas pour les secondes.
Les ROM programmables sont des ROM dans lesquelles on ne peut évidemment pas écrire, mais qui permettent cependant de réécrire intégralement leur contenu : on dit qu'on reprogramme la ROM. Insistons sur la différence entre reprogrammation et écriture : l'écriture permet de modifier un byte sélectionné/adressé, alors que la reprogrammation efface toute la mémoire et la réécrit en totalité. Ce terme de programmation vient du fait que les mémoires ROM sont souvent utilisées pour stocker des programmes sur certains ordinateurs assez simples.
Les mémoires non-programmables sont aussi appelées des '''''mask'' ROM'''. Elles sont utilisées dans quelques applications particulières, pour lesquelles on n'a pas besoin de changer leur contenu. Par exemple, elles étaient utilisées sur les vieilles consoles de jeux, pour stocker le jeu vidéo dans les cartouches. Elles servent aussi pour les ''firmware'' divers et variés, comme le ''firmware'' d'une imprimante ou d'une clé USB. De telles mémoires seront utiles dans les chapitres qui vont suivre. La raison en est que tout circuit combinatoire peut être remplacé par une mémoire adressable ! Imaginons que l'on souhaite créer un circuit combinatoire qui pour toute entrée A fournisse la sortie B. Celui-ci est équivalent à une ROM dont la lecture de l'adresse A renvoie B sur la sortie. Cette logique est notamment utilisée dans certains circuits programmables, les FPGA, comme on le verra plus tard.
===L'implémentation des mémoires ROM===
Les mémoires ROM sont conçues, sur le même principe que les mémoires SRAM : on combine des registres avec des multiplexeurs. Il y a cependant des différences importantes, liées au fait que les écritures sont interdites. Et il y a une grosse différence suivant que la mémoire soit reprogrammable ou non.
Si la mémoire est reprogrammable, la différence principale est que les registres sont conçus de manière à ne pas être effacés quand on coupe le courant. Ils ne sont pas fabriqués avec des bascules, mais avec d'autres circuits plus complexes, à base de transistors à grille flottante. Les bascules sont remplacés par un équivalent qui se comporte de la même manière, sauf qu'on ne peut pas changer leur contenu facilement (interdiction des écritures), et que leur contenu ne s'efface pas quand on coupe le courant. Il peut y avoir d'autres différences, mais nous verrons cela dans le chapitre dédié aux mémoires ROM.
Quant aux ''mask'' ROM, leur implémentation est beaucoup plus simple. Ils sont conçus sur le même principe que les SRAM. Sauf que vu que l'écriture et la reprogrammation sont interdites, on peut retirer les démultiplexeurs utilisés pour les écritures (et la reprogrammation). Quand aux registres, ils sont remplacés en connectant directement la tension d'alimentation ou la masse sur les entrées des multiplexeurs de lecture. Là où on veut mettre un 0, on connecte la masse. Là où on veut mettre un 1, on connecte la tension d'alimentation. Le circuit obtenu se simplifie alors et peut se remplacer par un circuit composé d'un décodeur connecté à un paquet de portes OU.
[[File:Rom simpel.png|centre|vignette|upright=2|Mémoire ROM simple.]]
L'implémentation d'une ''mask'' ROM est en réalité plus complexe sur certains points, notamment l'implémentation des portes OU, qui sont en réalité des OU câblés comme vu dans le chapitre sur les circuits imprimés. Mais nous reverrons cela dans quelques chapitres. L'important est que vous reteniez ce qu'est une mémoire ROM, qui n'est qu'un cas particulier de circuit combinatoire. Nous aurons à utiliser des mémoires ROM dans les chapitres suivants, à quelques endroits bien précis.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits synchrones et asynchrones
| prevText=Les circuits synchrones et asynchrones
| next=Les compteurs et timers
| nextText=Les compteurs et timers
}}
</noinclude>
pq7aei0i0f2becki5ke4zgywhi0zqm7
768635
768613
2026-06-25T16:24:28Z
Mewtow
31375
/* L'intérieur d'un banc de registre */
768635
wikitext
text/x-wiki
Dans les chapitres précédents, nous avons vu comment mémoriser un bit, dans une bascule. Mais les bascules en elles-mêmes sont rarement utiles seules, car les données à mémoriser font généralement plusieurs bits, pas un seul. Stocker plusieurs bits est la raison d'être des '''registres''', des composants qui mémorisent des plusieurs bits, que l'on peut modifier et/ou récupérer plus tard. Il existe plusieurs types de registres, et nous allons faire la distinction entre les registres simples et les registres à décalage. Les registres simples sont capables de mémoriser un nombre, de taille fixe, rien de plus. Les registres à décalage sont des registres simples améliorés, capables de faire quelques petites opérations sur leur contenu.
==Les registres simples==
Les '''registres simples''' sont capables de mémoriser un nombre, codé sur une quantité fixe de bits. On peut à tout moment récupérer le nombre mémorisé dans le registre : on dit alors qu'on effectue une lecture. On peut aussi mettre à jour le nombre mémorisé dans le registre, le remplacer par un autre : on dit qu'on effectue une écriture. Les seules opérations possibles sur ces registres sont la lecture (récupérer le nombre mémorisé dans le registre) et l'écriture (mettre à jour le nombre mémorisé dans le registre, le remplacer par un autre).
===L'interface d'un registre simple===
[[File:4 Bit Data Register.svg|vignette|Registre de 4 Bits. On voit que celui-ci contient 4 entrées (à gauche), et 4 sorties (à droite). On peut aussi remarquer une entrée CLK, qui joue le rôle d'entrée d'autorisation.]]
Niveau entrées et sorties, les registres possèdent des entrées-sorties pour les données mémorisées, mais aussi des entrées-sorties de commande. Les entrées-sorties pour les données permettent de lire le contenu du registre ou d'y écrire. Les entrées de commande permettent de configurer le registre pour lui ordonner de faire une écriture, pour le remettre à zéro, ou toute autre opération.
Les entrées de données sont utilisées pour l'écriture, alors que les sorties de données servent pour la lecture. Le nombre mémorisé dans le registre est disponible sur les sorties du registre. Pour utiliser les entrées d'écriture, on envoie le nombre à mémoriser (celui qui remplacera le contenu du registre) sur les entrées d'écriture et on configure les entrées de commande adéquates.
Les entrées de commande varient suivant le registre, mais on trouve au moins une entrée Enable, qui a le même rôle que pour une bascule, à savoir autoriser une écriture. Si l'entrée Enable est à 1, le registre mémorise ce qu'il y a sur l'entrée de donnée. Mais si l'entrée Enable est à 0, le registre n'est pas mis à jour : on peut mettre n'importe quelle valeur sur les entrées, le registre n'en tiendra pas compte et ne remplacera pas son contenu par ce qu'il y a sur l'entrée. Pour résumer, l'entrée Enable sert donc à indiquer au registre si son contenu doit être mis à jour, quand une écriture a lieu.
D'autres entrées de commandes sont parfois présentes, la plus commune étant une entrée permettant de remettre à zéro le registre. La présence d'un 1 sur cette entrée remet à zéro le contenu du registre, à savoir que celui-ci contient la valeur zéro.
Enfin, il faut distinguer les registres synchrones des registres asynchrones. Les '''registres synchrones''' sont reliés au signal d’horloge. Pour cela, ils disposent d'une entrée d'horloge sur laquelle on envoie le signal d'horloge. Ils ne sont mis à jour que si on présente un front montant sur l'entrée d'horloge. Les '''registres asynchrones''' ne sont pas reliés au signal d'horloge et sont mis à jour quand on envoie ce qu'il faut sur leur entré Enable, rien de plus.
===L'intérieur d'un registre simple===
[[File:Basic n-bit register.svg|vignette|Registre.]]
Un registre est composé de plusieurs bascules D qui sont toutes mises à jour en même temps. Cela vaut aussi bien pour les registres asynchrones que les registres synchrones. Pour cela, toutes les entrées E des bascules sont reliées à l'entrée de commande Enable. De plus, les registre synchrones envoient le signal d'horloge sur toutes les bascules. Avec un registre synchrone, toutes les bascules sont des bascules synchrones, qui ont toutes une entrée d'horloge, relié au signal d'horloge.
[[File:Register.svg|centre|vignette|upright=3|Registre.]]
==Les bancs de registre==
Maintenant que nous avons les registres, il est temps d'en montrer une utilisation assez intéressante. Nous allons combiner les registres avec des multiplexeurs/démultiplexeurs pour former une '''mémoire adressable'''. Plus précisément, nous allons voir les '''bancs de registre''', qui peuvent être vu comme un rassemblement de plusieurs registres. Mais ces registres ne sont pas assemblés pour obtenir un registre plus gros : par exemple, on peut fabriquer un registre de 32 bits à partir de 2 registres de 16 bits, ou de 4 registres de 8 bits. Ce n'est pas ce qui est fait sur les mémoires adressables, où les registres sont regroupés de manière à ce qu'il soit possible de sélectionner le registre qu'on veut consulter ou modifier.
Pour préciser le registre à sélectionner, chacun d'entre eux se voit attribuer un nombre : l''''adresse'''. On peut comparer une adresse à un numéro de téléphone (ou à une adresse d'appartement) : chacun de vos correspondants a un numéro de téléphone et vous savez que pour appeler telle personne, vous devez composer tel numéro. Les adresses mémoires en sont l'équivalent pour les registres d'une mémoire adressable. Il existe des mémoires qui ne fonctionnent pas sur ce principe, mais passons : ce sera pour la suite.
[[File:Adressage mémoire.png|centre|vignette|upright=2|Exemple : on demande à la mémoire de sélectionner le byte d'adresse 1002 et on récupère son contenu (ici, 17).]]
===L'intérieur d'un banc de registre===
Un banc de registre peut se fabriquer assez simplement : il suffit d'un ou de plusieurs multiplexeurs et de registres. Quand on présente l'adresse sur l'entrée de sélection du multiplexeur, celui-ci va connecter le registre demandé à la sortie (ou à l'entrée). La gestion des écritures est similaire et se fait avec un décodeur.
[[File:Intérieur d'une mémoire RAM.png|centre|vignette|upright=2|Intérieur d'une RAM fabriquée avec des registres et des multiplexeurs.]]
Pour la lecture, il suffit d'utiliser un "multiplexeur". Je mets des guillemets car ce n'est pas un multiplexeur qui prend en entrée N bits et a une sortie de 1 bit, comme les multiplexeurs classiques vus il y a quelques chapitres. Ici, il s'agit d'un multiplexeur qui prend en entrée N nombres et fournit en sortie un nombre, les nombres faisant ici B bits. Un tel "multiplxeur" est composé de B multiplexeurs normaux, précisément des multiplexeurs N vers 1. Le multiplexeur est connecté à chaque registre, un registre par entrée. Le processeur envoie le numéro du registre à lire sur l'entrée de commande, la donnée lue est disponible sur la sortie.
[[File:Addressable register read mux 01.jpg|centre|vignette|upright=2|Port de lecture du banc de registre.]]
Voici ce que cela donne avec une RAM reliée à un bus de 1 bit, à savoir que chaque case mémoire ne contient que 1 bit, il y a un bit par adresse. Il s'agit d'un exemple bien trop simple pour avoir la moindre application pratique, mais c'est un exemple clairement pédagogique. L'entrée d'écriture est reliée à toutes les bascules, mais seule celle sélectionnée est écrite. Lors d'une lecture, l'adresse est envoyée au multiplexeur et la donnée lue sur sa sortie. Lors d'une écriture, c'est le démultiplexeur/décodeur qui est utilisé. Le décodeur active la bascule voulue, via son entrée d'horloge ou Enable. Le bit R/W précise qu'il faut effectuer une écriture. L'entrée d'écriture est alors recopiée dans la bascule sélectionnée.
[[File:MemWrite.png|centre|vignette|upright=2|Intérieur d'une RAM de 4 bits, reliée à un bus de 1 bit, fabriquée avec des registres et des multiplexeurs.]]
===L'interface d'un banc de registre===
[[File:RAM 8x4 symbol.svg|vignette|Interface d'une SRAM.]]
Niveau entrées et sorties, un banc de registre contient des entrées-sorties dédiées aux transferts de données et plusieurs entrées de commande.
Les entrées de commande permettent de configurer la mémoire pour effectuer une lecture ou écriture, la mettre en veille, ou autre. Parmi les entrées de commande, on trouve une entrée de plusieurs bits, sur laquelle on peut envoyer l'adresse, appelée l'entrée d'adressage. On trouve aussi une entrée R/W d'un bit, qui permet de préciser si on veut faire une lecture ou une écriture. On trouve aussi parfois une entrée ''Enable'' Ou ''Chip Select'', qui indique si la RAM est activée ou mise en veille, qui ressemble à l'entrée ''Enable'' des bascules.
Pour les données, tout dépend du banc de registre considéré. Sur certains bancs de registre, on trouve une sortie sur laquelle on peut récupérer le registre sélectionné (on dit qu'on lit le registre) et une entrée sur laquelle on peut envoyer une donnée destinée à être écrite dans le registre sélectionné (on dit qu'on écrit le registre). On a donc une sortie pour la lecture et une entrée pour l'écriture.
Mais sur d'autres bancs de registre, l'entrée et la sortie sont fusionnées en une seule entrée-sortie. Et d'autres bancs de registre ont au contraire plusieurs sorties de lecture. Rien de bien compliqué à cela : il suffit d'ajouter plusieurs multiplexeurs, au lieu d'un seul. Il suffit d'un multiplexeur par sortie de lecture.
==Les mémoire mortes et mémoires vives==
Les bancs de registres sont un sous-type de '''mémoire SRAM'''. Formellement, les mémoires SRAM sont des mémoires similaires aux bancs de registre, la différence étant que les bascules ne sont pas fabriquées avec des portes logiques, mais avec des transistors. Mais pour le reste, la conception reste sensiblement la même. Les SRAM sont composées de bascules de 1 bit, assemblées en "pseudo-registres", elles mêmes reliées à des multiplexeurs/démultiplexeurs. Elles sont très utilisées, surtout dans les processeurs. Les mémoires sont très diverses et les mémoires SRAM ne sont qu'un type de mémoires parmi tant d'autres.
Les mémoires SRAM font elles-mêmes partie de la catégorie des mémoires vives, aussi appelées '''mémoires RAM''' (bien que ce soit un abus de langage, comme on le verra dans plusieurs chapitres). De telles mémoires sont des mémoires électroniques, qui sont adressables, dans lesquelles on peut lire et écrire. Nous verrons les différents types de RAM dans les chapitres sur les mémoires, aussi nous allons mettre cela de côté pour le moment.
Outre les mémoires RAM, il existe des mémoires qui sont elles aussi électroniques, adressables, mais dans lesquelles on ne peut pas écrire : ce sont les '''mémoires ROM'''. En général, les mémoires ROM conservent leur contenu quand on coupe l’alimentation électrique. Si on éteint l'ordinateur, le contenu de la ROM n'est pas perdu, il reste le même. C'est l'exact inverse de ce qu'on a avec les registres, mémoires SRAM, bascules et autres : tout est effacé quand on coupe le courant. Les mémoires RAM sont dites volatiles, alors que les mémoires ROM sont dites non-volatiles.
===Les mémoires ROM===
Il existe deux types de mémoires ROM : les ROM non-programmables et les ROM programmables. La différence est que les premières sont fournies telles quelle et qu'on ne peut pas changer leur contenu, alors que ce n'est pas le cas pour les secondes.
Les ROM programmables sont des ROM dans lesquelles on ne peut évidemment pas écrire, mais qui permettent cependant de réécrire intégralement leur contenu : on dit qu'on reprogramme la ROM. Insistons sur la différence entre reprogrammation et écriture : l'écriture permet de modifier un byte sélectionné/adressé, alors que la reprogrammation efface toute la mémoire et la réécrit en totalité. Ce terme de programmation vient du fait que les mémoires ROM sont souvent utilisées pour stocker des programmes sur certains ordinateurs assez simples.
Les mémoires non-programmables sont aussi appelées des '''''mask'' ROM'''. Elles sont utilisées dans quelques applications particulières, pour lesquelles on n'a pas besoin de changer leur contenu. Par exemple, elles étaient utilisées sur les vieilles consoles de jeux, pour stocker le jeu vidéo dans les cartouches. Elles servent aussi pour les ''firmware'' divers et variés, comme le ''firmware'' d'une imprimante ou d'une clé USB. De telles mémoires seront utiles dans les chapitres qui vont suivre. La raison en est que tout circuit combinatoire peut être remplacé par une mémoire adressable ! Imaginons que l'on souhaite créer un circuit combinatoire qui pour toute entrée A fournisse la sortie B. Celui-ci est équivalent à une ROM dont la lecture de l'adresse A renvoie B sur la sortie. Cette logique est notamment utilisée dans certains circuits programmables, les FPGA, comme on le verra plus tard.
===L'implémentation des mémoires ROM===
Les mémoires ROM sont conçues, sur le même principe que les mémoires SRAM : on combine des registres avec des multiplexeurs. Il y a cependant des différences importantes, liées au fait que les écritures sont interdites. Et il y a une grosse différence suivant que la mémoire soit reprogrammable ou non.
Si la mémoire est reprogrammable, la différence principale est que les registres sont conçus de manière à ne pas être effacés quand on coupe le courant. Ils ne sont pas fabriqués avec des bascules, mais avec d'autres circuits plus complexes, à base de transistors à grille flottante. Les bascules sont remplacés par un équivalent qui se comporte de la même manière, sauf qu'on ne peut pas changer leur contenu facilement (interdiction des écritures), et que leur contenu ne s'efface pas quand on coupe le courant. Il peut y avoir d'autres différences, mais nous verrons cela dans le chapitre dédié aux mémoires ROM.
Quant aux ''mask'' ROM, leur implémentation est beaucoup plus simple. Ils sont conçus sur le même principe que les SRAM. Sauf que vu que l'écriture et la reprogrammation sont interdites, on peut retirer les démultiplexeurs utilisés pour les écritures (et la reprogrammation). Quand aux registres, ils sont remplacés en connectant directement la tension d'alimentation ou la masse sur les entrées des multiplexeurs de lecture. Là où on veut mettre un 0, on connecte la masse. Là où on veut mettre un 1, on connecte la tension d'alimentation. Le circuit obtenu se simplifie alors et peut se remplacer par un circuit composé d'un décodeur connecté à un paquet de portes OU.
[[File:Rom simpel.png|centre|vignette|upright=2|Mémoire ROM simple.]]
L'implémentation d'une ''mask'' ROM est en réalité plus complexe sur certains points, notamment l'implémentation des portes OU, qui sont en réalité des OU câblés comme vu dans le chapitre sur les circuits imprimés. Mais nous reverrons cela dans quelques chapitres. L'important est que vous reteniez ce qu'est une mémoire ROM, qui n'est qu'un cas particulier de circuit combinatoire. Nous aurons à utiliser des mémoires ROM dans les chapitres suivants, à quelques endroits bien précis.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits synchrones et asynchrones
| prevText=Les circuits synchrones et asynchrones
| next=Les compteurs et timers
| nextText=Les compteurs et timers
}}
</noinclude>
pjq31scv8lf2qwihzewymp84ijyjtmb
768636
768635
2026-06-25T16:26:56Z
Mewtow
31375
/* L'intérieur d'un banc de registre */
768636
wikitext
text/x-wiki
Dans les chapitres précédents, nous avons vu comment mémoriser un bit, dans une bascule. Mais les bascules en elles-mêmes sont rarement utiles seules, car les données à mémoriser font généralement plusieurs bits, pas un seul. Stocker plusieurs bits est la raison d'être des '''registres''', des composants qui mémorisent des plusieurs bits, que l'on peut modifier et/ou récupérer plus tard. Il existe plusieurs types de registres, et nous allons faire la distinction entre les registres simples et les registres à décalage. Les registres simples sont capables de mémoriser un nombre, de taille fixe, rien de plus. Les registres à décalage sont des registres simples améliorés, capables de faire quelques petites opérations sur leur contenu.
==Les registres simples==
Les '''registres simples''' sont capables de mémoriser un nombre, codé sur une quantité fixe de bits. On peut à tout moment récupérer le nombre mémorisé dans le registre : on dit alors qu'on effectue une lecture. On peut aussi mettre à jour le nombre mémorisé dans le registre, le remplacer par un autre : on dit qu'on effectue une écriture. Les seules opérations possibles sur ces registres sont la lecture (récupérer le nombre mémorisé dans le registre) et l'écriture (mettre à jour le nombre mémorisé dans le registre, le remplacer par un autre).
===L'interface d'un registre simple===
[[File:4 Bit Data Register.svg|vignette|Registre de 4 Bits. On voit que celui-ci contient 4 entrées (à gauche), et 4 sorties (à droite). On peut aussi remarquer une entrée CLK, qui joue le rôle d'entrée d'autorisation.]]
Niveau entrées et sorties, les registres possèdent des entrées-sorties pour les données mémorisées, mais aussi des entrées-sorties de commande. Les entrées-sorties pour les données permettent de lire le contenu du registre ou d'y écrire. Les entrées de commande permettent de configurer le registre pour lui ordonner de faire une écriture, pour le remettre à zéro, ou toute autre opération.
Les entrées de données sont utilisées pour l'écriture, alors que les sorties de données servent pour la lecture. Le nombre mémorisé dans le registre est disponible sur les sorties du registre. Pour utiliser les entrées d'écriture, on envoie le nombre à mémoriser (celui qui remplacera le contenu du registre) sur les entrées d'écriture et on configure les entrées de commande adéquates.
Les entrées de commande varient suivant le registre, mais on trouve au moins une entrée Enable, qui a le même rôle que pour une bascule, à savoir autoriser une écriture. Si l'entrée Enable est à 1, le registre mémorise ce qu'il y a sur l'entrée de donnée. Mais si l'entrée Enable est à 0, le registre n'est pas mis à jour : on peut mettre n'importe quelle valeur sur les entrées, le registre n'en tiendra pas compte et ne remplacera pas son contenu par ce qu'il y a sur l'entrée. Pour résumer, l'entrée Enable sert donc à indiquer au registre si son contenu doit être mis à jour, quand une écriture a lieu.
D'autres entrées de commandes sont parfois présentes, la plus commune étant une entrée permettant de remettre à zéro le registre. La présence d'un 1 sur cette entrée remet à zéro le contenu du registre, à savoir que celui-ci contient la valeur zéro.
Enfin, il faut distinguer les registres synchrones des registres asynchrones. Les '''registres synchrones''' sont reliés au signal d’horloge. Pour cela, ils disposent d'une entrée d'horloge sur laquelle on envoie le signal d'horloge. Ils ne sont mis à jour que si on présente un front montant sur l'entrée d'horloge. Les '''registres asynchrones''' ne sont pas reliés au signal d'horloge et sont mis à jour quand on envoie ce qu'il faut sur leur entré Enable, rien de plus.
===L'intérieur d'un registre simple===
[[File:Basic n-bit register.svg|vignette|Registre.]]
Un registre est composé de plusieurs bascules D qui sont toutes mises à jour en même temps. Cela vaut aussi bien pour les registres asynchrones que les registres synchrones. Pour cela, toutes les entrées E des bascules sont reliées à l'entrée de commande Enable. De plus, les registre synchrones envoient le signal d'horloge sur toutes les bascules. Avec un registre synchrone, toutes les bascules sont des bascules synchrones, qui ont toutes une entrée d'horloge, relié au signal d'horloge.
[[File:Register.svg|centre|vignette|upright=3|Registre.]]
==Les bancs de registre==
Maintenant que nous avons les registres, il est temps d'en montrer une utilisation assez intéressante. Nous allons combiner les registres avec des multiplexeurs/démultiplexeurs pour former une '''mémoire adressable'''. Plus précisément, nous allons voir les '''bancs de registre''', qui peuvent être vu comme un rassemblement de plusieurs registres. Mais ces registres ne sont pas assemblés pour obtenir un registre plus gros : par exemple, on peut fabriquer un registre de 32 bits à partir de 2 registres de 16 bits, ou de 4 registres de 8 bits. Ce n'est pas ce qui est fait sur les mémoires adressables, où les registres sont regroupés de manière à ce qu'il soit possible de sélectionner le registre qu'on veut consulter ou modifier.
Pour préciser le registre à sélectionner, chacun d'entre eux se voit attribuer un nombre : l''''adresse'''. On peut comparer une adresse à un numéro de téléphone (ou à une adresse d'appartement) : chacun de vos correspondants a un numéro de téléphone et vous savez que pour appeler telle personne, vous devez composer tel numéro. Les adresses mémoires en sont l'équivalent pour les registres d'une mémoire adressable. Il existe des mémoires qui ne fonctionnent pas sur ce principe, mais passons : ce sera pour la suite.
[[File:Adressage mémoire.png|centre|vignette|upright=2|Exemple : on demande à la mémoire de sélectionner le byte d'adresse 1002 et on récupère son contenu (ici, 17).]]
===L'intérieur d'un banc de registre===
Un banc de registre peut se fabriquer assez simplement : il suffit d'un ou de plusieurs multiplexeurs et de registres. Quand on présente l'adresse sur l'entrée de sélection du multiplexeur, celui-ci va connecter le registre demandé à la sortie (ou à l'entrée). La gestion des écritures est similaire et se fait avec un décodeur.
[[File:Intérieur d'une mémoire RAM.png|centre|vignette|upright=2|Intérieur d'une RAM fabriquée avec des registres et des multiplexeurs.]]
Pour la lecture, il suffit d'utiliser un "multiplexeur". Je mets des guillemets car ce n'est pas un multiplexeur qui prend en entrée N bits et a une sortie de 1 bit, comme les multiplexeurs classiques vus il y a quelques chapitres. Ici, il s'agit d'un multiplexeur qui prend en entrée N nombres et fournit en sortie un nombre, les nombres faisant ici B bits. Un tel "multiplxeur" est composé de B multiplexeurs normaux, précisément des multiplexeurs N vers 1. Le multiplexeur est connecté à chaque registre, un registre par entrée. Le processeur envoie le numéro du registre à lire sur l'entrée de commande, la donnée lue est disponible sur la sortie.
[[File:Addressable register read mux 01.jpg|centre|vignette|upright=2|Port de lecture du banc de registre.]]
Pour l'écriture, les choses sont plus complexes. Intuitivement, oin se dit qu'il faut utiliser un "démultiplexeur", mais ce n'est pas exactement cela. LA la place, la donnée à écrire est envoyée sur tous les registres. Concrètement, l'entrée d'écriture du banc de registre est reliée à l'entrée d'écriture de chaque registre. Par contre, un seul registre sera sélectionné pour l'écriture. Pour ce registre, l'entrée Enable sera à 1. Les autres registres auront leur entrée Enable à 0. Cela demande de générer N bits, dont un seul est à 1. Un décodeur est tout indiqué. Le décodeur prend en entrée le numéro du registre pour l'écriture, et active l'entrée Enable correspondante, de même numéro.
[[File:Addressable register write.jpg|centre|vignette|upright=2|Port d'écriture du banc de registre.]]
Voici ce que cela donne avec une RAM reliée à un bus de 1 bit, à savoir que chaque case mémoire ne contient que 1 bit, il y a un bit par adresse. Il s'agit d'un exemple bien trop simple pour avoir la moindre application pratique, mais c'est un exemple clairement pédagogique. L'entrée d'écriture est reliée à toutes les bascules, mais seule celle sélectionnée est écrite. Lors d'une lecture, l'adresse est envoyée au multiplexeur et la donnée lue sur sa sortie. Lors d'une écriture, c'est le démultiplexeur/décodeur qui est utilisé. Le décodeur active la bascule voulue, via son entrée d'horloge ou Enable. Le bit R/W précise qu'il faut effectuer une écriture. L'entrée d'écriture est alors recopiée dans la bascule sélectionnée.
[[File:MemWrite.png|centre|vignette|upright=2|Intérieur d'une RAM de 4 bits, reliée à un bus de 1 bit, fabriquée avec des registres et des multiplexeurs.]]
===L'interface d'un banc de registre===
[[File:RAM 8x4 symbol.svg|vignette|Interface d'une SRAM.]]
Niveau entrées et sorties, un banc de registre contient des entrées-sorties dédiées aux transferts de données et plusieurs entrées de commande.
Les entrées de commande permettent de configurer la mémoire pour effectuer une lecture ou écriture, la mettre en veille, ou autre. Parmi les entrées de commande, on trouve une entrée de plusieurs bits, sur laquelle on peut envoyer l'adresse, appelée l'entrée d'adressage. On trouve aussi une entrée R/W d'un bit, qui permet de préciser si on veut faire une lecture ou une écriture. On trouve aussi parfois une entrée ''Enable'' Ou ''Chip Select'', qui indique si la RAM est activée ou mise en veille, qui ressemble à l'entrée ''Enable'' des bascules.
Pour les données, tout dépend du banc de registre considéré. Sur certains bancs de registre, on trouve une sortie sur laquelle on peut récupérer le registre sélectionné (on dit qu'on lit le registre) et une entrée sur laquelle on peut envoyer une donnée destinée à être écrite dans le registre sélectionné (on dit qu'on écrit le registre). On a donc une sortie pour la lecture et une entrée pour l'écriture.
Mais sur d'autres bancs de registre, l'entrée et la sortie sont fusionnées en une seule entrée-sortie. Et d'autres bancs de registre ont au contraire plusieurs sorties de lecture. Rien de bien compliqué à cela : il suffit d'ajouter plusieurs multiplexeurs, au lieu d'un seul. Il suffit d'un multiplexeur par sortie de lecture.
==Les mémoire mortes et mémoires vives==
Les bancs de registres sont un sous-type de '''mémoire SRAM'''. Formellement, les mémoires SRAM sont des mémoires similaires aux bancs de registre, la différence étant que les bascules ne sont pas fabriquées avec des portes logiques, mais avec des transistors. Mais pour le reste, la conception reste sensiblement la même. Les SRAM sont composées de bascules de 1 bit, assemblées en "pseudo-registres", elles mêmes reliées à des multiplexeurs/démultiplexeurs. Elles sont très utilisées, surtout dans les processeurs. Les mémoires sont très diverses et les mémoires SRAM ne sont qu'un type de mémoires parmi tant d'autres.
Les mémoires SRAM font elles-mêmes partie de la catégorie des mémoires vives, aussi appelées '''mémoires RAM''' (bien que ce soit un abus de langage, comme on le verra dans plusieurs chapitres). De telles mémoires sont des mémoires électroniques, qui sont adressables, dans lesquelles on peut lire et écrire. Nous verrons les différents types de RAM dans les chapitres sur les mémoires, aussi nous allons mettre cela de côté pour le moment.
Outre les mémoires RAM, il existe des mémoires qui sont elles aussi électroniques, adressables, mais dans lesquelles on ne peut pas écrire : ce sont les '''mémoires ROM'''. En général, les mémoires ROM conservent leur contenu quand on coupe l’alimentation électrique. Si on éteint l'ordinateur, le contenu de la ROM n'est pas perdu, il reste le même. C'est l'exact inverse de ce qu'on a avec les registres, mémoires SRAM, bascules et autres : tout est effacé quand on coupe le courant. Les mémoires RAM sont dites volatiles, alors que les mémoires ROM sont dites non-volatiles.
===Les mémoires ROM===
Il existe deux types de mémoires ROM : les ROM non-programmables et les ROM programmables. La différence est que les premières sont fournies telles quelle et qu'on ne peut pas changer leur contenu, alors que ce n'est pas le cas pour les secondes.
Les ROM programmables sont des ROM dans lesquelles on ne peut évidemment pas écrire, mais qui permettent cependant de réécrire intégralement leur contenu : on dit qu'on reprogramme la ROM. Insistons sur la différence entre reprogrammation et écriture : l'écriture permet de modifier un byte sélectionné/adressé, alors que la reprogrammation efface toute la mémoire et la réécrit en totalité. Ce terme de programmation vient du fait que les mémoires ROM sont souvent utilisées pour stocker des programmes sur certains ordinateurs assez simples.
Les mémoires non-programmables sont aussi appelées des '''''mask'' ROM'''. Elles sont utilisées dans quelques applications particulières, pour lesquelles on n'a pas besoin de changer leur contenu. Par exemple, elles étaient utilisées sur les vieilles consoles de jeux, pour stocker le jeu vidéo dans les cartouches. Elles servent aussi pour les ''firmware'' divers et variés, comme le ''firmware'' d'une imprimante ou d'une clé USB. De telles mémoires seront utiles dans les chapitres qui vont suivre. La raison en est que tout circuit combinatoire peut être remplacé par une mémoire adressable ! Imaginons que l'on souhaite créer un circuit combinatoire qui pour toute entrée A fournisse la sortie B. Celui-ci est équivalent à une ROM dont la lecture de l'adresse A renvoie B sur la sortie. Cette logique est notamment utilisée dans certains circuits programmables, les FPGA, comme on le verra plus tard.
===L'implémentation des mémoires ROM===
Les mémoires ROM sont conçues, sur le même principe que les mémoires SRAM : on combine des registres avec des multiplexeurs. Il y a cependant des différences importantes, liées au fait que les écritures sont interdites. Et il y a une grosse différence suivant que la mémoire soit reprogrammable ou non.
Si la mémoire est reprogrammable, la différence principale est que les registres sont conçus de manière à ne pas être effacés quand on coupe le courant. Ils ne sont pas fabriqués avec des bascules, mais avec d'autres circuits plus complexes, à base de transistors à grille flottante. Les bascules sont remplacés par un équivalent qui se comporte de la même manière, sauf qu'on ne peut pas changer leur contenu facilement (interdiction des écritures), et que leur contenu ne s'efface pas quand on coupe le courant. Il peut y avoir d'autres différences, mais nous verrons cela dans le chapitre dédié aux mémoires ROM.
Quant aux ''mask'' ROM, leur implémentation est beaucoup plus simple. Ils sont conçus sur le même principe que les SRAM. Sauf que vu que l'écriture et la reprogrammation sont interdites, on peut retirer les démultiplexeurs utilisés pour les écritures (et la reprogrammation). Quand aux registres, ils sont remplacés en connectant directement la tension d'alimentation ou la masse sur les entrées des multiplexeurs de lecture. Là où on veut mettre un 0, on connecte la masse. Là où on veut mettre un 1, on connecte la tension d'alimentation. Le circuit obtenu se simplifie alors et peut se remplacer par un circuit composé d'un décodeur connecté à un paquet de portes OU.
[[File:Rom simpel.png|centre|vignette|upright=2|Mémoire ROM simple.]]
L'implémentation d'une ''mask'' ROM est en réalité plus complexe sur certains points, notamment l'implémentation des portes OU, qui sont en réalité des OU câblés comme vu dans le chapitre sur les circuits imprimés. Mais nous reverrons cela dans quelques chapitres. L'important est que vous reteniez ce qu'est une mémoire ROM, qui n'est qu'un cas particulier de circuit combinatoire. Nous aurons à utiliser des mémoires ROM dans les chapitres suivants, à quelques endroits bien précis.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits synchrones et asynchrones
| prevText=Les circuits synchrones et asynchrones
| next=Les compteurs et timers
| nextText=Les compteurs et timers
}}
</noinclude>
srgm5mjmzhuignfex7h5nb5ufsrxco9
768637
768636
2026-06-25T16:29:34Z
Mewtow
31375
/* L'intérieur d'un banc de registre */
768637
wikitext
text/x-wiki
Dans les chapitres précédents, nous avons vu comment mémoriser un bit, dans une bascule. Mais les bascules en elles-mêmes sont rarement utiles seules, car les données à mémoriser font généralement plusieurs bits, pas un seul. Stocker plusieurs bits est la raison d'être des '''registres''', des composants qui mémorisent des plusieurs bits, que l'on peut modifier et/ou récupérer plus tard. Il existe plusieurs types de registres, et nous allons faire la distinction entre les registres simples et les registres à décalage. Les registres simples sont capables de mémoriser un nombre, de taille fixe, rien de plus. Les registres à décalage sont des registres simples améliorés, capables de faire quelques petites opérations sur leur contenu.
==Les registres simples==
Les '''registres simples''' sont capables de mémoriser un nombre, codé sur une quantité fixe de bits. On peut à tout moment récupérer le nombre mémorisé dans le registre : on dit alors qu'on effectue une lecture. On peut aussi mettre à jour le nombre mémorisé dans le registre, le remplacer par un autre : on dit qu'on effectue une écriture. Les seules opérations possibles sur ces registres sont la lecture (récupérer le nombre mémorisé dans le registre) et l'écriture (mettre à jour le nombre mémorisé dans le registre, le remplacer par un autre).
===L'interface d'un registre simple===
[[File:4 Bit Data Register.svg|vignette|Registre de 4 Bits. On voit que celui-ci contient 4 entrées (à gauche), et 4 sorties (à droite). On peut aussi remarquer une entrée CLK, qui joue le rôle d'entrée d'autorisation.]]
Niveau entrées et sorties, les registres possèdent des entrées-sorties pour les données mémorisées, mais aussi des entrées-sorties de commande. Les entrées-sorties pour les données permettent de lire le contenu du registre ou d'y écrire. Les entrées de commande permettent de configurer le registre pour lui ordonner de faire une écriture, pour le remettre à zéro, ou toute autre opération.
Les entrées de données sont utilisées pour l'écriture, alors que les sorties de données servent pour la lecture. Le nombre mémorisé dans le registre est disponible sur les sorties du registre. Pour utiliser les entrées d'écriture, on envoie le nombre à mémoriser (celui qui remplacera le contenu du registre) sur les entrées d'écriture et on configure les entrées de commande adéquates.
Les entrées de commande varient suivant le registre, mais on trouve au moins une entrée Enable, qui a le même rôle que pour une bascule, à savoir autoriser une écriture. Si l'entrée Enable est à 1, le registre mémorise ce qu'il y a sur l'entrée de donnée. Mais si l'entrée Enable est à 0, le registre n'est pas mis à jour : on peut mettre n'importe quelle valeur sur les entrées, le registre n'en tiendra pas compte et ne remplacera pas son contenu par ce qu'il y a sur l'entrée. Pour résumer, l'entrée Enable sert donc à indiquer au registre si son contenu doit être mis à jour, quand une écriture a lieu.
D'autres entrées de commandes sont parfois présentes, la plus commune étant une entrée permettant de remettre à zéro le registre. La présence d'un 1 sur cette entrée remet à zéro le contenu du registre, à savoir que celui-ci contient la valeur zéro.
Enfin, il faut distinguer les registres synchrones des registres asynchrones. Les '''registres synchrones''' sont reliés au signal d’horloge. Pour cela, ils disposent d'une entrée d'horloge sur laquelle on envoie le signal d'horloge. Ils ne sont mis à jour que si on présente un front montant sur l'entrée d'horloge. Les '''registres asynchrones''' ne sont pas reliés au signal d'horloge et sont mis à jour quand on envoie ce qu'il faut sur leur entré Enable, rien de plus.
===L'intérieur d'un registre simple===
[[File:Basic n-bit register.svg|vignette|Registre.]]
Un registre est composé de plusieurs bascules D qui sont toutes mises à jour en même temps. Cela vaut aussi bien pour les registres asynchrones que les registres synchrones. Pour cela, toutes les entrées E des bascules sont reliées à l'entrée de commande Enable. De plus, les registre synchrones envoient le signal d'horloge sur toutes les bascules. Avec un registre synchrone, toutes les bascules sont des bascules synchrones, qui ont toutes une entrée d'horloge, relié au signal d'horloge.
[[File:Register.svg|centre|vignette|upright=3|Registre.]]
==Les bancs de registre==
Maintenant que nous avons les registres, il est temps d'en montrer une utilisation assez intéressante. Nous allons combiner les registres avec des multiplexeurs/démultiplexeurs pour former une '''mémoire adressable'''. Plus précisément, nous allons voir les '''bancs de registre''', qui peuvent être vu comme un rassemblement de plusieurs registres. Mais ces registres ne sont pas assemblés pour obtenir un registre plus gros : par exemple, on peut fabriquer un registre de 32 bits à partir de 2 registres de 16 bits, ou de 4 registres de 8 bits. Ce n'est pas ce qui est fait sur les mémoires adressables, où les registres sont regroupés de manière à ce qu'il soit possible de sélectionner le registre qu'on veut consulter ou modifier.
Pour préciser le registre à sélectionner, chacun d'entre eux se voit attribuer un nombre : l''''adresse'''. On peut comparer une adresse à un numéro de téléphone (ou à une adresse d'appartement) : chacun de vos correspondants a un numéro de téléphone et vous savez que pour appeler telle personne, vous devez composer tel numéro. Les adresses mémoires en sont l'équivalent pour les registres d'une mémoire adressable. Il existe des mémoires qui ne fonctionnent pas sur ce principe, mais passons : ce sera pour la suite.
[[File:Adressage mémoire.png|centre|vignette|upright=2|Exemple : on demande à la mémoire de sélectionner le byte d'adresse 1002 et on récupère son contenu (ici, 17).]]
===L'intérieur d'un banc de registre===
Un banc de registre peut se fabriquer assez simplement : il suffit d'un ou de plusieurs multiplexeurs et de registres. Quand on présente l'adresse sur l'entrée de sélection du multiplexeur, celui-ci va connecter le registre demandé à la sortie (ou à l'entrée). La gestion des écritures est similaire et se fait avec un décodeur.
[[File:Intérieur d'une mémoire RAM.png|centre|vignette|upright=2|Intérieur d'une RAM fabriquée avec des registres et des multiplexeurs.]]
Pour la lecture, il suffit d'utiliser un "multiplexeur". Je mets des guillemets car ce n'est pas un multiplexeur qui prend en entrée N bits et a une sortie de 1 bit, comme les multiplexeurs classiques vus il y a quelques chapitres. Ici, il s'agit d'un multiplexeur qui prend en entrée N nombres et fournit en sortie un nombre, les nombres faisant ici B bits. Un tel "multiplxeur" est composé de B multiplexeurs normaux, précisément des multiplexeurs N vers 1. Le multiplexeur est connecté à chaque registre, un registre par entrée. Le processeur envoie le numéro du registre à lire sur l'entrée de commande, la donnée lue est disponible sur la sortie.
[[File:Addressable register read mux 01.jpg|centre|vignette|upright=1.5|Port de lecture du banc de registre.]]
Pour l'écriture, les choses sont plus complexes. Intuitivement, oin se dit qu'il faut utiliser un "démultiplexeur", mais ce n'est pas exactement cela. LA la place, la donnée à écrire est envoyée sur tous les registres. Concrètement, l'entrée d'écriture du banc de registre est reliée à l'entrée d'écriture de chaque registre. Par contre, un seul registre sera sélectionné pour l'écriture. Pour ce registre, l'entrée Enable sera à 1. Les autres registres auront leur entrée Enable à 0. Cela demande de générer N bits, dont un seul est à 1. Un décodeur est tout indiqué. Le décodeur prend en entrée le numéro du registre pour l'écriture, et active l'entrée Enable correspondante, de même numéro.
[[File:Addressable register write.jpg|centre|vignette|upright=1.5|Port d'écriture du banc de registre.]]
Voici ce que cela donne avec une RAM reliée à un bus de 1 bit, à savoir que chaque case mémoire ne contient que 1 bit, il y a un bit par adresse. Il s'agit d'un exemple bien trop simple pour avoir la moindre application pratique, mais c'est un exemple clairement pédagogique. L'entrée d'écriture est reliée à toutes les bascules, mais seule celle sélectionnée est écrite. Lors d'une lecture, l'adresse est envoyée au multiplexeur et la donnée lue sur sa sortie. Lors d'une écriture, c'est le démultiplexeur/décodeur qui est utilisé. Le décodeur active la bascule voulue, via son entrée d'horloge ou Enable. Le bit R/W précise qu'il faut effectuer une écriture. L'entrée d'écriture est alors recopiée dans la bascule sélectionnée.
[[File:MemWrite.png|centre|vignette|upright=2|Intérieur d'une RAM de 4 bits, reliée à un bus de 1 bit, fabriquée avec des registres et des multiplexeurs.]]
===L'interface d'un banc de registre===
[[File:RAM 8x4 symbol.svg|vignette|Interface d'une SRAM.]]
Niveau entrées et sorties, un banc de registre contient des entrées-sorties dédiées aux transferts de données et plusieurs entrées de commande.
Les entrées de commande permettent de configurer la mémoire pour effectuer une lecture ou écriture, la mettre en veille, ou autre. Parmi les entrées de commande, on trouve une entrée de plusieurs bits, sur laquelle on peut envoyer l'adresse, appelée l'entrée d'adressage. On trouve aussi une entrée R/W d'un bit, qui permet de préciser si on veut faire une lecture ou une écriture. On trouve aussi parfois une entrée ''Enable'' Ou ''Chip Select'', qui indique si la RAM est activée ou mise en veille, qui ressemble à l'entrée ''Enable'' des bascules.
Pour les données, tout dépend du banc de registre considéré. Sur certains bancs de registre, on trouve une sortie sur laquelle on peut récupérer le registre sélectionné (on dit qu'on lit le registre) et une entrée sur laquelle on peut envoyer une donnée destinée à être écrite dans le registre sélectionné (on dit qu'on écrit le registre). On a donc une sortie pour la lecture et une entrée pour l'écriture.
Mais sur d'autres bancs de registre, l'entrée et la sortie sont fusionnées en une seule entrée-sortie. Et d'autres bancs de registre ont au contraire plusieurs sorties de lecture. Rien de bien compliqué à cela : il suffit d'ajouter plusieurs multiplexeurs, au lieu d'un seul. Il suffit d'un multiplexeur par sortie de lecture.
==Les mémoire mortes et mémoires vives==
Les bancs de registres sont un sous-type de '''mémoire SRAM'''. Formellement, les mémoires SRAM sont des mémoires similaires aux bancs de registre, la différence étant que les bascules ne sont pas fabriquées avec des portes logiques, mais avec des transistors. Mais pour le reste, la conception reste sensiblement la même. Les SRAM sont composées de bascules de 1 bit, assemblées en "pseudo-registres", elles mêmes reliées à des multiplexeurs/démultiplexeurs. Elles sont très utilisées, surtout dans les processeurs. Les mémoires sont très diverses et les mémoires SRAM ne sont qu'un type de mémoires parmi tant d'autres.
Les mémoires SRAM font elles-mêmes partie de la catégorie des mémoires vives, aussi appelées '''mémoires RAM''' (bien que ce soit un abus de langage, comme on le verra dans plusieurs chapitres). De telles mémoires sont des mémoires électroniques, qui sont adressables, dans lesquelles on peut lire et écrire. Nous verrons les différents types de RAM dans les chapitres sur les mémoires, aussi nous allons mettre cela de côté pour le moment.
Outre les mémoires RAM, il existe des mémoires qui sont elles aussi électroniques, adressables, mais dans lesquelles on ne peut pas écrire : ce sont les '''mémoires ROM'''. En général, les mémoires ROM conservent leur contenu quand on coupe l’alimentation électrique. Si on éteint l'ordinateur, le contenu de la ROM n'est pas perdu, il reste le même. C'est l'exact inverse de ce qu'on a avec les registres, mémoires SRAM, bascules et autres : tout est effacé quand on coupe le courant. Les mémoires RAM sont dites volatiles, alors que les mémoires ROM sont dites non-volatiles.
===Les mémoires ROM===
Il existe deux types de mémoires ROM : les ROM non-programmables et les ROM programmables. La différence est que les premières sont fournies telles quelle et qu'on ne peut pas changer leur contenu, alors que ce n'est pas le cas pour les secondes.
Les ROM programmables sont des ROM dans lesquelles on ne peut évidemment pas écrire, mais qui permettent cependant de réécrire intégralement leur contenu : on dit qu'on reprogramme la ROM. Insistons sur la différence entre reprogrammation et écriture : l'écriture permet de modifier un byte sélectionné/adressé, alors que la reprogrammation efface toute la mémoire et la réécrit en totalité. Ce terme de programmation vient du fait que les mémoires ROM sont souvent utilisées pour stocker des programmes sur certains ordinateurs assez simples.
Les mémoires non-programmables sont aussi appelées des '''''mask'' ROM'''. Elles sont utilisées dans quelques applications particulières, pour lesquelles on n'a pas besoin de changer leur contenu. Par exemple, elles étaient utilisées sur les vieilles consoles de jeux, pour stocker le jeu vidéo dans les cartouches. Elles servent aussi pour les ''firmware'' divers et variés, comme le ''firmware'' d'une imprimante ou d'une clé USB. De telles mémoires seront utiles dans les chapitres qui vont suivre. La raison en est que tout circuit combinatoire peut être remplacé par une mémoire adressable ! Imaginons que l'on souhaite créer un circuit combinatoire qui pour toute entrée A fournisse la sortie B. Celui-ci est équivalent à une ROM dont la lecture de l'adresse A renvoie B sur la sortie. Cette logique est notamment utilisée dans certains circuits programmables, les FPGA, comme on le verra plus tard.
===L'implémentation des mémoires ROM===
Les mémoires ROM sont conçues, sur le même principe que les mémoires SRAM : on combine des registres avec des multiplexeurs. Il y a cependant des différences importantes, liées au fait que les écritures sont interdites. Et il y a une grosse différence suivant que la mémoire soit reprogrammable ou non.
Si la mémoire est reprogrammable, la différence principale est que les registres sont conçus de manière à ne pas être effacés quand on coupe le courant. Ils ne sont pas fabriqués avec des bascules, mais avec d'autres circuits plus complexes, à base de transistors à grille flottante. Les bascules sont remplacés par un équivalent qui se comporte de la même manière, sauf qu'on ne peut pas changer leur contenu facilement (interdiction des écritures), et que leur contenu ne s'efface pas quand on coupe le courant. Il peut y avoir d'autres différences, mais nous verrons cela dans le chapitre dédié aux mémoires ROM.
Quant aux ''mask'' ROM, leur implémentation est beaucoup plus simple. Ils sont conçus sur le même principe que les SRAM. Sauf que vu que l'écriture et la reprogrammation sont interdites, on peut retirer les démultiplexeurs utilisés pour les écritures (et la reprogrammation). Quand aux registres, ils sont remplacés en connectant directement la tension d'alimentation ou la masse sur les entrées des multiplexeurs de lecture. Là où on veut mettre un 0, on connecte la masse. Là où on veut mettre un 1, on connecte la tension d'alimentation. Le circuit obtenu se simplifie alors et peut se remplacer par un circuit composé d'un décodeur connecté à un paquet de portes OU.
[[File:Rom simpel.png|centre|vignette|upright=2|Mémoire ROM simple.]]
L'implémentation d'une ''mask'' ROM est en réalité plus complexe sur certains points, notamment l'implémentation des portes OU, qui sont en réalité des OU câblés comme vu dans le chapitre sur les circuits imprimés. Mais nous reverrons cela dans quelques chapitres. L'important est que vous reteniez ce qu'est une mémoire ROM, qui n'est qu'un cas particulier de circuit combinatoire. Nous aurons à utiliser des mémoires ROM dans les chapitres suivants, à quelques endroits bien précis.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits synchrones et asynchrones
| prevText=Les circuits synchrones et asynchrones
| next=Les compteurs et timers
| nextText=Les compteurs et timers
}}
</noinclude>
ageg8hwwl0zbcwwfowtcdfgw2h0rv1g
768638
768637
2026-06-25T16:35:16Z
Mewtow
31375
/* Les bancs de registre */
768638
wikitext
text/x-wiki
Dans les chapitres précédents, nous avons vu comment mémoriser un bit, dans une bascule. Mais les bascules en elles-mêmes sont rarement utiles seules, car les données à mémoriser font généralement plusieurs bits, pas un seul. Stocker plusieurs bits est la raison d'être des '''registres''', des composants qui mémorisent des plusieurs bits, que l'on peut modifier et/ou récupérer plus tard. Il existe plusieurs types de registres, et nous allons faire la distinction entre les registres simples et les registres à décalage. Les registres simples sont capables de mémoriser un nombre, de taille fixe, rien de plus. Les registres à décalage sont des registres simples améliorés, capables de faire quelques petites opérations sur leur contenu.
==Les registres simples==
Les '''registres simples''' sont capables de mémoriser un nombre, codé sur une quantité fixe de bits. On peut à tout moment récupérer le nombre mémorisé dans le registre : on dit alors qu'on effectue une lecture. On peut aussi mettre à jour le nombre mémorisé dans le registre, le remplacer par un autre : on dit qu'on effectue une écriture. Les seules opérations possibles sur ces registres sont la lecture (récupérer le nombre mémorisé dans le registre) et l'écriture (mettre à jour le nombre mémorisé dans le registre, le remplacer par un autre).
===L'interface d'un registre simple===
[[File:4 Bit Data Register.svg|vignette|Registre de 4 Bits. On voit que celui-ci contient 4 entrées (à gauche), et 4 sorties (à droite). On peut aussi remarquer une entrée CLK, qui joue le rôle d'entrée d'autorisation.]]
Niveau entrées et sorties, les registres possèdent des entrées-sorties pour les données mémorisées, mais aussi des entrées-sorties de commande. Les entrées-sorties pour les données permettent de lire le contenu du registre ou d'y écrire. Les entrées de commande permettent de configurer le registre pour lui ordonner de faire une écriture, pour le remettre à zéro, ou toute autre opération.
Les entrées de données sont utilisées pour l'écriture, alors que les sorties de données servent pour la lecture. Le nombre mémorisé dans le registre est disponible sur les sorties du registre. Pour utiliser les entrées d'écriture, on envoie le nombre à mémoriser (celui qui remplacera le contenu du registre) sur les entrées d'écriture et on configure les entrées de commande adéquates.
Les entrées de commande varient suivant le registre, mais on trouve au moins une entrée Enable, qui a le même rôle que pour une bascule, à savoir autoriser une écriture. Si l'entrée Enable est à 1, le registre mémorise ce qu'il y a sur l'entrée de donnée. Mais si l'entrée Enable est à 0, le registre n'est pas mis à jour : on peut mettre n'importe quelle valeur sur les entrées, le registre n'en tiendra pas compte et ne remplacera pas son contenu par ce qu'il y a sur l'entrée. Pour résumer, l'entrée Enable sert donc à indiquer au registre si son contenu doit être mis à jour, quand une écriture a lieu.
D'autres entrées de commandes sont parfois présentes, la plus commune étant une entrée permettant de remettre à zéro le registre. La présence d'un 1 sur cette entrée remet à zéro le contenu du registre, à savoir que celui-ci contient la valeur zéro.
Enfin, il faut distinguer les registres synchrones des registres asynchrones. Les '''registres synchrones''' sont reliés au signal d’horloge. Pour cela, ils disposent d'une entrée d'horloge sur laquelle on envoie le signal d'horloge. Ils ne sont mis à jour que si on présente un front montant sur l'entrée d'horloge. Les '''registres asynchrones''' ne sont pas reliés au signal d'horloge et sont mis à jour quand on envoie ce qu'il faut sur leur entré Enable, rien de plus.
===L'intérieur d'un registre simple===
[[File:Basic n-bit register.svg|vignette|Registre.]]
Un registre est composé de plusieurs bascules D qui sont toutes mises à jour en même temps. Cela vaut aussi bien pour les registres asynchrones que les registres synchrones. Pour cela, toutes les entrées E des bascules sont reliées à l'entrée de commande Enable. De plus, les registre synchrones envoient le signal d'horloge sur toutes les bascules. Avec un registre synchrone, toutes les bascules sont des bascules synchrones, qui ont toutes une entrée d'horloge, relié au signal d'horloge.
[[File:Register.svg|centre|vignette|upright=3|Registre.]]
==Les bancs de registre==
Maintenant que nous avons les registres, il est temps d'en montrer une utilisation assez intéressante. Nous allons combiner les registres avec des multiplexeurs/démultiplexeurs pour former une '''mémoire adressable'''. Plus précisément, nous allons voir les '''bancs de registre''', qui peuvent être vu comme un rassemblement de plusieurs registres. Mais ces registres ne sont pas assemblés pour obtenir un registre plus gros : par exemple, on peut fabriquer un registre de 32 bits à partir de 2 registres de 16 bits, ou de 4 registres de 8 bits. Ce n'est pas ce qui est fait sur les mémoires adressables, où les registres sont regroupés de manière à ce qu'il soit possible de sélectionner le registre qu'on veut consulter ou modifier.
Pour préciser le registre à sélectionner, chacun d'entre eux se voit attribuer un nombre : l''''adresse'''. On peut comparer une adresse à un numéro de téléphone (ou à une adresse d'appartement) : chacun de vos correspondants a un numéro de téléphone et vous savez que pour appeler telle personne, vous devez composer tel numéro. Les adresses mémoires en sont l'équivalent pour les registres d'une mémoire adressable. Il existe des mémoires qui ne fonctionnent pas sur ce principe, mais passons : ce sera pour la suite.
[[File:Adressage mémoire.png|centre|vignette|upright=2|Exemple : on demande à la mémoire de sélectionner le byte d'adresse 1002 et on récupère son contenu (ici, 17).]]
===L'interface d'un banc de registre===
[[File:RAM 8x4 symbol.svg|vignette|Interface d'une SRAM.]]
Niveau entrées et sorties, un banc de registre contient des entrées-sorties dédiées aux transferts de données et plusieurs entrées de commande.
Les entrées de commande permettent de configurer la mémoire pour effectuer une lecture ou écriture, la mettre en veille, ou autre. Parmi les entrées de commande, on trouve une entrée de plusieurs bits, sur laquelle on peut envoyer l'adresse, appelée l'entrée d'adressage. On trouve aussi une entrée R/W d'un bit, qui permet de préciser si on veut faire une lecture ou une écriture. On trouve aussi parfois une entrée ''Enable'' Ou ''Chip Select'', qui indique si la RAM est activée ou mise en veille, qui ressemble à l'entrée ''Enable'' des bascules.
Pour les données, tout dépend du banc de registre considéré. Sur certains bancs de registre, on trouve une sortie sur laquelle on peut récupérer le registre sélectionné (on dit qu'on lit le registre) et une entrée sur laquelle on peut envoyer une donnée destinée à être écrite dans le registre sélectionné (on dit qu'on écrit le registre). On a donc une sortie pour la lecture et une entrée pour l'écriture.
Mais sur d'autres bancs de registre, l'entrée et la sortie sont fusionnées en une seule entrée-sortie.
D'autres bancs de registre ont au contraire plusieurs sorties de lecture.
===L'intérieur d'un banc de registre===
Un banc de registre peut se fabriquer assez simplement : il suffit d'un ou de plusieurs multiplexeurs et de registres. Quand on présente l'adresse sur l'entrée de sélection du multiplexeur, celui-ci va connecter le registre demandé à la sortie (ou à l'entrée). La gestion des écritures est similaire et se fait avec un décodeur.
[[File:Intérieur d'une mémoire RAM.png|centre|vignette|upright=2|Intérieur d'une RAM fabriquée avec des registres et des multiplexeurs.]]
Pour la lecture, il suffit d'utiliser un "multiplexeur". Je mets des guillemets car ce n'est pas un multiplexeur qui prend en entrée N bits et a une sortie de 1 bit, comme les multiplexeurs classiques vus il y a quelques chapitres. Ici, il s'agit d'un multiplexeur qui prend en entrée N nombres et fournit en sortie un nombre, les nombres faisant ici B bits. Un tel "multiplxeur" est composé de B multiplexeurs normaux, précisément des multiplexeurs N vers 1. Le multiplexeur est connecté à chaque registre, un registre par entrée. Le processeur envoie le numéro du registre à lire sur l'entrée de commande, la donnée lue est disponible sur la sortie.
Pour avoir plusieurs portes de lecture, il suffit d'ajouter plusieurs "multiplexeurs", un par sortie de lecture.
[[File:Addressable register read mux 01.jpg|centre|vignette|upright=1.5|Port de lecture du banc de registre.]]
Pour l'écriture, les choses sont plus complexes. Intuitivement, oin se dit qu'il faut utiliser un "démultiplexeur", mais ce n'est pas exactement cela. LA la place, la donnée à écrire est envoyée sur tous les registres. Concrètement, l'entrée d'écriture du banc de registre est reliée à l'entrée d'écriture de chaque registre. Par contre, un seul registre sera sélectionné pour l'écriture. Pour ce registre, l'entrée Enable sera à 1. Les autres registres auront leur entrée Enable à 0. Cela demande de générer N bits, dont un seul est à 1. Un décodeur est tout indiqué. Le décodeur prend en entrée le numéro du registre pour l'écriture, et active l'entrée Enable correspondante, de même numéro.
[[File:Addressable register write.jpg|centre|vignette|upright=1.5|Port d'écriture du banc de registre.]]
Voici ce que cela donne avec une RAM reliée à un bus de 1 bit, à savoir que chaque case mémoire ne contient que 1 bit, il y a un bit par adresse. Il s'agit d'un exemple bien trop simple pour avoir la moindre application pratique, mais c'est un exemple clairement pédagogique. L'entrée d'écriture est reliée à toutes les bascules, mais seule celle sélectionnée est écrite. Lors d'une lecture, l'adresse est envoyée au multiplexeur et la donnée lue sur sa sortie. Lors d'une écriture, c'est le démultiplexeur/décodeur qui est utilisé. Le décodeur active la bascule voulue, via son entrée d'horloge ou Enable. Le bit R/W précise qu'il faut effectuer une écriture. L'entrée d'écriture est alors recopiée dans la bascule sélectionnée.
[[File:MemWrite.png|centre|vignette|upright=2|Intérieur d'une RAM de 4 bits, reliée à un bus de 1 bit, fabriquée avec des registres et des multiplexeurs.]]
==Les mémoire mortes et mémoires vives==
Les bancs de registres sont un sous-type de '''mémoire SRAM'''. Formellement, les mémoires SRAM sont des mémoires similaires aux bancs de registre, la différence étant que les bascules ne sont pas fabriquées avec des portes logiques, mais avec des transistors. Mais pour le reste, la conception reste sensiblement la même. Les SRAM sont composées de bascules de 1 bit, assemblées en "pseudo-registres", elles mêmes reliées à des multiplexeurs/démultiplexeurs. Elles sont très utilisées, surtout dans les processeurs. Les mémoires sont très diverses et les mémoires SRAM ne sont qu'un type de mémoires parmi tant d'autres.
Les mémoires SRAM font elles-mêmes partie de la catégorie des mémoires vives, aussi appelées '''mémoires RAM''' (bien que ce soit un abus de langage, comme on le verra dans plusieurs chapitres). De telles mémoires sont des mémoires électroniques, qui sont adressables, dans lesquelles on peut lire et écrire. Nous verrons les différents types de RAM dans les chapitres sur les mémoires, aussi nous allons mettre cela de côté pour le moment.
Outre les mémoires RAM, il existe des mémoires qui sont elles aussi électroniques, adressables, mais dans lesquelles on ne peut pas écrire : ce sont les '''mémoires ROM'''. En général, les mémoires ROM conservent leur contenu quand on coupe l’alimentation électrique. Si on éteint l'ordinateur, le contenu de la ROM n'est pas perdu, il reste le même. C'est l'exact inverse de ce qu'on a avec les registres, mémoires SRAM, bascules et autres : tout est effacé quand on coupe le courant. Les mémoires RAM sont dites volatiles, alors que les mémoires ROM sont dites non-volatiles.
===Les mémoires ROM===
Il existe deux types de mémoires ROM : les ROM non-programmables et les ROM programmables. La différence est que les premières sont fournies telles quelle et qu'on ne peut pas changer leur contenu, alors que ce n'est pas le cas pour les secondes.
Les ROM programmables sont des ROM dans lesquelles on ne peut évidemment pas écrire, mais qui permettent cependant de réécrire intégralement leur contenu : on dit qu'on reprogramme la ROM. Insistons sur la différence entre reprogrammation et écriture : l'écriture permet de modifier un byte sélectionné/adressé, alors que la reprogrammation efface toute la mémoire et la réécrit en totalité. Ce terme de programmation vient du fait que les mémoires ROM sont souvent utilisées pour stocker des programmes sur certains ordinateurs assez simples.
Les mémoires non-programmables sont aussi appelées des '''''mask'' ROM'''. Elles sont utilisées dans quelques applications particulières, pour lesquelles on n'a pas besoin de changer leur contenu. Par exemple, elles étaient utilisées sur les vieilles consoles de jeux, pour stocker le jeu vidéo dans les cartouches. Elles servent aussi pour les ''firmware'' divers et variés, comme le ''firmware'' d'une imprimante ou d'une clé USB. De telles mémoires seront utiles dans les chapitres qui vont suivre. La raison en est que tout circuit combinatoire peut être remplacé par une mémoire adressable ! Imaginons que l'on souhaite créer un circuit combinatoire qui pour toute entrée A fournisse la sortie B. Celui-ci est équivalent à une ROM dont la lecture de l'adresse A renvoie B sur la sortie. Cette logique est notamment utilisée dans certains circuits programmables, les FPGA, comme on le verra plus tard.
===L'implémentation des mémoires ROM===
Les mémoires ROM sont conçues, sur le même principe que les mémoires SRAM : on combine des registres avec des multiplexeurs. Il y a cependant des différences importantes, liées au fait que les écritures sont interdites. Et il y a une grosse différence suivant que la mémoire soit reprogrammable ou non.
Si la mémoire est reprogrammable, la différence principale est que les registres sont conçus de manière à ne pas être effacés quand on coupe le courant. Ils ne sont pas fabriqués avec des bascules, mais avec d'autres circuits plus complexes, à base de transistors à grille flottante. Les bascules sont remplacés par un équivalent qui se comporte de la même manière, sauf qu'on ne peut pas changer leur contenu facilement (interdiction des écritures), et que leur contenu ne s'efface pas quand on coupe le courant. Il peut y avoir d'autres différences, mais nous verrons cela dans le chapitre dédié aux mémoires ROM.
Quant aux ''mask'' ROM, leur implémentation est beaucoup plus simple. Ils sont conçus sur le même principe que les SRAM. Sauf que vu que l'écriture et la reprogrammation sont interdites, on peut retirer les démultiplexeurs utilisés pour les écritures (et la reprogrammation). Quand aux registres, ils sont remplacés en connectant directement la tension d'alimentation ou la masse sur les entrées des multiplexeurs de lecture. Là où on veut mettre un 0, on connecte la masse. Là où on veut mettre un 1, on connecte la tension d'alimentation. Le circuit obtenu se simplifie alors et peut se remplacer par un circuit composé d'un décodeur connecté à un paquet de portes OU.
[[File:Rom simpel.png|centre|vignette|upright=2|Mémoire ROM simple.]]
L'implémentation d'une ''mask'' ROM est en réalité plus complexe sur certains points, notamment l'implémentation des portes OU, qui sont en réalité des OU câblés comme vu dans le chapitre sur les circuits imprimés. Mais nous reverrons cela dans quelques chapitres. L'important est que vous reteniez ce qu'est une mémoire ROM, qui n'est qu'un cas particulier de circuit combinatoire. Nous aurons à utiliser des mémoires ROM dans les chapitres suivants, à quelques endroits bien précis.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits synchrones et asynchrones
| prevText=Les circuits synchrones et asynchrones
| next=Les compteurs et timers
| nextText=Les compteurs et timers
}}
</noinclude>
jaowb1n0t2tqalmbty7clzljcehyxsm
Python pour le calcul scientifique/Algèbre linéaire
0
78137
768632
766038
2026-06-25T15:30:01Z
Cdang
1202
/* Opérations matricielles */ matrix -> array
768632
wikitext
text/x-wiki
Rappelons que dorénavant les programmes commencent tous par :
<syntaxhighlight lang="python">
#!/usr/bin/python3
import numpy as np
import matplotlib.pyplot as plt
</syntaxhighlight>
L'algèbre linéaire consiste essentiellement en la manipulation de matrices.
{{loupe|../Manipulation de matrices}}
== Modules à utiliser ==
Deux modules fournissent les fonctions utiles pour l'algèbre linéaire : <code lang="python">numpy.linalg</code> et <code lang="python">scipy.linalg</code>. Le module SciPy est plus complet et est parfois plus rapide que le module NumPy. Nous considérons donc que nous utilisons le premier et ajoutons en en-tête :
<syntaxhighlight lang="python">
from scipy import linalg
</syntaxhighlight>
== Opérations vectorielles ==
Un vecteur ''a'' de composantes (''a''<sub>1</sub>, ''a''<sub>2</sub>, ''a''<sub>3</sub>) se définit par : <code>a = np.array([a1, a2, a3])</code>.
La somme de deux vecteurs ''a'' + ''b'' s'obtient par : <code>a + b</code>.
Le produit scalaire réel ''a'' ⋅ ''b'' s'obtient par : <code>a @ b</code>, <code>np.dot(a, b)</code> ou bien <code>a.dot(b)</code>.
Pour un espace vectoriel complexe, le produit hermitien ''a'' ⋅ ''b'' = ∑{{surligner|''a''}}<sub>''i''</sub> ⋅ ''b''<sub>''i''</sub> s'obtient par : <code>np.vecdot(a, b)</code>.
Le produit vectoriel ''a'' ∧ ''b'' (ou ''a'' × ''b'') s'obtient par : <code>np.cross(a, b)</code>.
{{note|1=Une matrice 1 × 3, crée par <code>a = np.matrix([a1, a2, a3])</code>, va être similaire à un vecteur de dimension 3, mais on ne pourra pas appliquer le produit scalaire avec <code>np.dot()</code> ou bien la méthode <code>.dot()</code>. On pourra en revanche utiliser le produit matriciel <code>a @ b.T</code>.}}
== Opérations matricielles ==
Une matrice
: <math>\mathrm{M} = \begin{pmatrix} m_{11} & m_{12} & m_{13} \\ m_{21} & m_{22} & m_{23} \\ m_{31} & m_{32} & m_{33} \end{pmatrix}</math>
se définit par : <code>M = np.array([[m11, m12, m13], [m21, m22, m23], [m31, m32, m33]])</code>.
La transposition <sup>t</sup>M s'obtient par <code>M.T</code>.
La somme de deux matrices A + B s'obtient par <code>A + B</code>.
Le produit ''a'' ⋅ M d'une matrice M par un scalaire ''a'' s'obtient par <code>a * M</code> ou bien <code>np.multiply(a, M)</code>.
Le produit matriciel A ⋅ B s'obtient par <code>A @ B</code> ou bien <code>np.matmult(A, B)</code>.
== Norme et déterminant ==
La norme « classique » s'obtient par <code lang="python">linalg.norm()</code> ; pour les vecteurs, il s'agit de la norme d'ordre 2 (L2) et pour les matrice, la norme de Frobénius
<syntaxhighlight lang="python">
A = np.array([[1,2],[3,4]])
linalg.norm(A) # 5.4772...
</syntaxhighlight>
Pour les vecteurs, on peut utiliser une norme d'ordre ''n'' quelconque, <math>\| x \| = \left ( \sum_i | x_i |^n \right ) ^{1/n}</math> avec
<syntaxhighlight lang="python">
linalg.norm(x, n)
</syntaxhighlight>
Si ''n'' prend pour valeur <code lang="python">inf</code>, cela renvoie max(|''x<sub>i</sub>''|) ; pour valeur <code lang="python">-inf</code>, min(|''x<sub>i</sub>''|).
Pour une matrice, les valeurs d'ordre possibles sont <code lang="python">±1</code>, <code lang="python">±2</code>, <code lang="python">±inf</code> et <code lang="python">"fro"</code> (pour Frobenius, valeur par défaut)<ref>voir {{lien web |url=https://scipy.github.io/devdocs/tutorial/linalg.html#computing-norms |titre=Computing norms |site=SciPy.org | lang=en |consulté le=2022-05-03}}.</ref>.
Si la matrice est un assemblage de vecteurs, alors on peut calculer la norme de chaque vecteur en indiquant le numéro de l'indice (l'axe) selon lequel on calcule la norme. Par exemple, si on a une matrice de vecteurs colonne M = [[X<sub>1</sub>, X<sub>2</sub>, …], [Y<sub>1</sub>, Y<sub>2</sub>, …]], alors <code lang="python">linalg.norm(M, axis=0)</code> va calculer la norme selon l'indice 0 (le numéro de ligne) : [|(X<sub>1</sub>, Y<sub>1</sub>)|, |(X<sub>2</sub>, Y<sub>2</sub>)|, …].
Le déterminant s'obtient avec
<syntaxhighlight lang="python">
linalg.det(A)
</syntaxhighlight>
== Résolution d'un système linéaire ==
Un système linéaire d'équations peut se représenter par une équation matricielle :
: <math>
\begin{cases}
a_1\cdot x + b_1\cdot y = _c1 \\
a_2\cdot x + b_2\cdot y = c_2 \\
\end{cases} \Leftrightarrow \mathrm{A} \cdot \mathrm{X} = b
</math>
avec
: <math>
\mathrm{A} = \begin{Bmatrix}
a_1 & b_1 \\
a_2 & b_2
\end{Bmatrix} ;
\mathrm{X} = \begin{Bmatrix} x \\ y \end{Bmatrix} ;
b = \begin{Bmatrix} c_1 \\ c_2 \end{Bmatrix}
</math>
Si le système possède une solution, alors la matrice A est inversible et le résultat peut s'obtenir par :
: <math>
\mathrm{X} = \mathrm{A}^{-1} \cdot b
</math>
En Python, cela peut s'obtenir de deux manières :
* <code lang="python">X = linalg.inv(A).dot(b)</code> ;
* <code lang="python">X = linalg.solve(A, b)</code> ;
la seconde méthode étant plus rapide.
Par exemple, pour résoudre le système
: <math>
\begin{cases}
x + 2\cdot y = 5 \\
3\cdot x + 4\cdot y = 6 \\
\end{cases}
</math>
<syntaxhighlight lang="python">
A = np.array([[1, 2], [3, 4]])
b = np.array([[5], [6]])
X = linalg.solve(A, b)
print(X)
# [[-4. ]
# [ 4.5]]
</syntaxhighlight>
== Notes et références ==
{{références}}
----
[[../Régression et optimisation|Régression et optimisation]] < [[../|↑]] > [[../Calcul différentiel et intégral|Calcul différentiel et intégral]]
{{DEFAULTSORT:Algebre lineaire}}
[[Catégorie:Python pour le calcul scientifique (livre)]]
qxszii2lgpf7oeodwllhwf7muhnqn55
Fonctionnement d'un ordinateur/L'interface d'une mémoire électronique
0
78252
768605
765326
2026-06-25T14:57:19Z
Mewtow
31375
/* L’interfaçage avec le bus de données : rappels */
768605
wikitext
text/x-wiki
Une mémoire communique avec d'autres composants : le processeur, les entrées-sorties, et peut-être d'autres. Pour cela, la mémoire est reliée à un ou plusieurs bus, des ensembles de fils qui permettent de la connecter aux autres composants. Suivant la mémoire et sa place dans la hiérarchie mémoire, le bus sera plus ou moins spécialisé. Par exemple, la mémoire principale est reliée au processeur et aux entrées-sorties via le bus système. Pour les autres mémoires, la logique est la même, si ce n'est que la mémoire est reliée à d'autres composants électroniques : une unité de calcul pour les registres, par exemple.
Dans tous les cas, le bus connecté à la mémoire est composé de deux ensembles de fils : le '''bus de données''' et le '''bus de commande'''. Le bus de données permet les transferts de données avec la mémoire, alors que le bus de commande prend en charge tout le reste. Nous allons commencer par voir le bus de données avant le bus de commandes, vu que son abord est plus simple. Le bus de commande est un ensemble d'entrées, là où ce n'est pas forcément le cas pour le bus de données. Le bus de données est soit une sortie (sur les mémoires ROM), soit une entrée-sortie (sur les mémoires RAM), les exceptions étant rares.
==Le bus de commande==
[[File:RAM 8x4 symbol.svg|vignette|Bus d'une mémoire RAM.]]
Le bus de commande transmet des commandes mémoire, des ordres auxquels la mémoire va devoir réagir pour faire ce qu'on lui demande. Dans les grandes lignes, chaque commande contient des bits qui ont une fonction fixée lors de la conception de la mémoire. Et les bits utilisés sont rarement les mêmes d'une mémoire à l'autre. Dans ce qui suit, nous verrons quelques bits qui reviennent régulièrement dans les bus de commande les plus communs, mais sachez qu'ils sont en réalité facultatifs. Le bus de commande dépend énormément du bus utilisé ou de la mémoire. Certains bus de commande se contentent d'un seul bit, d'autres en ont une dizaine, et d'autres en ont une petite centaine.
Comme on le verra plus bas, les mémoires adressables ont généralement des broches dédiées aux adresses, qui sont connectées au bus d'adresse. Mais les autres mémoires s'en passent et il arrive que certaines mémoires adressables arrivent à s'en passer. Pour résumer, le bus d'adresse est facultatif, seules certaines mémoires en ayant réellement un. On peut d'ailleurs voir le bus d'adresse comme une sous-partie du bus de commandes.
===Les bits ''Chip Select'' et ''Output Enable''===
La majorité des mémoires possède deux broches/bits qui servent à l'activer ou la désactiver : le '''bit CS''' (''Chip Select''). Lorsque ce bit est à 1, toutes les autres broches sont désactivées, qu'elles appartiennent au bus de données ou de commande. On verra dans quelques chapitres l'utilité de ce bit. Pour le moment, on peut dire qu'il permet d'éteindre une mémoire (temporairement) inutilisée. L'économie d'énergie qui en découle est souvent intéressante.
Tout aussi fréquent, le '''bit OE''' (''Output Enable'') désactive les broches du bus de données, laissant cependant le bus de commande fonctionner. Ce bit déconnecte la mémoire du bus de données, stoppant les transferts. Il a une utilité similaire au bit CE, avec cependant quelques différences. Ce bit ne va pas éteindre la mémoire, mais juste stopper les transmissions. L'économie d'énergie est donc plus faible. Cependant, déconnecter la mémoire est beaucoup plus rapide que de l'éteindre. On verra dans quelques chapitres l'utilité de ce bit. Grossièrement, il permet de déconnecter une mémoire quand un composant prioritaire souhaite communiquer sur le bus, en même temps que la mémoire.
===L'entrée d'horloge ou de synchronisation===
Certaines mémoires assez anciennes n'étaient pas synchronisées par un signal d'horloge, mais par d'autres procédés : on les appelle des mémoires asynchrones. Les bus de commande de ces mémoires devaient transmettre les informations de synchronisation, sous la forme de '''bits de synchronisation'''.
D'autres mémoires sont cadencées par un signal d'horloge : elles portent le nom de mémoires synchrones. Ces mémoires ont un bus de commande beaucoup plus simple, qui n'a qu'une seule broche de synchronisation. Celle-ci reçoit le signal d'horloge, d'où le nom d''''entrée d'horloge''' qui lui est donné.
===Les bits de lecture/écriture===
Le bus de commande doit préciser à la mémoire s'il faut effectuer une lecture ou une écriture. Pour cela, le bus envoie sur le bus de commande un bit appelé '''bit R/W''', qui indique s'il faut faire une lecture ou une écriture. Il est souvent admis par convention que R/W à 1 correspond à une lecture, tandis que R/W vaut 0 pour les écritures. Ce bit de commande est évidemment inutile sur les mémoires ROM, vu qu'elles ne peuvent effectuer que des lectures. Notons que les mémoires qui ont un bit R/W ont souvent un bit OE, bien que ce ne soit pas systématique. En effet, une mémoire n'a pas toujours une lecture ou écriture à effectuer et il faut préciser à la mémoire qu'elle n'a rien à faire, ce que le bit OE peut faire.
{|class="wikitable"
|-
! Bit OE
! Bit R/W
! Opération demandée à la mémoire
|-
| 0
| 0
| NOP (pas d'opération)
|-
| 0
| 1
| NOP (pas d'opération)
|-
| 1
| 0
| Écriture
|-
| 1
| 1
| Lecture
|}
Une autre solution est d'utiliser un bit pour indiquer qu'on veut faire une lecture, et un autre bit pour indiquer qu'on veut démarrer une écriture. On pourrait croire que c'est un gâchis, mais c'est en réalité assez pertinent. L'avantage est que la combinaison des deux bits permet de coder quatre valeurs : 00, 01, 10 et 11. En tout, on a donc une valeur pour la lecture, une pour l'écriture, et deux autres valeurs. La logique veut qu'une de ces valeur, le plus souvent 00, indique l'absence de lecture et d'écriture. Cela permet de fusionner le bit R/W avec le bit OE. Au lieu de mettre un bit OE à 0 quand la mémoire n'est pas utilisée, on a juste à mettre le bit de lecture et le bit d'écriture à 0 pour indiquer à la mémoire qu'elle n'a rien à faire. La valeur restante peut être utilisée pour autre chose, ce qui est utile sur les mémoires qui gèrent d'autres opérations que la lecture et l'écriture. Par exemple, les mémoires EPROM et EEPROM gèrent aussi l'effacement et il faut pouvoir le préciser.
{|class="wikitable"
|-
! Bit de lecture
! Bit d'écriture
! Opération demandée à la mémoire
|-
| 0
| 0
| NOP (pas d'opération)
|-
| 0
| 1
| Ecriture
|-
| 1
| 0
| Lecture
|-
| 1
| 1
| Interdit, ou alors code pour une autre opération (reprogrammation, effacement, NOP sur certaines mémoires)
|}
==Le bus d'adresse (facultatif)==
Toutes les mémoires adressables sont naturellement connectées au bus. La transmission de l'adresse à la mémoire peut se faire de plusieurs manières. La plus simple utilise un bus dédié pour envoyer les adresses à la mémoire, séparé du bus de données et du bus de commande. Le bus en question est appelé le '''bus d'adresse'''.
[[File:Entrées et sorties d'un bus normal.png|centre|vignette|upright=2|Entrées et sorties d'un bus normal.]]
Mais d'autres mémoires font autrement et fusionnent le bus d'adresse et de données. Le bus de commande existe toujours, il est secondé par un autre bus qui sert à transmettre données et adresses, mais pas en même temps. De tels bus sont appelés soit des bus multiplexés, soit des bus à transmission par paquet. Les deux méthodes sont légèrement différentes, comme on le verra dans ce qui suit.
===Les bus d'adresse multiplexés===
Avec un bus d'adresse dédié, il existe quelques astuces pour économiser des fils. La première astuce est d'envoyer l'adresse en plusieurs fois. Sur beaucoup de mémoires, l'adresse est envoyée en deux fois. Les bits de poids fort sont envoyés avant les bits de poids faible. On peut ainsi envoyer une adresse de 32 bits sur un bus d'adresse de 16 bits, par exemple. Le bus d'adresse contient alors environ moitié moins de fils que la normale. Cette technique est appelée un '''bus d'adresse multiplexé'''.
Elle est surtout utilisée sur les mémoires de grande capacité, pour lesquelles les adresses sont très grandes. Songez qu'il faut 32 fils d'adresse pour une mémoire de 4 gibioctet, ce qui est déjà assez peu pour la mémoire principale d'un ordinateur personnel. Et câbler 32 fils est déjà un sacré défi en soi, là où 16 bits d'adresse est déjà largement plus supportable. Aussi, la mémoire RAM d'un ordinateur utilise systématiquement un envoi de l'adresse en deux fois. Les SRAM étant de petite capacité, elles n'utilisent que rarement un bus d'adresse multiplexé. Inversement, les DRAM utilisent souvent un bus d'adresse multiplexé du fait de leur grande capacité.
{|class="wikitable"
|+ Relation entre le type de mémoire et l'envoi des adresses en une ou deux fois
|-
! Type de la mémoire
! Bus d'adresse normal ou multiplexé
|-
! ROM/PROM/EPROM/EEPROM
| Bus d'adresse normal (envoi de l'adresse en une seule fois)
|-
! SRAM
| Bus d'adresse normal
|-
! DRAM
| Bus d'adresse multiplexé (envoi de l'adresse en deux fois)
|}
===Les bus multiplexés===
Une autre astuce est celle des '''bus multiplexés''', à ne pas confondre avec les bus précédents où seule l'adresse est multiplexée. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse. Ces bus rajoutent un bit sur le bus de commande, qui précise si le contenu du bus est une adresse ou une donnée. Ce bit ''Adresse Line Enable'', aussi appelé bit ALE, vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée (ou l'inverse !).
[[File:Bus multiplexé avec bit ALE.png|centre|vignette|upright=2|Bus multiplexé avec bit ALE.]]
Un bus multiplexé est plus lent pour les écritures : l'adresse et la donnée à écrire ne peuvent pas être envoyées en même temps. Par contre, les lectures ne posent pas de problèmes, vu que l'envoi de l'adresse et la lecture proprement dite ne sont pas simultanées. Heureusement, les lectures en mémoire sont bien plus courantes que les écritures, ce qui fait que la perte de performance due à l'utilisation d'un bus multiplexé est souvent supportable.
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 8 bits, mais aussi des données de 8 bits. Cela entraine un couplage entre la taille des données et la taille de la capacité de la mémoire. Cela peut être compensé avec un bus d'adresse multiplexé, les deux techniques pouvant être combinées sans problèmes. Dans ce cas, les transferts avec la mémoire se font en plusieurs fois : l'adresse est transmise en plusieurs fois, la donnée récupérée/écrite ensuite.
===Les bus à commutation de paquet===
Des mémoires DRAM assez rares ont exploré un bus mémoire particulier : avoir un bus peu large mais de haute fréquence, sur lequel on envoie les commandes/données en plusieurs fois. Elles sont regroupées sous le nom de '''mémoires à commutation par paquets'''. Elles utilisent des bus spéciaux, où les commandes/adresses/données sont transmises par paquets, par trames, en plusieurs fois. Le processeur envoie des paquets de commandes, les mémoires répondent avec des paquets de données ou des accusés de réception. Toutes les barrettes de mémoire doivent vérifier toutes les transmissions et déterminer si elles sont concernées en analysant l'adresse transmise dans la trame. En théorie, ce qu'on a dit sur le codage des trames dans le chapitre sur le bus devrait s'appliquer à de telles mémoires. En pratique, les protocoles de transmission sur le bus mémoire sont simplifiés, pour gérer le fonctionnement à haute fréquence.
Les mémoires à commutation par paquets sont peu nombreuses. Les plus connues sont les mémoires conçues par la société Rambus, à savoir la RDRAM (Rambus DRAM) et ses deux successeurs XDR RAM et XDR RAM 2. La Synchronous-link DRAM (SLDRAM) est un format concurrent conçu par un consortium de plusieurs concepteurs de mémoire.
Un premier exemple est celui des mémoires RDRAM, où le bus permettait de transmettre soit des commandes (adresse inclue), soit des données, avec un multiplexage total. Le processeur envoie un paquet contenant commandes et adresse à la mémoire, qui répond avec un paquet d'acquittement. Lors d'une lecture, le paquet d'acquittement contient la donnée lue. Lors d'une écriture, le paquet d'acquittement est réduit au strict minimum. Le bus de commandes est réduit au strict minimum, à savoir l'horloge et quelques bits absolument essentiels, les bits RW est transmis dans un paquet et n'ont pas de ligne dédiée, pareil pour le bit OE.
Pour donner un autre exemple, parlons rapidement des mémoires SLDRAM. Elles utilisaient un bus de commande de 11 bits, qui était utilisé pour transmettre des commandes de 40 bits, transmises en quatre cycles d'horloge consécutifs. Le bus de données était de 18 bits, mais les transferts de donnée se faisaient par paquets de 4 à 8 octets (32-65 bits). Pour résumer, données et commandes sont chacunes transmises en plusieurs cycles consécutifs, sur un bus de commande/données plus court que les données/commandes elle-mêmes.
==Le bus de données et les mémoires multiports==
Le bus de données transmet un nombre fixe de bits. Dans la plupart des cas, le bus de données peut transmettre plusieurs bits à chaque transmission (à chaque cycle d'horloge). Un bus qui permet cela est appelé un ''bus parallèle''. Les bus mémoire modernes sont assez larges : 16, 32 ou 64 bits, parfois plus ! Les PC modernes ont des bus mémoire de 128 ou 256 bits avec les technologies ''dual/quad channel'' !
Un bus mémoire parallèle transmet un '''mot mémoire''', à savoir un paquet de bits transmis en même temps sur le bus de données. Un mot mémoire est généralement composé de plusieurs octets consécutifs dans la RAM. Il correspond souvent à une case mémoire, mais ce n'est pas toujours le cas. Il arrive qu'une case mémoire soit transmise en plusieurs fois sur le bus mémoire. Par exemple, il est possible d'avoir un mot mémoire de 64 bits, pour une case mémoire de 128 bits : la case mémoire est envoyée en deux cycles d'horloge sur le bus mémoire. C'est ce principe qui est utilisé sur les mémoires DDR, qu'on abordera dans quelques chapitres. Pour le moment, nous allons considérer qu'un mot mémoire a la même taille qu'une case mémoire.
Quelques mémoires sont cependant connectées à un bus qui ne peut transmettre qu'un seul bit à la fois. Un tel bus est appelé un ''bus série''. Les mémoires avec un bus série ne sont pas forcément adressables bit par bit. Elles permettent de lire ou écrire par octets complets, mais ceux-ci sont transmis bits par bits sur le bus de données. La conversion entre octet et flux de bits sur le bus est réalisée par un simple registre à décalage. On pourrait croire que de telles mémoires séries sont rares, mais ce n'est pas le cas : les mémoires Flash, très utilisées dans les clés USB ou les disques durs SSD sont des mémoires séries.
[[File:Mémoire série et parallèle.png|centre|vignette|upright=2|Mémoire série et parallèle]]
===Le sens de transmission sur le bus===
Le bus de données est un bus bidirectionnel, sauf pour les ''mask ROM'' qui ne gèrent que la lecture, qui peuvent se contenter d'un bus unidirectionnel. Sur la plupart des mémoires, le bus de données est bidirectionnel et sert aussi bien pour les lectures que pour les écritures. Les mémoires de ce type sont appelées des '''mémoire simple port'''.
[[File:Mémoire simple-port.png|centre|vignette|upright=2|Mémoire simple-port]]
Sur la majorité des mémoires SRAM, on trouve deux bus de données : un dédié aux lectures et un autre pour les écritures. Le bus de commande est alors assez compliqué, dans le sens où il y a deux bus d'adresses : un qui commande l'entrée d'écriture et un pour la sortie de lecture. Le bus d'adresse est donc dupliqué et d'autres bits du bus de commande le sont aussi, mais les signaux d'horloge et le bit CS ne sont pas dupliqués. En théorie, il n'y a pas besoin de bit R/W, qui est remplacé par deux bits : un qui indique qu'on veut faire une écriture sur le bus dédié, un autre pour indiquer qu'on veut faire une lecture sur l'autre bus. Les mémoires de ce type sont appelées des '''mémoire double port lecture-écriture'''.
L’avantage d'utiliser un bus de lecture séparé du bus d'écriture est que cela permet d'effectuer une lecture en même temps qu'une écriture. Cependant, cet avantage signifie que la conception interne de la mémoire est naturellement plus compliquée. Par exemple, la mémoire doit gérer le cas où la donnée lue est identique à celle écrite en même temps. L'augmentation du nombre de broches est aussi un désavantage.
[[File:Mémoire double port (lecture et écriture séparées).png|centre|vignette|upright=2|Mémoire double port (lecture et écriture séparées)]]
En général, les mémoires ROM et DRAM sont de des mémoires simple port, alors que les mémoires SRAM sont double port, les exceptions étant rares. C'est assez intuitif pour les mémoires ROM : elles sont utilisées en lecture uniquement, avec des reprogrammations éventuelles assez rares. Pas besoin d'ajouter un second bus pour des reprogrammation rares, ce qui aurait un cout en termes de broches, de packaging, etc. Pour les DRAM, la raison est tout autre, et tient au fait que le plan mémoire des DRAM est naturellement siomple port, mais cela deviendra plus clair dans le chapitre sur les cellules mémoire. Les SRAM sont généralement double port pour les mêmes raisons : le plan mémoire est naturellement double port.
===Les mémoires multiport===
Le cas précédent, avec deux bus séparés, est un cas particulier de '''mémoire multiport'''. Celles-ci sont reliées non pas à un, mais à plusieurs bus de données. Évidemment, le bus de commande d'une telle mémoire est adapté à la présence de plusieurs bus de données. La plupart des bits du bus de commande sont dupliqués, avec un bit par bus de données. c'est le cas pour les bits R/W, les bits d'adresse, le bit OE, etc. Par contre, d'autres entrées du bus de commande ne sont pas dupliquées : c'est le cas du bit CS, de l'entrée d'horloge, etc. Les entrées de commandes associés à chaque bus de données, ainsi que les broches du bus de données, sont regroupées dans ce qu'on appelle un '''port'''.
[[File:Mémoire multiport.png|centre|vignette|upright=2|Mémoire multiport, où chaque port est bidirectionnel.]]
Dans l'exemple de la section précédente, on a un port pour les lectures et un autre pour les écritures. Chaque port est donc spécialisé soit dans les lectures, soit dans les écritures. D'autres mémoires suivent ce principe et ont deux/trois ports de lecture et un d'écriture, d'autres trois ports de lecture et deux d'écriture, bref : les combinaisons possibles sont légion. Mais d'autres mémoires ont des ports bidirectionnels, capables d'effectuer soit une lecture, soit une écriture. On peut imaginer une mémoire avec 5 ports, chacun faisant lecture et écriture.
Les mémoires multiport permettent de transférer plusieurs données à la fois, une par port. Le débit est sont donc supérieur à celui des mémoires mono-port. De plus, chaque port peut être relié à des composants différents, ce qui permet de partager une mémoire entre plusieurs composants.
==Les circuits de l'interface mémoire==
Une mémoire RAM a donc une interface assez précise, notamment pour le bus de commande. L'interface la plus simple regroupe un bus de données, un bus d'adresse, un bit R/W, deux bits ''Chip Select'' et ''Output Enable'', une entrée d'horloge. À l'intérieur de la mémoire, il existe un cœur qui contient les cases mémoire et un décodeur d'adresse connecté au bus d'adresse. Entre ce cœur et le bus mémoire, se trouve un '''circuit d’interfaçage mémoire''', qui est commandé par une logique de contrôle, elle-même commandée par les bits RW, ''Chip Select'' et ''Output Enable''. Et nous allons voir ce circuit d’interfaçage dans ce qui suit.
Il faut préciser que ce circuit d’interfaçage est situé dans la mémoire RAM/ROM, pas en-dehors. Il faut en effet distinguer l'interface mémoire, interne aux puces de RAM/ROM, de la ''glue logic'' qui connecte la mémoire RAM/ROM au bus mémoire proprement dit. La ''glue logic'' du bus mémoire est située en-dehors des puces de RAM/ROM, elle est composée de circuits distincts, qu'on verra au chapitre suivant, qui porte justement sur le bus mémoire. Ici, nous allons voir tout ce qui est relié directement aux broches de la puce mémoire, pas autre chose.
[[File:Td6bfig1.png|centre|vignette|upright=2|Organisation interne d'une mémoire adressable.]]
===L’interfaçage avec le bus de données : rappels===
[[File:Tampons3E.png|vignette|Tampons 3 états.]]
Expliquer Le circuit d’interfaçage n'est pas compliqué, si on se rappelle ce qu'on a vu dans le chapitre sur les bus électroniques. Pour interfacer un composant avec un bus, la solution la plus utilisée est d'utiliser des '''tampons trois états'''. Pour rappel, un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Suivant ce qui est mis sur l'entrée de commande, la sortie est soit déconnectée de l'entrée, 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 corrected.svg|centre|vignette|upright=1.5|Tampon trois-états.]]
L'intérêt d'utiliser des tampons trois-états est que cela permet de déconnecter la mémoire du bus mémoire si besoin, ce qui gère nativement le bit ''Output Enable''. Prenons par exemple une mémoire ''mask ROM'', pour laquelle seule la lecture est possible, pas l'écriture ni la re-programmation. Sur une ROM, le bus de commande se résume à une entrée d'horloge et le bit ''Output Enable'', il n'y a pas de bit R/W. Une mémoire ROM utilise donc un tampon trois-état par bit du bus de données , qui est commandé par le bit ''Output Enable''. Si ''Output Enable'' est à 0, le tampon trois-état est ouvert et la ROM est déconnectée du bus mémoire, sa sortie est désactivée. Si ''Output Enable'' est à 1, le tampon trois-état se ferme et la mémoire ROM est connectée sur le bus mémoire, sa sortie est activée.
[[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]]
L'interface d'une mémoire SRAM ou DRAM utilise quant à elle deux tampons trois-état : un pour les lectures, un autre pour les écritures. La raison est que les tampons-trois état sont des composants unidirectionnels. Ce ne sont pas des interrupteurs qu'on ouvre et ferme, qui laissent passer le courant dans les deux sens. Les tampons trois états sont commandés par le bit ''Output Enable'', 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. Je laisse la conception de ce petit circuit en exercice au lecteur, le circuti est vraiment très simple.
[[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]]
===Les interfaces ''Dual'' et ''Quad data rate''===
L'interface avec le bus mémoire est une source d'optimisations assez importantes. En effet, le bus mémoire doit idéalement être très rapide, ce qui demande de jouer sur deux caractéristiques : sa fréquence et sa largeur. Pour rappel, la performance d'un bus peut se résumer à son débit binaire, à savoir la quantité d'octets qu'il peut transmettre par seconde. Et celle-ci est le produit entre sa fréquence et la largeur du bus (le nombre de bits transmis en une fois sur le bus de données). Il se trouve que la largeur du bus est relativement contrainte, le nombre de fils qu'on peut câbler sur la carte mère est limité, de même que le nombre de broches sur les puces mémoire. À l'inverse, un bus mémoire peut fonctionner à faute fréquence sans trop de problèmes.
Par contre, les puces mémoires ont le compromis inverse. Les mémoires modernes ont un temps d'accès assez élevé, qui stagne depuis des décennies. En conséquence, la fréquence réelle des mémoires stagne relativement dans le temps, ou tout du moins elle augmente très lentement au cours des décennies. À l'opposé, Les mémoires modernes sont capables d'avoir un débit binaire important : il suffit d'utiliser des cases mémoire très larges, ce qui permet de lire/écrire plein d'octets à la fois. Rein de compliqué à implémenter.
Toujours est-il que le bus mémoire et les mémoires RAM ont des caractéristiques opposées en termes de performances. Pour profiter du débit binaire élevé des RAM, il faudrait augmenter la largeur du bus, mais cela a trop de désavantages : il faudrait câbler beaucoup trop de fils. De même, impossible d'augmenter la fréquence du bus, car la fréquence de la mémoire ne suivrait pas. Mais il existe une solution alternative, qui est une sorte de mélange des deux techniques. Cette technique s'appelle le préchargement, ''prefetching'' en anglais.
Les mémoires sans préchargement sont appelées des '''mémoires SDR (''Single Data Rate'')'''. Avec elles, le plan mémoire et le bus vont à la même fréquence et ils ont la même largeur (le nombre de bits transmit en une fois). Par exemple, si le bus mémoire a une largeur de 64 bits et une fréquence de 100 MHz, alors le plan mémoire fait de même. Toute augmentation de la fréquence et/ou de la largeur du bus se répercute sur la mémoire et réciproquement. La conséquence est que la fréquence et la largeur du bus mémoire sont réglés sur le plus petit dénominateur commun avec la RAM : fréquence basse pour rester compatible avec la RAM, largeur restreinte pour respecter les contraintes du bus.
[[File:Préchargement mémoire sur des DDR.png|centre|vignette|upright=2|Mémoire SDR.]]
Le préchargement augmente la largeur du plan mémoire sans en augmenter la fréquence, mais on fait l'inverse pour le bus. En faisant cela, le plan mémoire a une fréquence inférieure à celle du bus, mais a une largeur plus importante qui compense exactement la différence de fréquence. Si le plan mémoire a une largeur de N fois celle du bus, le bus a une fréquence N plus élevée pour compenser.
Sur les '''mémoires DDR (''Double Data Rate'')''', le plan mémoire est deux fois plus large que le bus, mais a une fréquence deux fois plus faible. Les données lues ou écrites dans le plan mémoire sont envoyées en deux fois sur le bus, ce qui est compensé par le fait qu'il soit deux fois plus rapide. Ceci dit, il faut découper un mot mémoire de 128 bits en deux blocs de 64, à envoyer sur le bus dans le bon ordre. Cela se fait dans l'interface avec le bus, grâce à un registre qui accumule les 128 bits lus ou à écrire, couplé à des multiplexeurs pour envoyer les 64 bits adéquats sur le bus.
[[File:Mémoire DDR.jpg|centre|vignette|upright=2|Mémoire DDR.]]
Il existe aussi des '''mémoires quad data rate''', pour lesquelles la fréquence du bus est quatre fois celle du plan mémoire. Évidemment, la mémoire peut alors lire ou écrire 4 fois plus de données par cycle que ce que le bus peut supporter.
[[File:Mémoire QDR.png|centre|vignette|upright=2|Mémoire QDR.]]
Le préchargement augmente donc le débit théorique maximal, en optimisant l'interface mémoire. L'interface mémoire fonctionne à double ou quadruple fréquence, voire plus, pour se synchroniser avec le bus mémoire. Elle doit interfacer un cœur mémoire très large avec un bus mémoire moins large, et doit aussi envoyer une donnée très large en plusieurs morceaux sur le bus. L'implémentation demande un registre et des multiplexeurs. A noter que sur les mémoires DDR dans les ordinateurs personnels, seul un signal d'horloge est utilisé, que ce soit pour le bus, le plan mémoire, ou le contrôleur. Seulement, le bus et les contrôleurs mémoire réagissent à la fois sur les fronts montants et sur les fronts descendants de l'horloge. Le plan mémoire, lui, ne réagit qu'aux fronts montants.
Sur les mémoires sans préchargement, le débit théorique maximal se calcule en multipliant la largeur du bus de données par sa fréquence. Par exemple, une mémoire SDRAM fonctionnant à 133 Mhz et qui utilise un bus de 8 octets, aura un débit de 8 * 133 * 1024 * 1024 octets par seconde, ce qui fait environ du 1 giga-octets par secondes. Pour les mémoires DDR, il faut multiplier la largeur du bus mémoire par la fréquence, et multiplier le tout par deux pour obtenir le débit maximal théorique. En reprenant notre exemple d'une mémoire DDR fonctionnant à 200 Mhz et utilisée en simple canal utilisera un bus de 8 octets, ce qui donnera un débit de 8 * 200 * 1024 * 1024 octets par seconde, ce qui fait environ du 2.1 gigaoctets par secondes.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les différents types de mémoires
| prevText=Les différents types de mémoires
| next=Le bus mémoire
| nextText=Le bus mémoire
}}
</noinclude>
5gtvoic3gmud8tvrk3i5tjxgccsw9xj
Fonctionnement d'un ordinateur/Version imprimable 2
0
78962
768679
765987
2026-06-25T20:41:28Z
Mewtow
31375
768679
wikitext
text/x-wiki
__NOTOC__
{{:Fonctionnement d'un ordinateur/Introduction}}
=Le codage des informations=
{{:Fonctionnement d'un ordinateur/L'encodage des données}}
{{:Fonctionnement d'un ordinateur/Le codage des nombres}}
{{:Fonctionnement d'un ordinateur/Les codes de détection/correction d'erreur}}
=Les circuits électroniques=
{{:Fonctionnement d'un ordinateur/Les portes logiques}}
==Les circuits combinatoires==
{{:Fonctionnement d'un ordinateur/Les circuits combinatoires}}
{{:Fonctionnement d'un ordinateur/Les circuits de masquage}}
{{:Fonctionnement d'un ordinateur/Les circuits de sélection}}
{{:Fonctionnement d'un ordinateur/Les circuits incrémenteurs/décrémenteurs}
==Les circuits séquentiels==
{{:Fonctionnement d'un ordinateur/Les bascules : des mémoires de 1 bit}}
{{:Fonctionnement d'un ordinateur/Les circuits synchrones et asynchrones}}
{{:Fonctionnement d'un ordinateur/Les registres et mémoires adressables}}
{{:Fonctionnement d'un ordinateur/Les compteurs et timers}}
{{:Fonctionnement d'un ordinateur/Les registres à décalage et les LSFR}}
==Les circuits de calcul et de comparaison==
{{:Fonctionnement d'un ordinateur/Les circuits de décalage et de rotation}}
{{:Fonctionnement d'un ordinateur/Les circuits pour l'addition et la soustraction}}
{{:Fonctionnement d'un ordinateur/Les unités arithmétiques et logiques entières (simples)}}
{{:Fonctionnement d'un ordinateur/Les circuits de calcul logique et bit à bit}}
{{:Fonctionnement d'un ordinateur/Les circuits de comparaison}}
{{:Fonctionnement d'un ordinateur/Les circuits pour l'addition multiopérande}}
{{:Fonctionnement d'un ordinateur/Les circuits pour la multiplication et la division}}
{{:Fonctionnement d'un ordinateur/Les circuits de calcul flottant}}
{{:Fonctionnement d'un ordinateur/Les circuits de calcul trigonométriques}}
==Les circuits intégrés==
{{:Fonctionnement d'un ordinateur/Les transistors et portes logiques}}
{{:Fonctionnement d'un ordinateur/Les circuits intégrés}}
{{:Fonctionnement d'un ordinateur/L'interface électrique entre circuits intégrés et bus}}
=L'architecture d'un ordinateur=
{{:Fonctionnement d'un ordinateur/L'architecture de base d'un ordinateur}}
{{:Fonctionnement d'un ordinateur/La hiérarchie mémoire}}
{{:Fonctionnement d'un ordinateur/La performance d'un ordinateur}}
{{:Fonctionnement d'un ordinateur/La loi de Moore et les tendances technologiques}}
{{:Fonctionnement d'un ordinateur/Les techniques de réduction de la consommation électrique d'un processeur}}
=Les bus et liaisons point à point=
{{:Fonctionnement d'un ordinateur/La carte mère, chipset et BIOS}}
{{:Fonctionnement d'un ordinateur/Les bus et liaisons point à point (généralités)}}
{{:Fonctionnement d'un ordinateur/Les encodages spécifiques aux bus}}
{{:Fonctionnement d'un ordinateur/Les liaisons point à point}}
{{:Fonctionnement d'un ordinateur/Les bus électroniques}}
{{:Fonctionnement d'un ordinateur/Quelques exemples de bus et de liaisons point à point}}
=Les mémoires=
{{:Fonctionnement d'un ordinateur/Les différents types de mémoires}}
{{:Fonctionnement d'un ordinateur/L'interface d'une mémoire électronique}}
{{:Fonctionnement d'un ordinateur/Le bus mémoire}}
==La micro-architecture d'une mémoire adressable==
{{:Fonctionnement d'un ordinateur/Les cellules mémoires}}
{{:Fonctionnement d'un ordinateur/Le plan mémoire}}
{{:Fonctionnement d'un ordinateur/Contrôleur mémoire interne}}
{{:Fonctionnement d'un ordinateur/Mémoires évoluées}}
==Les mémoires primaires==
{{:Fonctionnement d'un ordinateur/Les mémoires ROM}}
{{:Fonctionnement d'un ordinateur/Les mémoires SRAM synchrones}}
{{:Fonctionnement d'un ordinateur/Les mémoires RAM dynamiques (DRAM)}}
{{:Fonctionnement d'un ordinateur/Contrôleur mémoire externe}}
==Les mémoires exotiques==
{{:Fonctionnement d'un ordinateur/Les mémoires associatives}}
{{:Fonctionnement d'un ordinateur/Les mémoires FIFO et LIFO}}
=Le processeur=
==L'architecture externe==
{{:Fonctionnement d'un ordinateur/Langage machine et assembleur}}
{{:Fonctionnement d'un ordinateur/Les registres du processeur}}
{{:Fonctionnement d'un ordinateur/Les modes d'adressage}}
{{:Fonctionnement d'un ordinateur/L'encodage des instructions}}
{{:Fonctionnement d'un ordinateur/Les jeux d'instructions}}
{{:Fonctionnement d'un ordinateur/La pile d'appel et les fonctions}}
{{:Fonctionnement d'un ordinateur/Les interruptions et exceptions}}
{{:Fonctionnement d'un ordinateur/Le modèle mémoire : alignement et boutisme}}
==La micro-architecture==
{{:Fonctionnement d'un ordinateur/Les composants d'un processeur}}
{{:Fonctionnement d'un ordinateur/Le chemin de données}}
{{:Fonctionnement d'un ordinateur/L'unité de chargement et le program counter}}
{{:Fonctionnement d'un ordinateur/L'unité de contrôle}}
{{:Fonctionnement d'un ordinateur/L'implémentation matérielle des branchements}}
==Les jeux d’instructions spécialisés==
{{:Fonctionnement d'un ordinateur/Les architectures à accumulateur}}
{{:Fonctionnement d'un ordinateur/Les processeurs 8 bits et moins}}
{{:Fonctionnement d'un ordinateur/Les architectures à pile et mémoire-mémoire}}
==La mémoire virtuelle et la protection mémoire==
{{:Fonctionnement d'un ordinateur/L'espace d'adressage du processeur}}
{{:Fonctionnement d'un ordinateur/L'abstraction mémoire et la mémoire virtuelle}}
=Les entrées-sorties et périphériques=
{{:Fonctionnement d'un ordinateur/Les méthodes de synchronisation entre processeur et périphériques}}
{{:Fonctionnement d'un ordinateur/L'adressage des périphériques}}
{{:Fonctionnement d'un ordinateur/Les périphériques et les cartes d'extension}}
=Les mémoires de masse=
{{:Fonctionnement d'un ordinateur/Les mémoires de masse : généralités}}
{{:Fonctionnement d'un ordinateur/Les disques durs}}
{{:Fonctionnement d'un ordinateur/Les solid-state drives}}
{{:Fonctionnement d'un ordinateur/Les disques optiques}}
{{:Fonctionnement d'un ordinateur/Les technologies RAID}}
=La mémoire cache=
{{:Fonctionnement d'un ordinateur/Les mémoires cache}}
{{:Fonctionnement d'un ordinateur/Le préchargement}}
{{:Fonctionnement d'un ordinateur/Le Translation Lookaside Buffer}}
=Le parallélisme d’instructions=
{{:Fonctionnement d'un ordinateur/Le pipeline}}
==Les branchements et le front-end==
{{:Fonctionnement d'un ordinateur/La prédiction de branchement}}
{{:Fonctionnement d'un ordinateur/Les optimisations du chargement des instructions}}
==Les pipelines multicycles simples==
{{:Fonctionnement d'un ordinateur/Les pipelines multicycles}}
{{:Fonctionnement d'un ordinateur/L'émission dans l'ordre des instructions}}
{{:Fonctionnement d'un ordinateur/Le contournement (data forwarding)}}
{{:Fonctionnement d'un ordinateur/Les premiers processeurs Intel}}
==L’exécution dans le désordre==
{{:Fonctionnement d'un ordinateur/L'exécution dans le désordre}}
{{:Fonctionnement d'un ordinateur/Le renommage de registres}}
{{:Fonctionnement d'un ordinateur/Le scoreboarding et l'algorithme de Tomasulo}}
==Les optimisations des accès mémoire==
{{:Fonctionnement d'un ordinateur/La désambiguïsation mémoire}}
{{:Fonctionnement d'un ordinateur/Les optimisations des accès mémoire}}
==L'émission multiple==
{{:Fonctionnement d'un ordinateur/Les processeurs superscalaires}}
{{:Fonctionnement d'un ordinateur/Exemples de microarchitectures CPU : le cas du x86}}
{{:Fonctionnement d'un ordinateur/Les processeurs VLIW et EPIC}}
{{:Fonctionnement d'un ordinateur/Les architectures dataflow}}
=Les architectures parallèles=
{{:Fonctionnement d'un ordinateur/Les architectures parallèles}}
{{:Fonctionnement d'un ordinateur/Architectures multiprocesseurs et multicœurs}}
{{:Fonctionnement d'un ordinateur/Architectures multithreadées et Hyperthreading}}
{{:Fonctionnement d'un ordinateur/Les architectures à parallélisme de données}}
{{:Fonctionnement d'un ordinateur/La cohérence des caches}}
{{:Fonctionnement d'un ordinateur/Les sections critiques et le modèle mémoire}}
=Annexes=
==Les nombres flottants : FPUs et coprocesseurs==
{{:Fonctionnement d'un ordinateur/Un exemple de jeu d'instruction : l'extension x87}}
{{:Fonctionnement d'un ordinateur/Les coprocesseurs : FPU et IO}}
==Les jeux d’instruction spécialisés==
{{:Fonctionnement d'un ordinateur/L'accélération matérielle de la virtualisation}}
{{:Fonctionnement d'un ordinateur/Les ISA optimisés pour la compilation/interprétation}}
{{:Fonctionnement d'un ordinateur/Les processeurs de traitement du signal}}
{{:Fonctionnement d'un ordinateur/Les architectures actionnées par déplacement}}
{{:Fonctionnement d'un ordinateur/Les architectures systoliques}}
==Les autres annexes==
{{:Fonctionnement d'un ordinateur/Les réseaux de neurones matériels}}
{{:Fonctionnement d'un ordinateur/Le matériel réseau}}
{{:Fonctionnement d'un ordinateur/La tolérance aux pannes}}
{{:Fonctionnement d'un ordinateur/Les ordinateurs de première génération : tubes à vide et mémoires}}
{{:Fonctionnement d'un ordinateur/Les ordinateurs à encodages non-binaires}}
{{:Fonctionnement d'un ordinateur/Les circuits réversibles}}
{{:Fonctionnement d'un ordinateur/Les circuits de conversion analogique-numérique}}
{{autocat}}
0n3lbq2wek596muemw8pyvyka3mzdtc
Fonctionnement d'un ordinateur/Les registres à décalage et les LSFR
0
79341
768611
768176
2026-06-25T15:19:45Z
Mewtow
31375
768611
wikitext
text/x-wiki
Les '''registres à décalage''' sont des registres dont le contenu est décalé d'un cran vers la gauche ou la droite sur commande. Nous aurons à les réutiliser plus tard dans ce cours, notamment dans la section sur les circuits de génération de nombres aléatoires, ou dans certains circuits liés au cache. Les registres à décalage sont presque tous synchrones et ce chapitre ne parlera que ce ces derniers. L'animation suivante illustre le fonctionnement d'un registre à décalage qui décale son contenu d'un cran vers la droite à chaque cycle d'horloge.
[[File:4-Bit PISO Shift Register Seq.gif|centre|vignette|upright=2|Registre à décalage.]]
==Les registres à décalage : sous-types==
On peut classer les registres selon le caractère de l'entrée et de la sortie, qui peut être parallèle (entrée de plusieurs bits) ou série (entrée d'un seul bit).
* Sur les registres simples, les entrées et sorties pour les données sont toujours parallèles. Pour un registre de N bits, il y a une entrée d'écriture de N bits et une sortie de N bits. C'est la raison pour laquelle ils sont appelés des '''registres à entrées et sorties parallèles'''.
* Sur les registres à '''entrée et sortie série''', on peut mettre à jour un bit à la fois, de même qu'on ne peut en récupérer qu'un à la fois. Ces registres servent essentiellement à mettre en attente des bits tout en gardant leur ordre : un bit envoyé en entrée ressortira sur la sortie après plusieurs commandes de mise à jour sur l'entrée Enable.
* Les registres à décalage à '''entrée série et sortie parallèle''' sont similaires aux précédents : on peut ajouter un nouveau bit en commandant l'entrée Enable et les anciens bits sont alors décalés d'un cran. Par contre, on peut récupérer (lire) tous les bits en une seule fois. Ils permettent notamment de reconstituer un nombre qui est envoyé bit par bit sur un fil (un bus série).
* Enfin, il reste les registres à '''entrée parallèle et sortie série'''. Ces registres sont utiles quand on veut transmettre un nombre sur un fil : on peut ainsi envoyer les bits un par un.
[[File:Td5bfig1.png|centre|vignette|upright=2|Classification des registres à décalage.]]
Pour résumer, on distingue quatre types de registres (à décalage ou non), qui portent les noms de PIPO, PISO, SIPO et SISO. Les noms peuvent sembler barbares, mais il y a une logique derrière ces termes.La lettre P est pour parallèle, la lettre S est pour série. La lettre I signifie ''Input'', ce qui veut dire entrée en anglais, la lettre O est pour ''Output'', la sortie en anglais.
{|class="wikitable"
|+ Classification des registres
|-
!
! Entrée parallèle
! Entrée série
|-
! Sortie parallèle
| PIPO (registre simple)
| SIPO
|-
! Sortie série
| PISO
| SISO
|}
==L'intérieur d'un registre à décalage==
Tous les registres sont conçus en plaçant plusieurs bascules les unes à la suite des autres, que ce soit pour les registres simples ou les registres à décalage. La seule différence tient dans la manière dont les bascules sont reliées. Toutes les bascules sont reliées à l'entrée d'horloge, l'entrée Enable, l'entrée Reset, ou aux autres entrées de commandes. Mais c'est une autre paire de manche pour les entrées/sorties de données.
Dans un registre simple, les bascules sont indépendantes et ne sont pas reliées entre elles.
[[File:Register.svg|centre|vignette|upright=3|Registre simple.]]
À l'inverse, dans les registres à décalage, il existe des connexions entre bascules. Plus précisément, les bascules sont reliées les unes à la suite des autres, elles forment une chaîne de bascules reliées deux à deux. Et les connexions entre bascules sont les mêmes que l'on parle d'un registre à décalage de type SIPO, PISO ou SISO.
[[File:Shift-Register.svg|centre|vignette|upright=3|Exemple de registre à décalage]]
Outre le fait que les bascules sont reliées de la même manière, les autres connexions sont les mêmes dans tous les registres. L'entrée d'horloge (non-représentée dans les schémas qui vont suivre) est envoyée à toutes les bascules. Même chose pour l'entrée Enable, qui est reliée aux entrées E de toutes les bascules. La différence entre ces registres tient dans les endroits où se trouvent les entrées et les sorties du registre.
{|
|+ Implémentation des registres avec des bascules.
|-
|[[File:Registre à entrée parallèle et sortie série.png|vignette|upright=2|Registre à entrée et sortie série.]]
|[[File:Registre à lecture et écriture parallèle.png|vignette|upright=2|Registre à entrée et sortie parallèle.]]
|-
|[[File:Registre à entrée et sortie série.png|vignette|upright=2|Registre à entrée série et sortie parallèle.]]
|[[File:Registre à entrée série et sortie parallèle.png|vignette|upright=2|Registre à entrée parallèle et sortie série.]]
|}
==Les compteurs basés sur des registres à décalage==
Les registres à décalage peuvent être utilisés pour implémenter des compteurs. Les compteurs en question peuvent être des compteurs classiques, qui comptent en binaire. De tels compteurs sont appelés des compteurs asynchrones, bien que ce soit un abus de langage. Une autre utilisation est celle des compteurs ''one hot'', qui ne comptent pas en binaire normal. Voyons ces deux utilisations dans ce qui suit.
===Les mal-nommés ''compteurs asynchrones''===
Les '''compteurs asynchrones''' n'utilisent pas de circuit incrémenteur. A la place, ils sont fabriqués à partir d'un registre à décalage. Ils utilisent moins de portes logiques que les compteurs synchrones. Et ils ne sont pas forcément plus difficiles à comprendre. Précisons cependant qu'ils sont très mal nommés, à savoir que ce sont bien des circuits synchrones.
Pour comprendre comment fonctionne un compteur asynchrone, il faut regarder la séquence des premiers entiers :
* 000 ;
* 001 ;
* 010 ;
* 011 ;
* 100 ;
* 101 ;
* 110 ;
* 111.
Il faut remarquer que le bit de poids faible s'inverse à chaque cycle d'horloge. Pour les colonnes suivantes, le bit s'inverse quand le bit de la colonne précédente passe de 1 à 0, lors d'un front descendant sur la colonne précédente. Maintenant que l'on sait cela, on peut créer un compteur avec des bascules T (elles inversent leur contenu à chaque cycle d'horloge). La première colonne inverse son contenu à chaque cycle, elle correspond donc à une bascule T reliée directement à l'horloge. Les autres colonnes utilisent des bascules T activées sur front descendant.
: Attention, la bascule la plus à gauche stocke le bit de poids faible, pas celui de poids fort. Cela sera pareil dans tous les schémas qui suivront.
[[File:AsyncCounter Alternativ 2.svg|centre|vignette|upright=2|Compteur asynchrone de 4 bits.]]
Le compteur précédent ne peut pas être réinitialisé, ce qui pose des problèmes pour implémenter des compteurs modulo. Pour cela, il faut que les bascules du compteur aient une entrée de réinitialisation ''Reset'', qui les force à se remettre à zéro. Il suffit alors de connecter ensemble les entrées ''Reset'' des bascules à l'entrée ''Reset'' du compteur.
[[File:AsyncCounter mod 8 with RST .svg|centre|vignette|upright=2|Compteur réinitialisable.]]
Implémenter un compteur modulo demande d'ajouter un comparateur qui détecte quand la valeur maximale est atteinte, afin de commander l'entrée de réinitialisation. Un tel circuit est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Modulo-5-Zähler.svg|centre|vignette|upright=2|Compteur modulo 10.]]
Il est aussi possible d'utiliser des bascules D à la place des bascules T simplifiées. En effet, une bascule T simplifiée est identique à une bascule D dont on boucle la sortie /Q sur l'entrée de données. Cette implémentation permet d'ailleurs de réinitialiser le compteur à une valeur non-nulle. Pour cela, l'entrée de chaque bascule D est précédée d'un multiplexeur, qui choisit entre le bit calculé par le compteur et celui présenté sur l'entrée de ré-initialisation. Quand l'entrée ''Reset'' est activée, les multiplexeurs connectent les bascules aux bits sur l'entrée de ré-initialisation. Dans le cas contraire, le compteur fonctionne normalement, les multiplexeurs connectant l'entrée de chaque bascule à sa sortie.
[[File:Compteur asynchrone, avec initialisation.jpg|centre|vignette|upright=2|Compteur asynchrone, avec initialisation.]]
Pour finir, la sortie de débordement n'est autre que la sortie de la dernière bascule, celle qui contient le bit de poids fort.
==Les registres à décalage à rétroaction linéaire==
Les '''registres à décalage à rétroaction linéaire''' sont des registres à décalages un peu particuliers. Le terme anglais pour de tels registres est ''Linear Feedback Shift Register'', ce qui s’abrège en LFSR. Nous utiliserons cette abréviation dans ce qui suit pour simplifier grandement l'écriture.
Les LFSR sont appelés ainsi pour plusieurs raisons. Déjà, registre à décalage implique qu'ils sont fabriqués avec un registre à décalage. À rétroaction indique que l'on boucle la sortie sur l'entrée. Le terme combinaison linéaire demande quelques explications. L'idée est qu'entre la sortie et l'entrée, il y a un circuit combinatoire qui s'intercale entre la sortie et l'entrée, qui calcule ce qu'il faut mettre sur l'entrée, à partir du contenu du registre
Pour simplifier, cela veut dire qu'on multiplie chaque bit par 0 ou 1, avant d'additionner le tout. Dans ce calcul, on ne garde qu'un seul bit du résultat, vu que l'entrée du registre à décalage ne fait qu'un bit. Par simplicité, on ne garde que le bit de poids faible. Or, il s'avère que cela simplifie grandement les calculs, car cela permet de remplacer les additions par une simple opération XOR.
: <math>1 \times a_n \oplus ... + 1 \oplus a_3 + 1 \oplus a_1</math>
Le résultat est ce que l'on appelle un '''''LFSR de Fibonacci''''', ou encore un LFSR classique, qui celui qui colle le mieux avec la définition.
[[File:LFSR-F16.gif|centre|vignette|upright=2|Registre à décalage à rétroaction de Fibonnaci.]]
Les '''registres à décalages à rétroaction affine''' sont identique aux précédents à une différence près : le bit calculé est inversé avant d'être inséré dans le registre. Un tel circuit est donc composé de portes NXOR, comparé à son comparse linéaire, composé à partir de portes XOR. Petite remarque : si je prends un registre à rétroaction linéaire et un registre à rétroaction affine avec les mêmes coefficients sur les mêmes bits, le résultat du premier sera égal à l'inverse de l'autre.
Les '''LSFR de Gallois''' sont un peu l'inverse des LFSR vus juste avant. Au lieu d'utiliser un registre à décalage SIPO, ils utilisent un registre à décalage PISO. Pour faire la différence, nous appellerons ces derniers les LFSR PISO, et les premiers LFSR SIPO. Avec les LFSR PISO, on prend le bit sortant et on en déduit plusieurs bits à partir d'un circuit combinatoire, qui sont chacun insérés dans le registre à décalage à un endroit bien précis. Bien sûr, la fonction qui calcule des différents bits à partir du bit d'entrée conserve les mêmes propriétés que celle utilisée pour les LFSR : elle se calcule avec uniquement des portes XOR, ou NXOR pour leur variante affine.
Leur avantage est qu'ils sont plus rapides, car il n'y a qu'une seule porte logique entre la sortie et une entrée du registre à décalage, contre potentiellement plusieurs avec les LFSR SIPO. Notons que tout comme les LFSR qui ne peuvent pas mémoriser un 0, de tels registres à décalage à rétroaction ne peuvent pas avoir la valeur maximale stockable dans le registre. Cette valeur gèle le registre à cette valeur, dans le sens où le résultat au cycle suivant sera identique. Mais cela ne pose pas de problèmes pour l'initialisation du compteur.
[[File:LFSR-G16.svg|centre|vignette|upright=2|Registre à décalage à rétroaction de Galois.]]
==Les généralités sur les LSFRs ==
Quelques propriétés sont communes à tous les LSFRs. Un LSFR est déterministe : pour le même résultat en entrée, il donnera toujours le même résultat en sortie. De plus, il ne peut contenir qu'un nombre fini de valeurs. Et ces deux propriétés sont source de comportements qu'on va détailler ci-dessous.
===L'initialisation d'un LSFR===
Les LSFR ne peuvent pas être initialisés à une valeur arbitraire, en raison de la présence d'une '''valeur interdite'''. Concrètement, une fois que le décompte arrive sur une valeur interdite, le compteur reste bloqué sur cette valeur. La valeur suivante, calculée par le compteur, est cette valeur elle-même.
Le premier cas est celui où le compteur peut être initialisé avec zéro sans que cela ne pose problème. Sur de tels LSFRs, la réinitialisation se fait comme pour n'importe quel registre/compteur. A savoir que les entrées de reset des bascules sont toutes connectées ensemble, au même signal de reset.
[[File:Johnson Counter 4bit.svg|centre|vignette|upright=2|Compteur de Johnson de 4 bits]]
Le second cas est celui des LFSR non-affine. Lors de la réinitialisation, il faut que toutes les bascules soient réinitialisées à 0, sauf une qui est mise à 1. La bascule en question doit disposer d'une entrée S (''Set'') qui met la bascule à 1 quand elle est activée. Cela garantit que le registre est réinitialisé avec un zéro codé en ''one-hot''.
[[File:Overbeck Counter 4bit.svg|centre|vignette|upright=2|Compteur en anneau de 4 bits]]
Une autre solution est de mettre un multiplexeur juste avant l'entrée du registre à décalage. Cette solution marché bien dans le sens où elle permet d'initialiser le registre avec une valeur arbitraire, qui est insérée dans le registre en plusieurs cycles. Pour les LFSR, le multiplexeur est connecté soit au bit calculé par les portes XOR, soit par une entrée servant uniquement de l'initialisation.
[[File:Initialisation d'un LFSR.jpg|centre|vignette|upright=2|Initialisation d'un LFSR]]
===La période d'un LSFR===
Si le compteur est initialisé correctement, il passera d'une valeur à l'autre et finira par repasser par une valeur qu'il aura déjà parcourue et son fonctionnement se reproduira à l'identique comparé à son passage antérieur, il bouclera. Il parcourt un nombre N de valeurs à chaque cycle, ce nombre étant appelé la '''période du compteur'''.
La période d'un LSFR dépend fortement de la fonction utilisée pour calculer le bit de sortie. Dans le meilleur des cas, le LSFR passera par toutes les valeurs que le registre peut prendre, sauf une : suivant le registre, le zéro ou sa valeur maximale sont interdits. Si un LSFR passe par zéro, il y reste bloqué définitivement. La raison à cela est simple : un XOR sur des zéros donnera toujours 0. Le même raisonnement peut être tenu pour les registres à rétroaction affine, sauf que cette fois-ci, c'est la valeur maximale stockable dans le registre qui est fautive.
Tout le chalenge consiste donc à trouver quels sont les LSFR dont la période est maximale : ceux dont la période vaut <math>2^n - 1</math>. Qu'on se rassure, quelle que soit la longueur du registre, il en existe au moins un : cela se prouve mathématiquement.
<noinclude>[[File:LFSR-F4.GIF|centre|vignette|upright=2|Exemple avec un registre à rétroaction linéaire de 4 bits.]]</noinclude>
==La génération de nombres pseudo-aléatoires==
Les LSFRs peuvent aussi être utilisés pour générer des nombres "aléatoires". Je dis aléatoires entre guillemets, car ils ne sont pas vraiment aléatoires, mais s'en rapprochent suffisamment pour être considérés comme tels. Pour mettre en avant cela, on parle aussi de nombres "pseudo-aléatoires". De nombreuses situations demandent de générer des nombres pseudo-aléatoire de manière matérielle. Cela peut servir pour sélectionner une ligne de cache à remplacer lors d'un défaut de cache, pour implémenter des circuits cryptographiques, pour calculer la durée d'émission sur un bus Ethernet à la suite d'une collision, et j'en passe.
Les méthodes que nous allons voir produisent un nombre pseudo-aléatoire un bit à la fois, à quelques exceptions près. Les circuits que nous allons voir fournissent un bit sur leur sortie et ce bit varie de manière assez aléatoire. Les bits en sortie du circuit sont accumulés dans un registre à décalage normal, pour former un nombre aléatoire. Nous appellerons ce registre : l'accumulateur.
===L'usage de registres à décalage à rétroaction===
[[File:Nonlinear-combo-generator.png|thumb|Nonlinear-combo-generator]]
Un LSFR seul ne fournit pas un aléatoire digne de ce nom, mais il est possible de combiner plusieurs LSFR pour obtenir une meilleure approximation de l'aléatoire. Avec cette technique, plusieurs registres à décalages à rétroaction sont reliés à un circuit combinatoire non-linéaire. Ce circuit prendra en entrée un (ou plusieurs) bit de chaque registre à décalage à rétroaction, et combinera ces bits pour fournir un bit de sortie.
[[File:A5-1 GSM cipher.svg|centre|thumb|upright=2|Exemple avec trois LSFR différents, de tailles différentes : le bit envoyé à l'accumulateur est un XOR du bit sortant des trois LSFR.]]
Pour rendre le tout encore plus aléatoire, il est possible de cadencer les LSFR à des fréquences différentes. Cette technique est utilisée dans les générateurs ''stop-and-go'', ''alternative step'', et à ''shrinking''.
* Le générateur ''alternative step'' utilise trois LSFR. Le premier commande un multiplexeur qui choisit la sortie parmi les deux restants.
* Le générateur ''stop-and-go'' utilise deux LSFR. Le premier est relié à l'entrée d'horloge du second et le bit de sortie du second est utilisé comme résultat. Une technique similaire était utilisée dans les processeurs VIA C3, pour l'implémentation de leurs instructions cryptographiques.
* Le ''shrinking generator'' utilise deux LSFR cadencés à des vitesses différentes. Si le bit de sortie du premier vaut 1, alors le bit de sortie du second est utilisé comme résultat. Par contre, si le bit de sortie du premier vaut 0, aucun bit n'est fourni en sortie, le bit de sortie du second registre est oublié.
===L'aléatoire généré par des ''timers'' ou des compteurs d'horloge===
Au-delà des LSFR, il est possible d'utiliser des compteurs pour générer du pseudo-aléatoire. Par exemple, une technique très simple utilise un simple ''timer''. Si on a besoin d'un nombre pseudo-aléatoire, il suffit de lire le ''timer'' et d'utiliser le nombre lu comme nombre pseudo-aléatoire. Si le délai entre deux demandes est irrégulier, le résultat semblera aléatoire. Mais il s'agit là d'une technique assez peu fiable dans le monde réel et seules quelques applications bien spécifiques se satisfont de cette méthode.
Une solution un peu plus fiable utilise ce qu'on appelle la '''dérive de l'horloge'''. Il faut savoir qu'un signal d'horloge n'est jamais vraiment très précis. Une horloge censée tourner à 1 Ghz ne tournera pas en permanence à 1Ghz exactement, mais verra sa fréquence varier de quelques Hz ou Khz de manière irrégulière. Ces variations peuvent venir de variations aléatoires de température, des variations de tension, des perturbations électromagnétiques, ou à des phénomènes assez compliqués qui peuvent se produire dans tout circuit électrique (comme le ''shot noise'').
L'idée la plus simple utilise deux horloges : une horloge lente et une horloge rapide, dont la fréquence est un multiple de l'autre. Par exemple, on peut choisir une fréquence de 1 Mhz et une autre de 100 Hz : la fréquence la plus grande est égale à 10000 fois l'autre. La dérive d'horloge fera son œuvre, les deux horloges seront très légèrement désynchronisées en permanence, et cette désynchronisation peut être utilisée pour produire des nombres aléatoires. Par exemple, on peut compter le nombre de cycles d'horloge produit par l'horloge rapide durant une période de l'horloge lente. Si ce nombre est pair, on produit un bit aléatoire qui vaut 1 , il vaut 0 si ce nombre est pair. Pour information, c'est exactement cette technique qui était utilisée dans l'''Intel 82802 Firmware Hub''.
Il existe d'autres solutions matérielles qui utilisent le bruit thermique. Tous les circuits électroniques de l'univers sont soumis à de microscopiques variations de température, dues à l'agitation thermique des atomes. Plus la température est élevée, plus les atomes qui composent les fils métalliques des circuits s'agitent. Vu que les particules d'un métal contiennent des charges électriques, ces vibrations font naître des variations de tensions assez infimes. Il suffit d'amplifier ces variations pour obtenir un résultat capable de représenter un zéro ou un 1. Ce principe a été utilisé sur des anciens processeurs Intel qui géraient l'instruction RDRAND, une instruction qui produisait un nombre aléatoire.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les compteurs et timers
| prevText=Les compteurs et timers
| next=Les circuits de décalage et de rotation
| nextText=Les circuits de décalage et de rotation
}}
</noinclude>
fptsmjn48mjkf54a32l63x0ziyefztg
768616
768611
2026-06-25T15:20:32Z
Mewtow
31375
/* Les mal-nommés compteurs asynchrones */
768616
wikitext
text/x-wiki
Les '''registres à décalage''' sont des registres dont le contenu est décalé d'un cran vers la gauche ou la droite sur commande. Nous aurons à les réutiliser plus tard dans ce cours, notamment dans la section sur les circuits de génération de nombres aléatoires, ou dans certains circuits liés au cache. Les registres à décalage sont presque tous synchrones et ce chapitre ne parlera que ce ces derniers. L'animation suivante illustre le fonctionnement d'un registre à décalage qui décale son contenu d'un cran vers la droite à chaque cycle d'horloge.
[[File:4-Bit PISO Shift Register Seq.gif|centre|vignette|upright=2|Registre à décalage.]]
==Les registres à décalage : sous-types==
On peut classer les registres selon le caractère de l'entrée et de la sortie, qui peut être parallèle (entrée de plusieurs bits) ou série (entrée d'un seul bit).
* Sur les registres simples, les entrées et sorties pour les données sont toujours parallèles. Pour un registre de N bits, il y a une entrée d'écriture de N bits et une sortie de N bits. C'est la raison pour laquelle ils sont appelés des '''registres à entrées et sorties parallèles'''.
* Sur les registres à '''entrée et sortie série''', on peut mettre à jour un bit à la fois, de même qu'on ne peut en récupérer qu'un à la fois. Ces registres servent essentiellement à mettre en attente des bits tout en gardant leur ordre : un bit envoyé en entrée ressortira sur la sortie après plusieurs commandes de mise à jour sur l'entrée Enable.
* Les registres à décalage à '''entrée série et sortie parallèle''' sont similaires aux précédents : on peut ajouter un nouveau bit en commandant l'entrée Enable et les anciens bits sont alors décalés d'un cran. Par contre, on peut récupérer (lire) tous les bits en une seule fois. Ils permettent notamment de reconstituer un nombre qui est envoyé bit par bit sur un fil (un bus série).
* Enfin, il reste les registres à '''entrée parallèle et sortie série'''. Ces registres sont utiles quand on veut transmettre un nombre sur un fil : on peut ainsi envoyer les bits un par un.
[[File:Td5bfig1.png|centre|vignette|upright=2|Classification des registres à décalage.]]
Pour résumer, on distingue quatre types de registres (à décalage ou non), qui portent les noms de PIPO, PISO, SIPO et SISO. Les noms peuvent sembler barbares, mais il y a une logique derrière ces termes.La lettre P est pour parallèle, la lettre S est pour série. La lettre I signifie ''Input'', ce qui veut dire entrée en anglais, la lettre O est pour ''Output'', la sortie en anglais.
{|class="wikitable"
|+ Classification des registres
|-
!
! Entrée parallèle
! Entrée série
|-
! Sortie parallèle
| PIPO (registre simple)
| SIPO
|-
! Sortie série
| PISO
| SISO
|}
==L'intérieur d'un registre à décalage==
Tous les registres sont conçus en plaçant plusieurs bascules les unes à la suite des autres, que ce soit pour les registres simples ou les registres à décalage. La seule différence tient dans la manière dont les bascules sont reliées. Toutes les bascules sont reliées à l'entrée d'horloge, l'entrée Enable, l'entrée Reset, ou aux autres entrées de commandes. Mais c'est une autre paire de manche pour les entrées/sorties de données.
Dans un registre simple, les bascules sont indépendantes et ne sont pas reliées entre elles.
[[File:Register.svg|centre|vignette|upright=3|Registre simple.]]
À l'inverse, dans les registres à décalage, il existe des connexions entre bascules. Plus précisément, les bascules sont reliées les unes à la suite des autres, elles forment une chaîne de bascules reliées deux à deux. Et les connexions entre bascules sont les mêmes que l'on parle d'un registre à décalage de type SIPO, PISO ou SISO.
[[File:Shift-Register.svg|centre|vignette|upright=3|Exemple de registre à décalage]]
Outre le fait que les bascules sont reliées de la même manière, les autres connexions sont les mêmes dans tous les registres. L'entrée d'horloge (non-représentée dans les schémas qui vont suivre) est envoyée à toutes les bascules. Même chose pour l'entrée Enable, qui est reliée aux entrées E de toutes les bascules. La différence entre ces registres tient dans les endroits où se trouvent les entrées et les sorties du registre.
{|
|+ Implémentation des registres avec des bascules.
|-
|[[File:Registre à entrée parallèle et sortie série.png|vignette|upright=2|Registre à entrée et sortie série.]]
|[[File:Registre à lecture et écriture parallèle.png|vignette|upright=2|Registre à entrée et sortie parallèle.]]
|-
|[[File:Registre à entrée et sortie série.png|vignette|upright=2|Registre à entrée série et sortie parallèle.]]
|[[File:Registre à entrée série et sortie parallèle.png|vignette|upright=2|Registre à entrée parallèle et sortie série.]]
|}
==Les compteurs basés sur des registres à décalage==
Les registres à décalage peuvent être utilisés pour implémenter des compteurs. Les compteurs en question peuvent être des compteurs classiques, qui comptent en binaire. De tels compteurs sont appelés des compteurs asynchrones, bien que ce soit un abus de langage. Une autre utilisation est celle des compteurs ''one hot'', qui ne comptent pas en binaire normal. Voyons ces deux utilisations dans ce qui suit.
===Les mal-nommés ''compteurs asynchrones''===
Les '''compteurs asynchrones''' n'utilisent pas de circuit incrémenteur. A la place, ils sont fabriqués à partir d'un registre à décalage. Ils utilisent moins de portes logiques que les compteurs synchrones. Et ils ne sont pas forcément plus difficiles à comprendre. Précisons cependant qu'ils sont très mal nommés, à savoir que ce sont bien des circuits synchrones.
Pour comprendre comment fonctionne un compteur asynchrone, il faut regarder la séquence des premiers entiers :
* 000 ;
* 001 ;
* 010 ;
* 011 ;
* 100 ;
* 101 ;
* 110 ;
* 111.
Il faut remarquer que le bit de poids faible s'inverse à chaque cycle d'horloge. Pour les colonnes suivantes, le bit s'inverse quand le bit de la colonne précédente passe de 1 à 0, lors d'un front descendant sur la colonne précédente. Maintenant que l'on sait cela, on peut créer un compteur avec des bascules T (elles inversent leur contenu à chaque cycle d'horloge). La première colonne inverse son contenu à chaque cycle, elle correspond donc à une bascule T reliée directement à l'horloge. Les autres colonnes utilisent des bascules T activées sur front descendant.
: Attention, la bascule la plus à gauche stocke le bit de poids faible, pas celui de poids fort. Cela sera pareil dans tous les schémas qui suivront.
[[File:AsyncCounter Alternativ 2.svg|centre|vignette|upright=2|Compteur asynchrone de 4 bits.]]
Le compteur précédent ne peut pas être réinitialisé, ce qui pose des problèmes pour implémenter des compteurs modulo. Pour cela, il faut que les bascules du compteur aient une entrée de réinitialisation ''Reset'', qui les force à se remettre à zéro. Il suffit alors de connecter ensemble les entrées ''Reset'' des bascules à l'entrée ''Reset'' du compteur.
[[File:AsyncCounter mod 8 with RST .svg|centre|vignette|upright=2|Compteur réinitialisable.]]
Implémenter un compteur modulo demande d'ajouter un comparateur qui détecte quand la valeur maximale est atteinte, afin de commander l'entrée de réinitialisation. Un tel circuit est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Modulo-5-Zähler.svg|centre|vignette|upright=2|Compteur modulo 10.]]
Il est aussi possible d'utiliser des bascules D à la place des bascules T simplifiées. En effet, une bascule T simplifiée est identique à une bascule D dont on boucle la sortie /Q sur l'entrée de données. Cette implémentation permet d'ailleurs de réinitialiser le compteur à une valeur non-nulle. Pour cela, l'entrée de chaque bascule D est précédée d'un multiplexeur, qui choisit entre le bit calculé par le compteur et celui présenté sur l'entrée de ré-initialisation. Quand l'entrée ''Reset'' est activée, les multiplexeurs connectent les bascules aux bits sur l'entrée de ré-initialisation. Dans le cas contraire, le compteur fonctionne normalement, les multiplexeurs connectant l'entrée de chaque bascule à sa sortie.
[[File:Compteur asynchrone, avec initialisation.jpg|centre|vignette|upright=2|Compteur asynchrone, avec initialisation.]]
Pour finir, la sortie de débordement n'est autre que la sortie de la dernière bascule, celle qui contient le bit de poids fort.
===Les compteurs ''one-hot''===
Les '''compteurs ''one-hot''''' sont appelés ainsi, car ils permettent de compter dans une représentation des nombres appelée la représentation ''one-hot''. Pour rappel, dans une telle représentation, un seul bit est à 1 pendant que les autres sont à 0. Les entiers sont codés de la manière suivante : le nombre N est encodé en mettant le énième bit à 1, avec la condition que l'on commence à compteur à partir de zéro. Il est important de remarquer que dans cette représentation, le zéro est n'est PAS codé en mettant tous les bits à 0, la valeur 0000...0000 est une valeur interdite. À la place, le zéro est codé en mettant le bit de poids faible à 1. Pour N bits, on peut encoder seulement N valeurs, dont le zéro.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! ''One-hot''
|-
| 0 || 000 || 00000001
|-
| 1 || 001 || 00000010
|-
| 2 || 010 || 00000100
|-
| 3 || 011 || 00001000
|-
| 4 || 100 || 00010000
|-
| 5 || 101 || 00100000
|-
| 6 || 110 || 01000000
|-
| 7 || 111 || 10000000
|}
Un compteur en représentation ''one-hot'' contient un nombre codé de cette manière, qui est incrémenté ou décrémenté si besoin. Pour donner un exemple, la séquence d'un compteur en anneau de 4 bits est :
* 0001 (0) ;
* 0010 (1) ;
* 0100 (2) ;
* 1000 (3) .
Un compteur ''one-hot'' basique est composé d'un registre à décalage dont on boucle la sortie sur son entrée. En faisant cela, on garantit que le registre revient à zéro lors d'un débordement, zéro étant codé avec un 1 dans le bit de poids faible. Au passage, si vous ne mettez que des 0 dans un compteur en anneau, il restera bloqué pour toujours : décaler une suite de 0 donnera la même suite de 0. Initialiser un compteur ''one-hot'' demande donc quelques subtilités qu'on détaillera plus bas.
[[File:Compteur en anneau de 4 bits.jpg|centre|vignette|upright=2|Compteur en anneau de 4 bits]]
Un compteur ''one-hot'' de N bits peut prendre N valeurs différentes, qui ont toutes un seul bit à 1. Pour 5 bits, la séquence est illustrée ci-dessous.
[[File:Lauflicht 5 LED.gif|centre|vignette|Compteur en anneau de 5 bits.]]
Faire des comparaisons avec ce type de compteur est très simple : le compteur contient la valeur N si le énième bit est à 1. Pas besoin d'utiliser de circuit comparateur, juste de lire un bit. Par contre, ce compteur n'est pas très économe en bascules. Imaginons que l'on veut un compteur qui compte jusqu'à une valeur N arbitraire : un compteur binaire normal utilisera environ <math>\log_2{(N)}</math> bascules, alors qu'un compteur ''one-hot'' demande N bascules. Mais si N est assez petit, l'économie de bascules est assez faible, alors que l'économie de circuits comparateurs/incrémenteurs l'est beaucoup plus.
Il y a peu d'applications qui utilisent des compteurs en anneau. Ils étaient autrefois utilisés dans les tous premiers ordinateurs, notamment ceux qui géraient une représentation des nombres spécifique appelée la '''''Bi-quinary coded decimal'''''. De nos jours, de tels compteurs sont utilisés dans les séquenceurs de processeurs, mais aussi dans les séquenceurs de certains périphériques, ou dans les circuits séquentiels simples qui se résument à des machines à états. Ils sont alors utilisés car très rapides, parfaitement adaptés au stockage de petites valeur, et surtout : ils n'ont pas besoin de circuit comparateur pour connaitre la valeur stockée dedans. Nous n'allons pas rentrer dans le détail de leurs utilisations, car nous en reparlerons dans la suite du cours.
==Les registres à décalage à rétroaction linéaire==
Les '''registres à décalage à rétroaction linéaire''' sont des registres à décalages un peu particuliers. Le terme anglais pour de tels registres est ''Linear Feedback Shift Register'', ce qui s’abrège en LFSR. Nous utiliserons cette abréviation dans ce qui suit pour simplifier grandement l'écriture.
Les LFSR sont appelés ainsi pour plusieurs raisons. Déjà, registre à décalage implique qu'ils sont fabriqués avec un registre à décalage. À rétroaction indique que l'on boucle la sortie sur l'entrée. Le terme combinaison linéaire demande quelques explications. L'idée est qu'entre la sortie et l'entrée, il y a un circuit combinatoire qui s'intercale entre la sortie et l'entrée, qui calcule ce qu'il faut mettre sur l'entrée, à partir du contenu du registre
Pour simplifier, cela veut dire qu'on multiplie chaque bit par 0 ou 1, avant d'additionner le tout. Dans ce calcul, on ne garde qu'un seul bit du résultat, vu que l'entrée du registre à décalage ne fait qu'un bit. Par simplicité, on ne garde que le bit de poids faible. Or, il s'avère que cela simplifie grandement les calculs, car cela permet de remplacer les additions par une simple opération XOR.
: <math>1 \times a_n \oplus ... + 1 \oplus a_3 + 1 \oplus a_1</math>
Le résultat est ce que l'on appelle un '''''LFSR de Fibonacci''''', ou encore un LFSR classique, qui celui qui colle le mieux avec la définition.
[[File:LFSR-F16.gif|centre|vignette|upright=2|Registre à décalage à rétroaction de Fibonnaci.]]
Les '''registres à décalages à rétroaction affine''' sont identique aux précédents à une différence près : le bit calculé est inversé avant d'être inséré dans le registre. Un tel circuit est donc composé de portes NXOR, comparé à son comparse linéaire, composé à partir de portes XOR. Petite remarque : si je prends un registre à rétroaction linéaire et un registre à rétroaction affine avec les mêmes coefficients sur les mêmes bits, le résultat du premier sera égal à l'inverse de l'autre.
Les '''LSFR de Gallois''' sont un peu l'inverse des LFSR vus juste avant. Au lieu d'utiliser un registre à décalage SIPO, ils utilisent un registre à décalage PISO. Pour faire la différence, nous appellerons ces derniers les LFSR PISO, et les premiers LFSR SIPO. Avec les LFSR PISO, on prend le bit sortant et on en déduit plusieurs bits à partir d'un circuit combinatoire, qui sont chacun insérés dans le registre à décalage à un endroit bien précis. Bien sûr, la fonction qui calcule des différents bits à partir du bit d'entrée conserve les mêmes propriétés que celle utilisée pour les LFSR : elle se calcule avec uniquement des portes XOR, ou NXOR pour leur variante affine.
Leur avantage est qu'ils sont plus rapides, car il n'y a qu'une seule porte logique entre la sortie et une entrée du registre à décalage, contre potentiellement plusieurs avec les LFSR SIPO. Notons que tout comme les LFSR qui ne peuvent pas mémoriser un 0, de tels registres à décalage à rétroaction ne peuvent pas avoir la valeur maximale stockable dans le registre. Cette valeur gèle le registre à cette valeur, dans le sens où le résultat au cycle suivant sera identique. Mais cela ne pose pas de problèmes pour l'initialisation du compteur.
[[File:LFSR-G16.svg|centre|vignette|upright=2|Registre à décalage à rétroaction de Galois.]]
==Les généralités sur les LSFRs ==
Quelques propriétés sont communes à tous les LSFRs. Un LSFR est déterministe : pour le même résultat en entrée, il donnera toujours le même résultat en sortie. De plus, il ne peut contenir qu'un nombre fini de valeurs. Et ces deux propriétés sont source de comportements qu'on va détailler ci-dessous.
===L'initialisation d'un LSFR===
Les LSFR ne peuvent pas être initialisés à une valeur arbitraire, en raison de la présence d'une '''valeur interdite'''. Concrètement, une fois que le décompte arrive sur une valeur interdite, le compteur reste bloqué sur cette valeur. La valeur suivante, calculée par le compteur, est cette valeur elle-même.
Le premier cas est celui où le compteur peut être initialisé avec zéro sans que cela ne pose problème. Sur de tels LSFRs, la réinitialisation se fait comme pour n'importe quel registre/compteur. A savoir que les entrées de reset des bascules sont toutes connectées ensemble, au même signal de reset.
[[File:Johnson Counter 4bit.svg|centre|vignette|upright=2|Compteur de Johnson de 4 bits]]
Le second cas est celui des LFSR non-affine. Lors de la réinitialisation, il faut que toutes les bascules soient réinitialisées à 0, sauf une qui est mise à 1. La bascule en question doit disposer d'une entrée S (''Set'') qui met la bascule à 1 quand elle est activée. Cela garantit que le registre est réinitialisé avec un zéro codé en ''one-hot''.
[[File:Overbeck Counter 4bit.svg|centre|vignette|upright=2|Compteur en anneau de 4 bits]]
Une autre solution est de mettre un multiplexeur juste avant l'entrée du registre à décalage. Cette solution marché bien dans le sens où elle permet d'initialiser le registre avec une valeur arbitraire, qui est insérée dans le registre en plusieurs cycles. Pour les LFSR, le multiplexeur est connecté soit au bit calculé par les portes XOR, soit par une entrée servant uniquement de l'initialisation.
[[File:Initialisation d'un LFSR.jpg|centre|vignette|upright=2|Initialisation d'un LFSR]]
===La période d'un LSFR===
Si le compteur est initialisé correctement, il passera d'une valeur à l'autre et finira par repasser par une valeur qu'il aura déjà parcourue et son fonctionnement se reproduira à l'identique comparé à son passage antérieur, il bouclera. Il parcourt un nombre N de valeurs à chaque cycle, ce nombre étant appelé la '''période du compteur'''.
La période d'un LSFR dépend fortement de la fonction utilisée pour calculer le bit de sortie. Dans le meilleur des cas, le LSFR passera par toutes les valeurs que le registre peut prendre, sauf une : suivant le registre, le zéro ou sa valeur maximale sont interdits. Si un LSFR passe par zéro, il y reste bloqué définitivement. La raison à cela est simple : un XOR sur des zéros donnera toujours 0. Le même raisonnement peut être tenu pour les registres à rétroaction affine, sauf que cette fois-ci, c'est la valeur maximale stockable dans le registre qui est fautive.
Tout le chalenge consiste donc à trouver quels sont les LSFR dont la période est maximale : ceux dont la période vaut <math>2^n - 1</math>. Qu'on se rassure, quelle que soit la longueur du registre, il en existe au moins un : cela se prouve mathématiquement.
<noinclude>[[File:LFSR-F4.GIF|centre|vignette|upright=2|Exemple avec un registre à rétroaction linéaire de 4 bits.]]</noinclude>
==La génération de nombres pseudo-aléatoires==
Les LSFRs peuvent aussi être utilisés pour générer des nombres "aléatoires". Je dis aléatoires entre guillemets, car ils ne sont pas vraiment aléatoires, mais s'en rapprochent suffisamment pour être considérés comme tels. Pour mettre en avant cela, on parle aussi de nombres "pseudo-aléatoires". De nombreuses situations demandent de générer des nombres pseudo-aléatoire de manière matérielle. Cela peut servir pour sélectionner une ligne de cache à remplacer lors d'un défaut de cache, pour implémenter des circuits cryptographiques, pour calculer la durée d'émission sur un bus Ethernet à la suite d'une collision, et j'en passe.
Les méthodes que nous allons voir produisent un nombre pseudo-aléatoire un bit à la fois, à quelques exceptions près. Les circuits que nous allons voir fournissent un bit sur leur sortie et ce bit varie de manière assez aléatoire. Les bits en sortie du circuit sont accumulés dans un registre à décalage normal, pour former un nombre aléatoire. Nous appellerons ce registre : l'accumulateur.
===L'usage de registres à décalage à rétroaction===
[[File:Nonlinear-combo-generator.png|thumb|Nonlinear-combo-generator]]
Un LSFR seul ne fournit pas un aléatoire digne de ce nom, mais il est possible de combiner plusieurs LSFR pour obtenir une meilleure approximation de l'aléatoire. Avec cette technique, plusieurs registres à décalages à rétroaction sont reliés à un circuit combinatoire non-linéaire. Ce circuit prendra en entrée un (ou plusieurs) bit de chaque registre à décalage à rétroaction, et combinera ces bits pour fournir un bit de sortie.
[[File:A5-1 GSM cipher.svg|centre|thumb|upright=2|Exemple avec trois LSFR différents, de tailles différentes : le bit envoyé à l'accumulateur est un XOR du bit sortant des trois LSFR.]]
Pour rendre le tout encore plus aléatoire, il est possible de cadencer les LSFR à des fréquences différentes. Cette technique est utilisée dans les générateurs ''stop-and-go'', ''alternative step'', et à ''shrinking''.
* Le générateur ''alternative step'' utilise trois LSFR. Le premier commande un multiplexeur qui choisit la sortie parmi les deux restants.
* Le générateur ''stop-and-go'' utilise deux LSFR. Le premier est relié à l'entrée d'horloge du second et le bit de sortie du second est utilisé comme résultat. Une technique similaire était utilisée dans les processeurs VIA C3, pour l'implémentation de leurs instructions cryptographiques.
* Le ''shrinking generator'' utilise deux LSFR cadencés à des vitesses différentes. Si le bit de sortie du premier vaut 1, alors le bit de sortie du second est utilisé comme résultat. Par contre, si le bit de sortie du premier vaut 0, aucun bit n'est fourni en sortie, le bit de sortie du second registre est oublié.
===L'aléatoire généré par des ''timers'' ou des compteurs d'horloge===
Au-delà des LSFR, il est possible d'utiliser des compteurs pour générer du pseudo-aléatoire. Par exemple, une technique très simple utilise un simple ''timer''. Si on a besoin d'un nombre pseudo-aléatoire, il suffit de lire le ''timer'' et d'utiliser le nombre lu comme nombre pseudo-aléatoire. Si le délai entre deux demandes est irrégulier, le résultat semblera aléatoire. Mais il s'agit là d'une technique assez peu fiable dans le monde réel et seules quelques applications bien spécifiques se satisfont de cette méthode.
Une solution un peu plus fiable utilise ce qu'on appelle la '''dérive de l'horloge'''. Il faut savoir qu'un signal d'horloge n'est jamais vraiment très précis. Une horloge censée tourner à 1 Ghz ne tournera pas en permanence à 1Ghz exactement, mais verra sa fréquence varier de quelques Hz ou Khz de manière irrégulière. Ces variations peuvent venir de variations aléatoires de température, des variations de tension, des perturbations électromagnétiques, ou à des phénomènes assez compliqués qui peuvent se produire dans tout circuit électrique (comme le ''shot noise'').
L'idée la plus simple utilise deux horloges : une horloge lente et une horloge rapide, dont la fréquence est un multiple de l'autre. Par exemple, on peut choisir une fréquence de 1 Mhz et une autre de 100 Hz : la fréquence la plus grande est égale à 10000 fois l'autre. La dérive d'horloge fera son œuvre, les deux horloges seront très légèrement désynchronisées en permanence, et cette désynchronisation peut être utilisée pour produire des nombres aléatoires. Par exemple, on peut compter le nombre de cycles d'horloge produit par l'horloge rapide durant une période de l'horloge lente. Si ce nombre est pair, on produit un bit aléatoire qui vaut 1 , il vaut 0 si ce nombre est pair. Pour information, c'est exactement cette technique qui était utilisée dans l'''Intel 82802 Firmware Hub''.
Il existe d'autres solutions matérielles qui utilisent le bruit thermique. Tous les circuits électroniques de l'univers sont soumis à de microscopiques variations de température, dues à l'agitation thermique des atomes. Plus la température est élevée, plus les atomes qui composent les fils métalliques des circuits s'agitent. Vu que les particules d'un métal contiennent des charges électriques, ces vibrations font naître des variations de tensions assez infimes. Il suffit d'amplifier ces variations pour obtenir un résultat capable de représenter un zéro ou un 1. Ce principe a été utilisé sur des anciens processeurs Intel qui géraient l'instruction RDRAND, une instruction qui produisait un nombre aléatoire.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les compteurs et timers
| prevText=Les compteurs et timers
| next=Les circuits de décalage et de rotation
| nextText=Les circuits de décalage et de rotation
}}
</noinclude>
ovurnlb9wpvzcndlryc3ky9xwk64qej
768617
768616
2026-06-25T15:20:46Z
Mewtow
31375
/* Les compteurs basés sur des registres à décalage */
768617
wikitext
text/x-wiki
Les '''registres à décalage''' sont des registres dont le contenu est décalé d'un cran vers la gauche ou la droite sur commande. Nous aurons à les réutiliser plus tard dans ce cours, notamment dans la section sur les circuits de génération de nombres aléatoires, ou dans certains circuits liés au cache. Les registres à décalage sont presque tous synchrones et ce chapitre ne parlera que ce ces derniers. L'animation suivante illustre le fonctionnement d'un registre à décalage qui décale son contenu d'un cran vers la droite à chaque cycle d'horloge.
[[File:4-Bit PISO Shift Register Seq.gif|centre|vignette|upright=2|Registre à décalage.]]
==Les registres à décalage : sous-types==
On peut classer les registres selon le caractère de l'entrée et de la sortie, qui peut être parallèle (entrée de plusieurs bits) ou série (entrée d'un seul bit).
* Sur les registres simples, les entrées et sorties pour les données sont toujours parallèles. Pour un registre de N bits, il y a une entrée d'écriture de N bits et une sortie de N bits. C'est la raison pour laquelle ils sont appelés des '''registres à entrées et sorties parallèles'''.
* Sur les registres à '''entrée et sortie série''', on peut mettre à jour un bit à la fois, de même qu'on ne peut en récupérer qu'un à la fois. Ces registres servent essentiellement à mettre en attente des bits tout en gardant leur ordre : un bit envoyé en entrée ressortira sur la sortie après plusieurs commandes de mise à jour sur l'entrée Enable.
* Les registres à décalage à '''entrée série et sortie parallèle''' sont similaires aux précédents : on peut ajouter un nouveau bit en commandant l'entrée Enable et les anciens bits sont alors décalés d'un cran. Par contre, on peut récupérer (lire) tous les bits en une seule fois. Ils permettent notamment de reconstituer un nombre qui est envoyé bit par bit sur un fil (un bus série).
* Enfin, il reste les registres à '''entrée parallèle et sortie série'''. Ces registres sont utiles quand on veut transmettre un nombre sur un fil : on peut ainsi envoyer les bits un par un.
[[File:Td5bfig1.png|centre|vignette|upright=2|Classification des registres à décalage.]]
Pour résumer, on distingue quatre types de registres (à décalage ou non), qui portent les noms de PIPO, PISO, SIPO et SISO. Les noms peuvent sembler barbares, mais il y a une logique derrière ces termes.La lettre P est pour parallèle, la lettre S est pour série. La lettre I signifie ''Input'', ce qui veut dire entrée en anglais, la lettre O est pour ''Output'', la sortie en anglais.
{|class="wikitable"
|+ Classification des registres
|-
!
! Entrée parallèle
! Entrée série
|-
! Sortie parallèle
| PIPO (registre simple)
| SIPO
|-
! Sortie série
| PISO
| SISO
|}
==L'intérieur d'un registre à décalage==
Tous les registres sont conçus en plaçant plusieurs bascules les unes à la suite des autres, que ce soit pour les registres simples ou les registres à décalage. La seule différence tient dans la manière dont les bascules sont reliées. Toutes les bascules sont reliées à l'entrée d'horloge, l'entrée Enable, l'entrée Reset, ou aux autres entrées de commandes. Mais c'est une autre paire de manche pour les entrées/sorties de données.
Dans un registre simple, les bascules sont indépendantes et ne sont pas reliées entre elles.
[[File:Register.svg|centre|vignette|upright=3|Registre simple.]]
À l'inverse, dans les registres à décalage, il existe des connexions entre bascules. Plus précisément, les bascules sont reliées les unes à la suite des autres, elles forment une chaîne de bascules reliées deux à deux. Et les connexions entre bascules sont les mêmes que l'on parle d'un registre à décalage de type SIPO, PISO ou SISO.
[[File:Shift-Register.svg|centre|vignette|upright=3|Exemple de registre à décalage]]
Outre le fait que les bascules sont reliées de la même manière, les autres connexions sont les mêmes dans tous les registres. L'entrée d'horloge (non-représentée dans les schémas qui vont suivre) est envoyée à toutes les bascules. Même chose pour l'entrée Enable, qui est reliée aux entrées E de toutes les bascules. La différence entre ces registres tient dans les endroits où se trouvent les entrées et les sorties du registre.
{|
|+ Implémentation des registres avec des bascules.
|-
|[[File:Registre à entrée parallèle et sortie série.png|vignette|upright=2|Registre à entrée et sortie série.]]
|[[File:Registre à lecture et écriture parallèle.png|vignette|upright=2|Registre à entrée et sortie parallèle.]]
|-
|[[File:Registre à entrée et sortie série.png|vignette|upright=2|Registre à entrée série et sortie parallèle.]]
|[[File:Registre à entrée série et sortie parallèle.png|vignette|upright=2|Registre à entrée parallèle et sortie série.]]
|}
==Les compteurs basés sur des registres à décalage==
Les registres à décalage peuvent être utilisés pour implémenter des compteurs. Les compteurs en question peuvent être des compteurs classiques, qui comptent en binaire. De tels compteurs sont appelés des compteurs asynchrones, bien que ce soit un abus de langage. Une autre utilisation est celle des compteurs ''one hot'', qui ne comptent pas en binaire normal. Voyons ces deux utilisations dans ce qui suit.
===Les mal-nommés ''compteurs asynchrones''===
Les '''compteurs asynchrones''' n'utilisent pas de circuit incrémenteur. A la place, ils sont fabriqués à partir d'un registre à décalage. Ils utilisent moins de portes logiques que les compteurs synchrones. Et ils ne sont pas forcément plus difficiles à comprendre. Précisons cependant qu'ils sont très mal nommés, à savoir que ce sont bien des circuits synchrones.
Pour comprendre comment fonctionne un compteur asynchrone, il faut regarder la séquence des premiers entiers :
* 000 ;
* 001 ;
* 010 ;
* 011 ;
* 100 ;
* 101 ;
* 110 ;
* 111.
Il faut remarquer que le bit de poids faible s'inverse à chaque cycle d'horloge. Pour les colonnes suivantes, le bit s'inverse quand le bit de la colonne précédente passe de 1 à 0, lors d'un front descendant sur la colonne précédente. Maintenant que l'on sait cela, on peut créer un compteur avec des bascules T (elles inversent leur contenu à chaque cycle d'horloge). La première colonne inverse son contenu à chaque cycle, elle correspond donc à une bascule T reliée directement à l'horloge. Les autres colonnes utilisent des bascules T activées sur front descendant.
: Attention, la bascule la plus à gauche stocke le bit de poids faible, pas celui de poids fort. Cela sera pareil dans tous les schémas qui suivront.
[[File:AsyncCounter Alternativ 2.svg|centre|vignette|upright=2|Compteur asynchrone de 4 bits.]]
Le compteur précédent ne peut pas être réinitialisé, ce qui pose des problèmes pour implémenter des compteurs modulo. Pour cela, il faut que les bascules du compteur aient une entrée de réinitialisation ''Reset'', qui les force à se remettre à zéro. Il suffit alors de connecter ensemble les entrées ''Reset'' des bascules à l'entrée ''Reset'' du compteur.
[[File:AsyncCounter mod 8 with RST .svg|centre|vignette|upright=2|Compteur réinitialisable.]]
Implémenter un compteur modulo demande d'ajouter un comparateur qui détecte quand la valeur maximale est atteinte, afin de commander l'entrée de réinitialisation. Un tel circuit est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Modulo-5-Zähler.svg|centre|vignette|upright=2|Compteur modulo 10.]]
Il est aussi possible d'utiliser des bascules D à la place des bascules T simplifiées. En effet, une bascule T simplifiée est identique à une bascule D dont on boucle la sortie /Q sur l'entrée de données. Cette implémentation permet d'ailleurs de réinitialiser le compteur à une valeur non-nulle. Pour cela, l'entrée de chaque bascule D est précédée d'un multiplexeur, qui choisit entre le bit calculé par le compteur et celui présenté sur l'entrée de ré-initialisation. Quand l'entrée ''Reset'' est activée, les multiplexeurs connectent les bascules aux bits sur l'entrée de ré-initialisation. Dans le cas contraire, le compteur fonctionne normalement, les multiplexeurs connectant l'entrée de chaque bascule à sa sortie.
[[File:Compteur asynchrone, avec initialisation.jpg|centre|vignette|upright=2|Compteur asynchrone, avec initialisation.]]
Pour finir, la sortie de débordement n'est autre que la sortie de la dernière bascule, celle qui contient le bit de poids fort.
===Les compteurs ''one-hot''===
Les '''compteurs ''one-hot''''' sont appelés ainsi, car ils permettent de compter dans une représentation des nombres appelée la représentation ''one-hot''. Pour rappel, dans une telle représentation, un seul bit est à 1 pendant que les autres sont à 0. Les entiers sont codés de la manière suivante : le nombre N est encodé en mettant le énième bit à 1, avec la condition que l'on commence à compteur à partir de zéro. Il est important de remarquer que dans cette représentation, le zéro est n'est PAS codé en mettant tous les bits à 0, la valeur 0000...0000 est une valeur interdite. À la place, le zéro est codé en mettant le bit de poids faible à 1. Pour N bits, on peut encoder seulement N valeurs, dont le zéro.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! ''One-hot''
|-
| 0 || 000 || 00000001
|-
| 1 || 001 || 00000010
|-
| 2 || 010 || 00000100
|-
| 3 || 011 || 00001000
|-
| 4 || 100 || 00010000
|-
| 5 || 101 || 00100000
|-
| 6 || 110 || 01000000
|-
| 7 || 111 || 10000000
|}
Un compteur en représentation ''one-hot'' contient un nombre codé de cette manière, qui est incrémenté ou décrémenté si besoin. Pour donner un exemple, la séquence d'un compteur en anneau de 4 bits est :
* 0001 (0) ;
* 0010 (1) ;
* 0100 (2) ;
* 1000 (3) .
Un compteur ''one-hot'' basique est composé d'un registre à décalage dont on boucle la sortie sur son entrée. En faisant cela, on garantit que le registre revient à zéro lors d'un débordement, zéro étant codé avec un 1 dans le bit de poids faible. Au passage, si vous ne mettez que des 0 dans un compteur en anneau, il restera bloqué pour toujours : décaler une suite de 0 donnera la même suite de 0. Initialiser un compteur ''one-hot'' demande donc quelques subtilités qu'on détaillera plus bas.
[[File:Compteur en anneau de 4 bits.jpg|centre|vignette|upright=2|Compteur en anneau de 4 bits]]
Un compteur ''one-hot'' de N bits peut prendre N valeurs différentes, qui ont toutes un seul bit à 1. Pour 5 bits, la séquence est illustrée ci-dessous.
[[File:Lauflicht 5 LED.gif|centre|vignette|Compteur en anneau de 5 bits.]]
Faire des comparaisons avec ce type de compteur est très simple : le compteur contient la valeur N si le énième bit est à 1. Pas besoin d'utiliser de circuit comparateur, juste de lire un bit. Par contre, ce compteur n'est pas très économe en bascules. Imaginons que l'on veut un compteur qui compte jusqu'à une valeur N arbitraire : un compteur binaire normal utilisera environ <math>\log_2{(N)}</math> bascules, alors qu'un compteur ''one-hot'' demande N bascules. Mais si N est assez petit, l'économie de bascules est assez faible, alors que l'économie de circuits comparateurs/incrémenteurs l'est beaucoup plus.
Il y a peu d'applications qui utilisent des compteurs en anneau. Ils étaient autrefois utilisés dans les tous premiers ordinateurs, notamment ceux qui géraient une représentation des nombres spécifique appelée la '''''Bi-quinary coded decimal'''''. De nos jours, de tels compteurs sont utilisés dans les séquenceurs de processeurs, mais aussi dans les séquenceurs de certains périphériques, ou dans les circuits séquentiels simples qui se résument à des machines à états. Ils sont alors utilisés car très rapides, parfaitement adaptés au stockage de petites valeur, et surtout : ils n'ont pas besoin de circuit comparateur pour connaitre la valeur stockée dedans. Nous n'allons pas rentrer dans le détail de leurs utilisations, car nous en reparlerons dans la suite du cours.
==Les registres à décalage à rétroaction linéaire==
Les '''registres à décalage à rétroaction linéaire''' sont des registres à décalages un peu particuliers. Le terme anglais pour de tels registres est ''Linear Feedback Shift Register'', ce qui s’abrège en LFSR. Nous utiliserons cette abréviation dans ce qui suit pour simplifier grandement l'écriture.
Les LFSR sont appelés ainsi pour plusieurs raisons. Déjà, registre à décalage implique qu'ils sont fabriqués avec un registre à décalage. À rétroaction indique que l'on boucle la sortie sur l'entrée. Le terme combinaison linéaire demande quelques explications. L'idée est qu'entre la sortie et l'entrée, il y a un circuit combinatoire qui s'intercale entre la sortie et l'entrée, qui calcule ce qu'il faut mettre sur l'entrée, à partir du contenu du registre
Pour simplifier, cela veut dire qu'on multiplie chaque bit par 0 ou 1, avant d'additionner le tout. Dans ce calcul, on ne garde qu'un seul bit du résultat, vu que l'entrée du registre à décalage ne fait qu'un bit. Par simplicité, on ne garde que le bit de poids faible. Or, il s'avère que cela simplifie grandement les calculs, car cela permet de remplacer les additions par une simple opération XOR.
: <math>1 \times a_n \oplus ... + 1 \oplus a_3 + 1 \oplus a_1</math>
Le résultat est ce que l'on appelle un '''''LFSR de Fibonacci''''', ou encore un LFSR classique, qui celui qui colle le mieux avec la définition.
[[File:LFSR-F16.gif|centre|vignette|upright=2|Registre à décalage à rétroaction de Fibonnaci.]]
Les '''registres à décalages à rétroaction affine''' sont identique aux précédents à une différence près : le bit calculé est inversé avant d'être inséré dans le registre. Un tel circuit est donc composé de portes NXOR, comparé à son comparse linéaire, composé à partir de portes XOR. Petite remarque : si je prends un registre à rétroaction linéaire et un registre à rétroaction affine avec les mêmes coefficients sur les mêmes bits, le résultat du premier sera égal à l'inverse de l'autre.
Les '''LSFR de Gallois''' sont un peu l'inverse des LFSR vus juste avant. Au lieu d'utiliser un registre à décalage SIPO, ils utilisent un registre à décalage PISO. Pour faire la différence, nous appellerons ces derniers les LFSR PISO, et les premiers LFSR SIPO. Avec les LFSR PISO, on prend le bit sortant et on en déduit plusieurs bits à partir d'un circuit combinatoire, qui sont chacun insérés dans le registre à décalage à un endroit bien précis. Bien sûr, la fonction qui calcule des différents bits à partir du bit d'entrée conserve les mêmes propriétés que celle utilisée pour les LFSR : elle se calcule avec uniquement des portes XOR, ou NXOR pour leur variante affine.
Leur avantage est qu'ils sont plus rapides, car il n'y a qu'une seule porte logique entre la sortie et une entrée du registre à décalage, contre potentiellement plusieurs avec les LFSR SIPO. Notons que tout comme les LFSR qui ne peuvent pas mémoriser un 0, de tels registres à décalage à rétroaction ne peuvent pas avoir la valeur maximale stockable dans le registre. Cette valeur gèle le registre à cette valeur, dans le sens où le résultat au cycle suivant sera identique. Mais cela ne pose pas de problèmes pour l'initialisation du compteur.
[[File:LFSR-G16.svg|centre|vignette|upright=2|Registre à décalage à rétroaction de Galois.]]
==Les généralités sur les LSFRs ==
Quelques propriétés sont communes à tous les LSFRs. Un LSFR est déterministe : pour le même résultat en entrée, il donnera toujours le même résultat en sortie. De plus, il ne peut contenir qu'un nombre fini de valeurs. Et ces deux propriétés sont source de comportements qu'on va détailler ci-dessous.
===L'initialisation d'un LSFR===
Les LSFR ne peuvent pas être initialisés à une valeur arbitraire, en raison de la présence d'une '''valeur interdite'''. Concrètement, une fois que le décompte arrive sur une valeur interdite, le compteur reste bloqué sur cette valeur. La valeur suivante, calculée par le compteur, est cette valeur elle-même.
Le premier cas est celui où le compteur peut être initialisé avec zéro sans que cela ne pose problème. Sur de tels LSFRs, la réinitialisation se fait comme pour n'importe quel registre/compteur. A savoir que les entrées de reset des bascules sont toutes connectées ensemble, au même signal de reset.
[[File:Johnson Counter 4bit.svg|centre|vignette|upright=2|Compteur de Johnson de 4 bits]]
Le second cas est celui des LFSR non-affine. Lors de la réinitialisation, il faut que toutes les bascules soient réinitialisées à 0, sauf une qui est mise à 1. La bascule en question doit disposer d'une entrée S (''Set'') qui met la bascule à 1 quand elle est activée. Cela garantit que le registre est réinitialisé avec un zéro codé en ''one-hot''.
[[File:Overbeck Counter 4bit.svg|centre|vignette|upright=2|Compteur en anneau de 4 bits]]
Une autre solution est de mettre un multiplexeur juste avant l'entrée du registre à décalage. Cette solution marché bien dans le sens où elle permet d'initialiser le registre avec une valeur arbitraire, qui est insérée dans le registre en plusieurs cycles. Pour les LFSR, le multiplexeur est connecté soit au bit calculé par les portes XOR, soit par une entrée servant uniquement de l'initialisation.
[[File:Initialisation d'un LFSR.jpg|centre|vignette|upright=2|Initialisation d'un LFSR]]
===La période d'un LSFR===
Si le compteur est initialisé correctement, il passera d'une valeur à l'autre et finira par repasser par une valeur qu'il aura déjà parcourue et son fonctionnement se reproduira à l'identique comparé à son passage antérieur, il bouclera. Il parcourt un nombre N de valeurs à chaque cycle, ce nombre étant appelé la '''période du compteur'''.
La période d'un LSFR dépend fortement de la fonction utilisée pour calculer le bit de sortie. Dans le meilleur des cas, le LSFR passera par toutes les valeurs que le registre peut prendre, sauf une : suivant le registre, le zéro ou sa valeur maximale sont interdits. Si un LSFR passe par zéro, il y reste bloqué définitivement. La raison à cela est simple : un XOR sur des zéros donnera toujours 0. Le même raisonnement peut être tenu pour les registres à rétroaction affine, sauf que cette fois-ci, c'est la valeur maximale stockable dans le registre qui est fautive.
Tout le chalenge consiste donc à trouver quels sont les LSFR dont la période est maximale : ceux dont la période vaut <math>2^n - 1</math>. Qu'on se rassure, quelle que soit la longueur du registre, il en existe au moins un : cela se prouve mathématiquement.
<noinclude>[[File:LFSR-F4.GIF|centre|vignette|upright=2|Exemple avec un registre à rétroaction linéaire de 4 bits.]]</noinclude>
==La génération de nombres pseudo-aléatoires==
Les LSFRs peuvent aussi être utilisés pour générer des nombres "aléatoires". Je dis aléatoires entre guillemets, car ils ne sont pas vraiment aléatoires, mais s'en rapprochent suffisamment pour être considérés comme tels. Pour mettre en avant cela, on parle aussi de nombres "pseudo-aléatoires". De nombreuses situations demandent de générer des nombres pseudo-aléatoire de manière matérielle. Cela peut servir pour sélectionner une ligne de cache à remplacer lors d'un défaut de cache, pour implémenter des circuits cryptographiques, pour calculer la durée d'émission sur un bus Ethernet à la suite d'une collision, et j'en passe.
Les méthodes que nous allons voir produisent un nombre pseudo-aléatoire un bit à la fois, à quelques exceptions près. Les circuits que nous allons voir fournissent un bit sur leur sortie et ce bit varie de manière assez aléatoire. Les bits en sortie du circuit sont accumulés dans un registre à décalage normal, pour former un nombre aléatoire. Nous appellerons ce registre : l'accumulateur.
===L'usage de registres à décalage à rétroaction===
[[File:Nonlinear-combo-generator.png|thumb|Nonlinear-combo-generator]]
Un LSFR seul ne fournit pas un aléatoire digne de ce nom, mais il est possible de combiner plusieurs LSFR pour obtenir une meilleure approximation de l'aléatoire. Avec cette technique, plusieurs registres à décalages à rétroaction sont reliés à un circuit combinatoire non-linéaire. Ce circuit prendra en entrée un (ou plusieurs) bit de chaque registre à décalage à rétroaction, et combinera ces bits pour fournir un bit de sortie.
[[File:A5-1 GSM cipher.svg|centre|thumb|upright=2|Exemple avec trois LSFR différents, de tailles différentes : le bit envoyé à l'accumulateur est un XOR du bit sortant des trois LSFR.]]
Pour rendre le tout encore plus aléatoire, il est possible de cadencer les LSFR à des fréquences différentes. Cette technique est utilisée dans les générateurs ''stop-and-go'', ''alternative step'', et à ''shrinking''.
* Le générateur ''alternative step'' utilise trois LSFR. Le premier commande un multiplexeur qui choisit la sortie parmi les deux restants.
* Le générateur ''stop-and-go'' utilise deux LSFR. Le premier est relié à l'entrée d'horloge du second et le bit de sortie du second est utilisé comme résultat. Une technique similaire était utilisée dans les processeurs VIA C3, pour l'implémentation de leurs instructions cryptographiques.
* Le ''shrinking generator'' utilise deux LSFR cadencés à des vitesses différentes. Si le bit de sortie du premier vaut 1, alors le bit de sortie du second est utilisé comme résultat. Par contre, si le bit de sortie du premier vaut 0, aucun bit n'est fourni en sortie, le bit de sortie du second registre est oublié.
===L'aléatoire généré par des ''timers'' ou des compteurs d'horloge===
Au-delà des LSFR, il est possible d'utiliser des compteurs pour générer du pseudo-aléatoire. Par exemple, une technique très simple utilise un simple ''timer''. Si on a besoin d'un nombre pseudo-aléatoire, il suffit de lire le ''timer'' et d'utiliser le nombre lu comme nombre pseudo-aléatoire. Si le délai entre deux demandes est irrégulier, le résultat semblera aléatoire. Mais il s'agit là d'une technique assez peu fiable dans le monde réel et seules quelques applications bien spécifiques se satisfont de cette méthode.
Une solution un peu plus fiable utilise ce qu'on appelle la '''dérive de l'horloge'''. Il faut savoir qu'un signal d'horloge n'est jamais vraiment très précis. Une horloge censée tourner à 1 Ghz ne tournera pas en permanence à 1Ghz exactement, mais verra sa fréquence varier de quelques Hz ou Khz de manière irrégulière. Ces variations peuvent venir de variations aléatoires de température, des variations de tension, des perturbations électromagnétiques, ou à des phénomènes assez compliqués qui peuvent se produire dans tout circuit électrique (comme le ''shot noise'').
L'idée la plus simple utilise deux horloges : une horloge lente et une horloge rapide, dont la fréquence est un multiple de l'autre. Par exemple, on peut choisir une fréquence de 1 Mhz et une autre de 100 Hz : la fréquence la plus grande est égale à 10000 fois l'autre. La dérive d'horloge fera son œuvre, les deux horloges seront très légèrement désynchronisées en permanence, et cette désynchronisation peut être utilisée pour produire des nombres aléatoires. Par exemple, on peut compter le nombre de cycles d'horloge produit par l'horloge rapide durant une période de l'horloge lente. Si ce nombre est pair, on produit un bit aléatoire qui vaut 1 , il vaut 0 si ce nombre est pair. Pour information, c'est exactement cette technique qui était utilisée dans l'''Intel 82802 Firmware Hub''.
Il existe d'autres solutions matérielles qui utilisent le bruit thermique. Tous les circuits électroniques de l'univers sont soumis à de microscopiques variations de température, dues à l'agitation thermique des atomes. Plus la température est élevée, plus les atomes qui composent les fils métalliques des circuits s'agitent. Vu que les particules d'un métal contiennent des charges électriques, ces vibrations font naître des variations de tensions assez infimes. Il suffit d'amplifier ces variations pour obtenir un résultat capable de représenter un zéro ou un 1. Ce principe a été utilisé sur des anciens processeurs Intel qui géraient l'instruction RDRAND, une instruction qui produisait un nombre aléatoire.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les compteurs et timers
| prevText=Les compteurs et timers
| next=Les circuits de décalage et de rotation
| nextText=Les circuits de décalage et de rotation
}}
</noinclude>
orurn9j893qqnrbxunhwq3ingtrhi86
768618
768617
2026-06-25T15:20:54Z
Mewtow
31375
/* Les généralités sur les LSFRs */
768618
wikitext
text/x-wiki
Les '''registres à décalage''' sont des registres dont le contenu est décalé d'un cran vers la gauche ou la droite sur commande. Nous aurons à les réutiliser plus tard dans ce cours, notamment dans la section sur les circuits de génération de nombres aléatoires, ou dans certains circuits liés au cache. Les registres à décalage sont presque tous synchrones et ce chapitre ne parlera que ce ces derniers. L'animation suivante illustre le fonctionnement d'un registre à décalage qui décale son contenu d'un cran vers la droite à chaque cycle d'horloge.
[[File:4-Bit PISO Shift Register Seq.gif|centre|vignette|upright=2|Registre à décalage.]]
==Les registres à décalage : sous-types==
On peut classer les registres selon le caractère de l'entrée et de la sortie, qui peut être parallèle (entrée de plusieurs bits) ou série (entrée d'un seul bit).
* Sur les registres simples, les entrées et sorties pour les données sont toujours parallèles. Pour un registre de N bits, il y a une entrée d'écriture de N bits et une sortie de N bits. C'est la raison pour laquelle ils sont appelés des '''registres à entrées et sorties parallèles'''.
* Sur les registres à '''entrée et sortie série''', on peut mettre à jour un bit à la fois, de même qu'on ne peut en récupérer qu'un à la fois. Ces registres servent essentiellement à mettre en attente des bits tout en gardant leur ordre : un bit envoyé en entrée ressortira sur la sortie après plusieurs commandes de mise à jour sur l'entrée Enable.
* Les registres à décalage à '''entrée série et sortie parallèle''' sont similaires aux précédents : on peut ajouter un nouveau bit en commandant l'entrée Enable et les anciens bits sont alors décalés d'un cran. Par contre, on peut récupérer (lire) tous les bits en une seule fois. Ils permettent notamment de reconstituer un nombre qui est envoyé bit par bit sur un fil (un bus série).
* Enfin, il reste les registres à '''entrée parallèle et sortie série'''. Ces registres sont utiles quand on veut transmettre un nombre sur un fil : on peut ainsi envoyer les bits un par un.
[[File:Td5bfig1.png|centre|vignette|upright=2|Classification des registres à décalage.]]
Pour résumer, on distingue quatre types de registres (à décalage ou non), qui portent les noms de PIPO, PISO, SIPO et SISO. Les noms peuvent sembler barbares, mais il y a une logique derrière ces termes.La lettre P est pour parallèle, la lettre S est pour série. La lettre I signifie ''Input'', ce qui veut dire entrée en anglais, la lettre O est pour ''Output'', la sortie en anglais.
{|class="wikitable"
|+ Classification des registres
|-
!
! Entrée parallèle
! Entrée série
|-
! Sortie parallèle
| PIPO (registre simple)
| SIPO
|-
! Sortie série
| PISO
| SISO
|}
==L'intérieur d'un registre à décalage==
Tous les registres sont conçus en plaçant plusieurs bascules les unes à la suite des autres, que ce soit pour les registres simples ou les registres à décalage. La seule différence tient dans la manière dont les bascules sont reliées. Toutes les bascules sont reliées à l'entrée d'horloge, l'entrée Enable, l'entrée Reset, ou aux autres entrées de commandes. Mais c'est une autre paire de manche pour les entrées/sorties de données.
Dans un registre simple, les bascules sont indépendantes et ne sont pas reliées entre elles.
[[File:Register.svg|centre|vignette|upright=3|Registre simple.]]
À l'inverse, dans les registres à décalage, il existe des connexions entre bascules. Plus précisément, les bascules sont reliées les unes à la suite des autres, elles forment une chaîne de bascules reliées deux à deux. Et les connexions entre bascules sont les mêmes que l'on parle d'un registre à décalage de type SIPO, PISO ou SISO.
[[File:Shift-Register.svg|centre|vignette|upright=3|Exemple de registre à décalage]]
Outre le fait que les bascules sont reliées de la même manière, les autres connexions sont les mêmes dans tous les registres. L'entrée d'horloge (non-représentée dans les schémas qui vont suivre) est envoyée à toutes les bascules. Même chose pour l'entrée Enable, qui est reliée aux entrées E de toutes les bascules. La différence entre ces registres tient dans les endroits où se trouvent les entrées et les sorties du registre.
{|
|+ Implémentation des registres avec des bascules.
|-
|[[File:Registre à entrée parallèle et sortie série.png|vignette|upright=2|Registre à entrée et sortie série.]]
|[[File:Registre à lecture et écriture parallèle.png|vignette|upright=2|Registre à entrée et sortie parallèle.]]
|-
|[[File:Registre à entrée et sortie série.png|vignette|upright=2|Registre à entrée série et sortie parallèle.]]
|[[File:Registre à entrée série et sortie parallèle.png|vignette|upright=2|Registre à entrée parallèle et sortie série.]]
|}
==Les compteurs basés sur des registres à décalage==
Les registres à décalage peuvent être utilisés pour implémenter des compteurs. Les compteurs en question peuvent être des compteurs classiques, qui comptent en binaire. De tels compteurs sont appelés des compteurs asynchrones, bien que ce soit un abus de langage. Une autre utilisation est celle des compteurs ''one hot'', qui ne comptent pas en binaire normal. Voyons ces deux utilisations dans ce qui suit.
===Les mal-nommés ''compteurs asynchrones''===
Les '''compteurs asynchrones''' n'utilisent pas de circuit incrémenteur. A la place, ils sont fabriqués à partir d'un registre à décalage. Ils utilisent moins de portes logiques que les compteurs synchrones. Et ils ne sont pas forcément plus difficiles à comprendre. Précisons cependant qu'ils sont très mal nommés, à savoir que ce sont bien des circuits synchrones.
Pour comprendre comment fonctionne un compteur asynchrone, il faut regarder la séquence des premiers entiers :
* 000 ;
* 001 ;
* 010 ;
* 011 ;
* 100 ;
* 101 ;
* 110 ;
* 111.
Il faut remarquer que le bit de poids faible s'inverse à chaque cycle d'horloge. Pour les colonnes suivantes, le bit s'inverse quand le bit de la colonne précédente passe de 1 à 0, lors d'un front descendant sur la colonne précédente. Maintenant que l'on sait cela, on peut créer un compteur avec des bascules T (elles inversent leur contenu à chaque cycle d'horloge). La première colonne inverse son contenu à chaque cycle, elle correspond donc à une bascule T reliée directement à l'horloge. Les autres colonnes utilisent des bascules T activées sur front descendant.
: Attention, la bascule la plus à gauche stocke le bit de poids faible, pas celui de poids fort. Cela sera pareil dans tous les schémas qui suivront.
[[File:AsyncCounter Alternativ 2.svg|centre|vignette|upright=2|Compteur asynchrone de 4 bits.]]
Le compteur précédent ne peut pas être réinitialisé, ce qui pose des problèmes pour implémenter des compteurs modulo. Pour cela, il faut que les bascules du compteur aient une entrée de réinitialisation ''Reset'', qui les force à se remettre à zéro. Il suffit alors de connecter ensemble les entrées ''Reset'' des bascules à l'entrée ''Reset'' du compteur.
[[File:AsyncCounter mod 8 with RST .svg|centre|vignette|upright=2|Compteur réinitialisable.]]
Implémenter un compteur modulo demande d'ajouter un comparateur qui détecte quand la valeur maximale est atteinte, afin de commander l'entrée de réinitialisation. Un tel circuit est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Modulo-5-Zähler.svg|centre|vignette|upright=2|Compteur modulo 10.]]
Il est aussi possible d'utiliser des bascules D à la place des bascules T simplifiées. En effet, une bascule T simplifiée est identique à une bascule D dont on boucle la sortie /Q sur l'entrée de données. Cette implémentation permet d'ailleurs de réinitialiser le compteur à une valeur non-nulle. Pour cela, l'entrée de chaque bascule D est précédée d'un multiplexeur, qui choisit entre le bit calculé par le compteur et celui présenté sur l'entrée de ré-initialisation. Quand l'entrée ''Reset'' est activée, les multiplexeurs connectent les bascules aux bits sur l'entrée de ré-initialisation. Dans le cas contraire, le compteur fonctionne normalement, les multiplexeurs connectant l'entrée de chaque bascule à sa sortie.
[[File:Compteur asynchrone, avec initialisation.jpg|centre|vignette|upright=2|Compteur asynchrone, avec initialisation.]]
Pour finir, la sortie de débordement n'est autre que la sortie de la dernière bascule, celle qui contient le bit de poids fort.
===Les compteurs ''one-hot''===
Les '''compteurs ''one-hot''''' sont appelés ainsi, car ils permettent de compter dans une représentation des nombres appelée la représentation ''one-hot''. Pour rappel, dans une telle représentation, un seul bit est à 1 pendant que les autres sont à 0. Les entiers sont codés de la manière suivante : le nombre N est encodé en mettant le énième bit à 1, avec la condition que l'on commence à compteur à partir de zéro. Il est important de remarquer que dans cette représentation, le zéro est n'est PAS codé en mettant tous les bits à 0, la valeur 0000...0000 est une valeur interdite. À la place, le zéro est codé en mettant le bit de poids faible à 1. Pour N bits, on peut encoder seulement N valeurs, dont le zéro.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! ''One-hot''
|-
| 0 || 000 || 00000001
|-
| 1 || 001 || 00000010
|-
| 2 || 010 || 00000100
|-
| 3 || 011 || 00001000
|-
| 4 || 100 || 00010000
|-
| 5 || 101 || 00100000
|-
| 6 || 110 || 01000000
|-
| 7 || 111 || 10000000
|}
Un compteur en représentation ''one-hot'' contient un nombre codé de cette manière, qui est incrémenté ou décrémenté si besoin. Pour donner un exemple, la séquence d'un compteur en anneau de 4 bits est :
* 0001 (0) ;
* 0010 (1) ;
* 0100 (2) ;
* 1000 (3) .
Un compteur ''one-hot'' basique est composé d'un registre à décalage dont on boucle la sortie sur son entrée. En faisant cela, on garantit que le registre revient à zéro lors d'un débordement, zéro étant codé avec un 1 dans le bit de poids faible. Au passage, si vous ne mettez que des 0 dans un compteur en anneau, il restera bloqué pour toujours : décaler une suite de 0 donnera la même suite de 0. Initialiser un compteur ''one-hot'' demande donc quelques subtilités qu'on détaillera plus bas.
[[File:Compteur en anneau de 4 bits.jpg|centre|vignette|upright=2|Compteur en anneau de 4 bits]]
Un compteur ''one-hot'' de N bits peut prendre N valeurs différentes, qui ont toutes un seul bit à 1. Pour 5 bits, la séquence est illustrée ci-dessous.
[[File:Lauflicht 5 LED.gif|centre|vignette|Compteur en anneau de 5 bits.]]
Faire des comparaisons avec ce type de compteur est très simple : le compteur contient la valeur N si le énième bit est à 1. Pas besoin d'utiliser de circuit comparateur, juste de lire un bit. Par contre, ce compteur n'est pas très économe en bascules. Imaginons que l'on veut un compteur qui compte jusqu'à une valeur N arbitraire : un compteur binaire normal utilisera environ <math>\log_2{(N)}</math> bascules, alors qu'un compteur ''one-hot'' demande N bascules. Mais si N est assez petit, l'économie de bascules est assez faible, alors que l'économie de circuits comparateurs/incrémenteurs l'est beaucoup plus.
Il y a peu d'applications qui utilisent des compteurs en anneau. Ils étaient autrefois utilisés dans les tous premiers ordinateurs, notamment ceux qui géraient une représentation des nombres spécifique appelée la '''''Bi-quinary coded decimal'''''. De nos jours, de tels compteurs sont utilisés dans les séquenceurs de processeurs, mais aussi dans les séquenceurs de certains périphériques, ou dans les circuits séquentiels simples qui se résument à des machines à états. Ils sont alors utilisés car très rapides, parfaitement adaptés au stockage de petites valeur, et surtout : ils n'ont pas besoin de circuit comparateur pour connaitre la valeur stockée dedans. Nous n'allons pas rentrer dans le détail de leurs utilisations, car nous en reparlerons dans la suite du cours.
==Les registres à décalage à rétroaction linéaire==
Les '''registres à décalage à rétroaction linéaire''' sont des registres à décalages un peu particuliers. Le terme anglais pour de tels registres est ''Linear Feedback Shift Register'', ce qui s’abrège en LFSR. Nous utiliserons cette abréviation dans ce qui suit pour simplifier grandement l'écriture.
Les LFSR sont appelés ainsi pour plusieurs raisons. Déjà, registre à décalage implique qu'ils sont fabriqués avec un registre à décalage. À rétroaction indique que l'on boucle la sortie sur l'entrée. Le terme combinaison linéaire demande quelques explications. L'idée est qu'entre la sortie et l'entrée, il y a un circuit combinatoire qui s'intercale entre la sortie et l'entrée, qui calcule ce qu'il faut mettre sur l'entrée, à partir du contenu du registre
Pour simplifier, cela veut dire qu'on multiplie chaque bit par 0 ou 1, avant d'additionner le tout. Dans ce calcul, on ne garde qu'un seul bit du résultat, vu que l'entrée du registre à décalage ne fait qu'un bit. Par simplicité, on ne garde que le bit de poids faible. Or, il s'avère que cela simplifie grandement les calculs, car cela permet de remplacer les additions par une simple opération XOR.
: <math>1 \times a_n \oplus ... + 1 \oplus a_3 + 1 \oplus a_1</math>
Le résultat est ce que l'on appelle un '''''LFSR de Fibonacci''''', ou encore un LFSR classique, qui celui qui colle le mieux avec la définition.
[[File:LFSR-F16.gif|centre|vignette|upright=2|Registre à décalage à rétroaction de Fibonnaci.]]
Les '''registres à décalages à rétroaction affine''' sont identique aux précédents à une différence près : le bit calculé est inversé avant d'être inséré dans le registre. Un tel circuit est donc composé de portes NXOR, comparé à son comparse linéaire, composé à partir de portes XOR. Petite remarque : si je prends un registre à rétroaction linéaire et un registre à rétroaction affine avec les mêmes coefficients sur les mêmes bits, le résultat du premier sera égal à l'inverse de l'autre.
Les '''LSFR de Gallois''' sont un peu l'inverse des LFSR vus juste avant. Au lieu d'utiliser un registre à décalage SIPO, ils utilisent un registre à décalage PISO. Pour faire la différence, nous appellerons ces derniers les LFSR PISO, et les premiers LFSR SIPO. Avec les LFSR PISO, on prend le bit sortant et on en déduit plusieurs bits à partir d'un circuit combinatoire, qui sont chacun insérés dans le registre à décalage à un endroit bien précis. Bien sûr, la fonction qui calcule des différents bits à partir du bit d'entrée conserve les mêmes propriétés que celle utilisée pour les LFSR : elle se calcule avec uniquement des portes XOR, ou NXOR pour leur variante affine.
Leur avantage est qu'ils sont plus rapides, car il n'y a qu'une seule porte logique entre la sortie et une entrée du registre à décalage, contre potentiellement plusieurs avec les LFSR SIPO. Notons que tout comme les LFSR qui ne peuvent pas mémoriser un 0, de tels registres à décalage à rétroaction ne peuvent pas avoir la valeur maximale stockable dans le registre. Cette valeur gèle le registre à cette valeur, dans le sens où le résultat au cycle suivant sera identique. Mais cela ne pose pas de problèmes pour l'initialisation du compteur.
[[File:LFSR-G16.svg|centre|vignette|upright=2|Registre à décalage à rétroaction de Galois.]]
Quelques propriétés sont communes à tous les LSFRs. Un LSFR est déterministe : pour le même résultat en entrée, il donnera toujours le même résultat en sortie. De plus, il ne peut contenir qu'un nombre fini de valeurs. Et ces deux propriétés sont source de comportements qu'on va détailler ci-dessous.
===L'initialisation d'un LSFR===
Les LSFR ne peuvent pas être initialisés à une valeur arbitraire, en raison de la présence d'une '''valeur interdite'''. Concrètement, une fois que le décompte arrive sur une valeur interdite, le compteur reste bloqué sur cette valeur. La valeur suivante, calculée par le compteur, est cette valeur elle-même.
Le premier cas est celui où le compteur peut être initialisé avec zéro sans que cela ne pose problème. Sur de tels LSFRs, la réinitialisation se fait comme pour n'importe quel registre/compteur. A savoir que les entrées de reset des bascules sont toutes connectées ensemble, au même signal de reset.
[[File:Johnson Counter 4bit.svg|centre|vignette|upright=2|Compteur de Johnson de 4 bits]]
Le second cas est celui des LFSR non-affine. Lors de la réinitialisation, il faut que toutes les bascules soient réinitialisées à 0, sauf une qui est mise à 1. La bascule en question doit disposer d'une entrée S (''Set'') qui met la bascule à 1 quand elle est activée. Cela garantit que le registre est réinitialisé avec un zéro codé en ''one-hot''.
[[File:Overbeck Counter 4bit.svg|centre|vignette|upright=2|Compteur en anneau de 4 bits]]
Une autre solution est de mettre un multiplexeur juste avant l'entrée du registre à décalage. Cette solution marché bien dans le sens où elle permet d'initialiser le registre avec une valeur arbitraire, qui est insérée dans le registre en plusieurs cycles. Pour les LFSR, le multiplexeur est connecté soit au bit calculé par les portes XOR, soit par une entrée servant uniquement de l'initialisation.
[[File:Initialisation d'un LFSR.jpg|centre|vignette|upright=2|Initialisation d'un LFSR]]
===La période d'un LSFR===
Si le compteur est initialisé correctement, il passera d'une valeur à l'autre et finira par repasser par une valeur qu'il aura déjà parcourue et son fonctionnement se reproduira à l'identique comparé à son passage antérieur, il bouclera. Il parcourt un nombre N de valeurs à chaque cycle, ce nombre étant appelé la '''période du compteur'''.
La période d'un LSFR dépend fortement de la fonction utilisée pour calculer le bit de sortie. Dans le meilleur des cas, le LSFR passera par toutes les valeurs que le registre peut prendre, sauf une : suivant le registre, le zéro ou sa valeur maximale sont interdits. Si un LSFR passe par zéro, il y reste bloqué définitivement. La raison à cela est simple : un XOR sur des zéros donnera toujours 0. Le même raisonnement peut être tenu pour les registres à rétroaction affine, sauf que cette fois-ci, c'est la valeur maximale stockable dans le registre qui est fautive.
Tout le chalenge consiste donc à trouver quels sont les LSFR dont la période est maximale : ceux dont la période vaut <math>2^n - 1</math>. Qu'on se rassure, quelle que soit la longueur du registre, il en existe au moins un : cela se prouve mathématiquement.
<noinclude>[[File:LFSR-F4.GIF|centre|vignette|upright=2|Exemple avec un registre à rétroaction linéaire de 4 bits.]]</noinclude>
==La génération de nombres pseudo-aléatoires==
Les LSFRs peuvent aussi être utilisés pour générer des nombres "aléatoires". Je dis aléatoires entre guillemets, car ils ne sont pas vraiment aléatoires, mais s'en rapprochent suffisamment pour être considérés comme tels. Pour mettre en avant cela, on parle aussi de nombres "pseudo-aléatoires". De nombreuses situations demandent de générer des nombres pseudo-aléatoire de manière matérielle. Cela peut servir pour sélectionner une ligne de cache à remplacer lors d'un défaut de cache, pour implémenter des circuits cryptographiques, pour calculer la durée d'émission sur un bus Ethernet à la suite d'une collision, et j'en passe.
Les méthodes que nous allons voir produisent un nombre pseudo-aléatoire un bit à la fois, à quelques exceptions près. Les circuits que nous allons voir fournissent un bit sur leur sortie et ce bit varie de manière assez aléatoire. Les bits en sortie du circuit sont accumulés dans un registre à décalage normal, pour former un nombre aléatoire. Nous appellerons ce registre : l'accumulateur.
===L'usage de registres à décalage à rétroaction===
[[File:Nonlinear-combo-generator.png|thumb|Nonlinear-combo-generator]]
Un LSFR seul ne fournit pas un aléatoire digne de ce nom, mais il est possible de combiner plusieurs LSFR pour obtenir une meilleure approximation de l'aléatoire. Avec cette technique, plusieurs registres à décalages à rétroaction sont reliés à un circuit combinatoire non-linéaire. Ce circuit prendra en entrée un (ou plusieurs) bit de chaque registre à décalage à rétroaction, et combinera ces bits pour fournir un bit de sortie.
[[File:A5-1 GSM cipher.svg|centre|thumb|upright=2|Exemple avec trois LSFR différents, de tailles différentes : le bit envoyé à l'accumulateur est un XOR du bit sortant des trois LSFR.]]
Pour rendre le tout encore plus aléatoire, il est possible de cadencer les LSFR à des fréquences différentes. Cette technique est utilisée dans les générateurs ''stop-and-go'', ''alternative step'', et à ''shrinking''.
* Le générateur ''alternative step'' utilise trois LSFR. Le premier commande un multiplexeur qui choisit la sortie parmi les deux restants.
* Le générateur ''stop-and-go'' utilise deux LSFR. Le premier est relié à l'entrée d'horloge du second et le bit de sortie du second est utilisé comme résultat. Une technique similaire était utilisée dans les processeurs VIA C3, pour l'implémentation de leurs instructions cryptographiques.
* Le ''shrinking generator'' utilise deux LSFR cadencés à des vitesses différentes. Si le bit de sortie du premier vaut 1, alors le bit de sortie du second est utilisé comme résultat. Par contre, si le bit de sortie du premier vaut 0, aucun bit n'est fourni en sortie, le bit de sortie du second registre est oublié.
===L'aléatoire généré par des ''timers'' ou des compteurs d'horloge===
Au-delà des LSFR, il est possible d'utiliser des compteurs pour générer du pseudo-aléatoire. Par exemple, une technique très simple utilise un simple ''timer''. Si on a besoin d'un nombre pseudo-aléatoire, il suffit de lire le ''timer'' et d'utiliser le nombre lu comme nombre pseudo-aléatoire. Si le délai entre deux demandes est irrégulier, le résultat semblera aléatoire. Mais il s'agit là d'une technique assez peu fiable dans le monde réel et seules quelques applications bien spécifiques se satisfont de cette méthode.
Une solution un peu plus fiable utilise ce qu'on appelle la '''dérive de l'horloge'''. Il faut savoir qu'un signal d'horloge n'est jamais vraiment très précis. Une horloge censée tourner à 1 Ghz ne tournera pas en permanence à 1Ghz exactement, mais verra sa fréquence varier de quelques Hz ou Khz de manière irrégulière. Ces variations peuvent venir de variations aléatoires de température, des variations de tension, des perturbations électromagnétiques, ou à des phénomènes assez compliqués qui peuvent se produire dans tout circuit électrique (comme le ''shot noise'').
L'idée la plus simple utilise deux horloges : une horloge lente et une horloge rapide, dont la fréquence est un multiple de l'autre. Par exemple, on peut choisir une fréquence de 1 Mhz et une autre de 100 Hz : la fréquence la plus grande est égale à 10000 fois l'autre. La dérive d'horloge fera son œuvre, les deux horloges seront très légèrement désynchronisées en permanence, et cette désynchronisation peut être utilisée pour produire des nombres aléatoires. Par exemple, on peut compter le nombre de cycles d'horloge produit par l'horloge rapide durant une période de l'horloge lente. Si ce nombre est pair, on produit un bit aléatoire qui vaut 1 , il vaut 0 si ce nombre est pair. Pour information, c'est exactement cette technique qui était utilisée dans l'''Intel 82802 Firmware Hub''.
Il existe d'autres solutions matérielles qui utilisent le bruit thermique. Tous les circuits électroniques de l'univers sont soumis à de microscopiques variations de température, dues à l'agitation thermique des atomes. Plus la température est élevée, plus les atomes qui composent les fils métalliques des circuits s'agitent. Vu que les particules d'un métal contiennent des charges électriques, ces vibrations font naître des variations de tensions assez infimes. Il suffit d'amplifier ces variations pour obtenir un résultat capable de représenter un zéro ou un 1. Ce principe a été utilisé sur des anciens processeurs Intel qui géraient l'instruction RDRAND, une instruction qui produisait un nombre aléatoire.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les compteurs et timers
| prevText=Les compteurs et timers
| next=Les circuits de décalage et de rotation
| nextText=Les circuits de décalage et de rotation
}}
</noinclude>
dgiyw0fsh9lm2wgvo8mlkh8h9ghiyem
768619
768618
2026-06-25T15:21:41Z
Mewtow
31375
/* Les registres à décalage : sous-types */
768619
wikitext
text/x-wiki
Les '''registres à décalage''' sont des registres dont le contenu est décalé d'un cran vers la gauche ou la droite sur commande. Nous aurons à les réutiliser plus tard dans ce cours, notamment dans la section sur les circuits de génération de nombres aléatoires, ou dans certains circuits liés au cache. Les registres à décalage sont presque tous synchrones et ce chapitre ne parlera que ce ces derniers. L'animation suivante illustre le fonctionnement d'un registre à décalage qui décale son contenu d'un cran vers la droite à chaque cycle d'horloge.
[[File:4-Bit PISO Shift Register Seq.gif|centre|vignette|upright=2|Registre à décalage.]]
On peut classer les registres selon le caractère de l'entrée et de la sortie, qui peut être parallèle (entrée de plusieurs bits) ou série (entrée d'un seul bit).
* Sur les registres simples, les entrées et sorties pour les données sont toujours parallèles. Pour un registre de N bits, il y a une entrée d'écriture de N bits et une sortie de N bits. C'est la raison pour laquelle ils sont appelés des '''registres à entrées et sorties parallèles'''.
* Sur les registres à '''entrée et sortie série''', on peut mettre à jour un bit à la fois, de même qu'on ne peut en récupérer qu'un à la fois. Ces registres servent essentiellement à mettre en attente des bits tout en gardant leur ordre : un bit envoyé en entrée ressortira sur la sortie après plusieurs commandes de mise à jour sur l'entrée Enable.
* Les registres à décalage à '''entrée série et sortie parallèle''' sont similaires aux précédents : on peut ajouter un nouveau bit en commandant l'entrée Enable et les anciens bits sont alors décalés d'un cran. Par contre, on peut récupérer (lire) tous les bits en une seule fois. Ils permettent notamment de reconstituer un nombre qui est envoyé bit par bit sur un fil (un bus série).
* Enfin, il reste les registres à '''entrée parallèle et sortie série'''. Ces registres sont utiles quand on veut transmettre un nombre sur un fil : on peut ainsi envoyer les bits un par un.
[[File:Td5bfig1.png|centre|vignette|upright=2|Classification des registres à décalage.]]
Pour résumer, on distingue quatre types de registres (à décalage ou non), qui portent les noms de PIPO, PISO, SIPO et SISO. Les noms peuvent sembler barbares, mais il y a une logique derrière ces termes.La lettre P est pour parallèle, la lettre S est pour série. La lettre I signifie ''Input'', ce qui veut dire entrée en anglais, la lettre O est pour ''Output'', la sortie en anglais.
{|class="wikitable"
|+ Classification des registres
|-
!
! Entrée parallèle
! Entrée série
|-
! Sortie parallèle
| PIPO (registre simple)
| SIPO
|-
! Sortie série
| PISO
| SISO
|}
==L'intérieur d'un registre à décalage==
Tous les registres sont conçus en plaçant plusieurs bascules les unes à la suite des autres, que ce soit pour les registres simples ou les registres à décalage. La seule différence tient dans la manière dont les bascules sont reliées. Toutes les bascules sont reliées à l'entrée d'horloge, l'entrée Enable, l'entrée Reset, ou aux autres entrées de commandes. Mais c'est une autre paire de manche pour les entrées/sorties de données.
Dans un registre simple, les bascules sont indépendantes et ne sont pas reliées entre elles.
[[File:Register.svg|centre|vignette|upright=3|Registre simple.]]
À l'inverse, dans les registres à décalage, il existe des connexions entre bascules. Plus précisément, les bascules sont reliées les unes à la suite des autres, elles forment une chaîne de bascules reliées deux à deux. Et les connexions entre bascules sont les mêmes que l'on parle d'un registre à décalage de type SIPO, PISO ou SISO.
[[File:Shift-Register.svg|centre|vignette|upright=3|Exemple de registre à décalage]]
Outre le fait que les bascules sont reliées de la même manière, les autres connexions sont les mêmes dans tous les registres. L'entrée d'horloge (non-représentée dans les schémas qui vont suivre) est envoyée à toutes les bascules. Même chose pour l'entrée Enable, qui est reliée aux entrées E de toutes les bascules. La différence entre ces registres tient dans les endroits où se trouvent les entrées et les sorties du registre.
{|
|+ Implémentation des registres avec des bascules.
|-
|[[File:Registre à entrée parallèle et sortie série.png|vignette|upright=2|Registre à entrée et sortie série.]]
|[[File:Registre à lecture et écriture parallèle.png|vignette|upright=2|Registre à entrée et sortie parallèle.]]
|-
|[[File:Registre à entrée et sortie série.png|vignette|upright=2|Registre à entrée série et sortie parallèle.]]
|[[File:Registre à entrée série et sortie parallèle.png|vignette|upright=2|Registre à entrée parallèle et sortie série.]]
|}
==Les compteurs basés sur des registres à décalage==
Les registres à décalage peuvent être utilisés pour implémenter des compteurs. Les compteurs en question peuvent être des compteurs classiques, qui comptent en binaire. De tels compteurs sont appelés des compteurs asynchrones, bien que ce soit un abus de langage. Une autre utilisation est celle des compteurs ''one hot'', qui ne comptent pas en binaire normal. Voyons ces deux utilisations dans ce qui suit.
===Les mal-nommés ''compteurs asynchrones''===
Les '''compteurs asynchrones''' n'utilisent pas de circuit incrémenteur. A la place, ils sont fabriqués à partir d'un registre à décalage. Ils utilisent moins de portes logiques que les compteurs synchrones. Et ils ne sont pas forcément plus difficiles à comprendre. Précisons cependant qu'ils sont très mal nommés, à savoir que ce sont bien des circuits synchrones.
Pour comprendre comment fonctionne un compteur asynchrone, il faut regarder la séquence des premiers entiers :
* 000 ;
* 001 ;
* 010 ;
* 011 ;
* 100 ;
* 101 ;
* 110 ;
* 111.
Il faut remarquer que le bit de poids faible s'inverse à chaque cycle d'horloge. Pour les colonnes suivantes, le bit s'inverse quand le bit de la colonne précédente passe de 1 à 0, lors d'un front descendant sur la colonne précédente. Maintenant que l'on sait cela, on peut créer un compteur avec des bascules T (elles inversent leur contenu à chaque cycle d'horloge). La première colonne inverse son contenu à chaque cycle, elle correspond donc à une bascule T reliée directement à l'horloge. Les autres colonnes utilisent des bascules T activées sur front descendant.
: Attention, la bascule la plus à gauche stocke le bit de poids faible, pas celui de poids fort. Cela sera pareil dans tous les schémas qui suivront.
[[File:AsyncCounter Alternativ 2.svg|centre|vignette|upright=2|Compteur asynchrone de 4 bits.]]
Le compteur précédent ne peut pas être réinitialisé, ce qui pose des problèmes pour implémenter des compteurs modulo. Pour cela, il faut que les bascules du compteur aient une entrée de réinitialisation ''Reset'', qui les force à se remettre à zéro. Il suffit alors de connecter ensemble les entrées ''Reset'' des bascules à l'entrée ''Reset'' du compteur.
[[File:AsyncCounter mod 8 with RST .svg|centre|vignette|upright=2|Compteur réinitialisable.]]
Implémenter un compteur modulo demande d'ajouter un comparateur qui détecte quand la valeur maximale est atteinte, afin de commander l'entrée de réinitialisation. Un tel circuit est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Modulo-5-Zähler.svg|centre|vignette|upright=2|Compteur modulo 10.]]
Il est aussi possible d'utiliser des bascules D à la place des bascules T simplifiées. En effet, une bascule T simplifiée est identique à une bascule D dont on boucle la sortie /Q sur l'entrée de données. Cette implémentation permet d'ailleurs de réinitialiser le compteur à une valeur non-nulle. Pour cela, l'entrée de chaque bascule D est précédée d'un multiplexeur, qui choisit entre le bit calculé par le compteur et celui présenté sur l'entrée de ré-initialisation. Quand l'entrée ''Reset'' est activée, les multiplexeurs connectent les bascules aux bits sur l'entrée de ré-initialisation. Dans le cas contraire, le compteur fonctionne normalement, les multiplexeurs connectant l'entrée de chaque bascule à sa sortie.
[[File:Compteur asynchrone, avec initialisation.jpg|centre|vignette|upright=2|Compteur asynchrone, avec initialisation.]]
Pour finir, la sortie de débordement n'est autre que la sortie de la dernière bascule, celle qui contient le bit de poids fort.
===Les compteurs ''one-hot''===
Les '''compteurs ''one-hot''''' sont appelés ainsi, car ils permettent de compter dans une représentation des nombres appelée la représentation ''one-hot''. Pour rappel, dans une telle représentation, un seul bit est à 1 pendant que les autres sont à 0. Les entiers sont codés de la manière suivante : le nombre N est encodé en mettant le énième bit à 1, avec la condition que l'on commence à compteur à partir de zéro. Il est important de remarquer que dans cette représentation, le zéro est n'est PAS codé en mettant tous les bits à 0, la valeur 0000...0000 est une valeur interdite. À la place, le zéro est codé en mettant le bit de poids faible à 1. Pour N bits, on peut encoder seulement N valeurs, dont le zéro.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! ''One-hot''
|-
| 0 || 000 || 00000001
|-
| 1 || 001 || 00000010
|-
| 2 || 010 || 00000100
|-
| 3 || 011 || 00001000
|-
| 4 || 100 || 00010000
|-
| 5 || 101 || 00100000
|-
| 6 || 110 || 01000000
|-
| 7 || 111 || 10000000
|}
Un compteur en représentation ''one-hot'' contient un nombre codé de cette manière, qui est incrémenté ou décrémenté si besoin. Pour donner un exemple, la séquence d'un compteur en anneau de 4 bits est :
* 0001 (0) ;
* 0010 (1) ;
* 0100 (2) ;
* 1000 (3) .
Un compteur ''one-hot'' basique est composé d'un registre à décalage dont on boucle la sortie sur son entrée. En faisant cela, on garantit que le registre revient à zéro lors d'un débordement, zéro étant codé avec un 1 dans le bit de poids faible. Au passage, si vous ne mettez que des 0 dans un compteur en anneau, il restera bloqué pour toujours : décaler une suite de 0 donnera la même suite de 0. Initialiser un compteur ''one-hot'' demande donc quelques subtilités qu'on détaillera plus bas.
[[File:Compteur en anneau de 4 bits.jpg|centre|vignette|upright=2|Compteur en anneau de 4 bits]]
Un compteur ''one-hot'' de N bits peut prendre N valeurs différentes, qui ont toutes un seul bit à 1. Pour 5 bits, la séquence est illustrée ci-dessous.
[[File:Lauflicht 5 LED.gif|centre|vignette|Compteur en anneau de 5 bits.]]
Faire des comparaisons avec ce type de compteur est très simple : le compteur contient la valeur N si le énième bit est à 1. Pas besoin d'utiliser de circuit comparateur, juste de lire un bit. Par contre, ce compteur n'est pas très économe en bascules. Imaginons que l'on veut un compteur qui compte jusqu'à une valeur N arbitraire : un compteur binaire normal utilisera environ <math>\log_2{(N)}</math> bascules, alors qu'un compteur ''one-hot'' demande N bascules. Mais si N est assez petit, l'économie de bascules est assez faible, alors que l'économie de circuits comparateurs/incrémenteurs l'est beaucoup plus.
Il y a peu d'applications qui utilisent des compteurs en anneau. Ils étaient autrefois utilisés dans les tous premiers ordinateurs, notamment ceux qui géraient une représentation des nombres spécifique appelée la '''''Bi-quinary coded decimal'''''. De nos jours, de tels compteurs sont utilisés dans les séquenceurs de processeurs, mais aussi dans les séquenceurs de certains périphériques, ou dans les circuits séquentiels simples qui se résument à des machines à états. Ils sont alors utilisés car très rapides, parfaitement adaptés au stockage de petites valeur, et surtout : ils n'ont pas besoin de circuit comparateur pour connaitre la valeur stockée dedans. Nous n'allons pas rentrer dans le détail de leurs utilisations, car nous en reparlerons dans la suite du cours.
==Les registres à décalage à rétroaction linéaire==
Les '''registres à décalage à rétroaction linéaire''' sont des registres à décalages un peu particuliers. Le terme anglais pour de tels registres est ''Linear Feedback Shift Register'', ce qui s’abrège en LFSR. Nous utiliserons cette abréviation dans ce qui suit pour simplifier grandement l'écriture.
Les LFSR sont appelés ainsi pour plusieurs raisons. Déjà, registre à décalage implique qu'ils sont fabriqués avec un registre à décalage. À rétroaction indique que l'on boucle la sortie sur l'entrée. Le terme combinaison linéaire demande quelques explications. L'idée est qu'entre la sortie et l'entrée, il y a un circuit combinatoire qui s'intercale entre la sortie et l'entrée, qui calcule ce qu'il faut mettre sur l'entrée, à partir du contenu du registre
Pour simplifier, cela veut dire qu'on multiplie chaque bit par 0 ou 1, avant d'additionner le tout. Dans ce calcul, on ne garde qu'un seul bit du résultat, vu que l'entrée du registre à décalage ne fait qu'un bit. Par simplicité, on ne garde que le bit de poids faible. Or, il s'avère que cela simplifie grandement les calculs, car cela permet de remplacer les additions par une simple opération XOR.
: <math>1 \times a_n \oplus ... + 1 \oplus a_3 + 1 \oplus a_1</math>
Le résultat est ce que l'on appelle un '''''LFSR de Fibonacci''''', ou encore un LFSR classique, qui celui qui colle le mieux avec la définition.
[[File:LFSR-F16.gif|centre|vignette|upright=2|Registre à décalage à rétroaction de Fibonnaci.]]
Les '''registres à décalages à rétroaction affine''' sont identique aux précédents à une différence près : le bit calculé est inversé avant d'être inséré dans le registre. Un tel circuit est donc composé de portes NXOR, comparé à son comparse linéaire, composé à partir de portes XOR. Petite remarque : si je prends un registre à rétroaction linéaire et un registre à rétroaction affine avec les mêmes coefficients sur les mêmes bits, le résultat du premier sera égal à l'inverse de l'autre.
Les '''LSFR de Gallois''' sont un peu l'inverse des LFSR vus juste avant. Au lieu d'utiliser un registre à décalage SIPO, ils utilisent un registre à décalage PISO. Pour faire la différence, nous appellerons ces derniers les LFSR PISO, et les premiers LFSR SIPO. Avec les LFSR PISO, on prend le bit sortant et on en déduit plusieurs bits à partir d'un circuit combinatoire, qui sont chacun insérés dans le registre à décalage à un endroit bien précis. Bien sûr, la fonction qui calcule des différents bits à partir du bit d'entrée conserve les mêmes propriétés que celle utilisée pour les LFSR : elle se calcule avec uniquement des portes XOR, ou NXOR pour leur variante affine.
Leur avantage est qu'ils sont plus rapides, car il n'y a qu'une seule porte logique entre la sortie et une entrée du registre à décalage, contre potentiellement plusieurs avec les LFSR SIPO. Notons que tout comme les LFSR qui ne peuvent pas mémoriser un 0, de tels registres à décalage à rétroaction ne peuvent pas avoir la valeur maximale stockable dans le registre. Cette valeur gèle le registre à cette valeur, dans le sens où le résultat au cycle suivant sera identique. Mais cela ne pose pas de problèmes pour l'initialisation du compteur.
[[File:LFSR-G16.svg|centre|vignette|upright=2|Registre à décalage à rétroaction de Galois.]]
Quelques propriétés sont communes à tous les LSFRs. Un LSFR est déterministe : pour le même résultat en entrée, il donnera toujours le même résultat en sortie. De plus, il ne peut contenir qu'un nombre fini de valeurs. Et ces deux propriétés sont source de comportements qu'on va détailler ci-dessous.
===L'initialisation d'un LSFR===
Les LSFR ne peuvent pas être initialisés à une valeur arbitraire, en raison de la présence d'une '''valeur interdite'''. Concrètement, une fois que le décompte arrive sur une valeur interdite, le compteur reste bloqué sur cette valeur. La valeur suivante, calculée par le compteur, est cette valeur elle-même.
Le premier cas est celui où le compteur peut être initialisé avec zéro sans que cela ne pose problème. Sur de tels LSFRs, la réinitialisation se fait comme pour n'importe quel registre/compteur. A savoir que les entrées de reset des bascules sont toutes connectées ensemble, au même signal de reset.
[[File:Johnson Counter 4bit.svg|centre|vignette|upright=2|Compteur de Johnson de 4 bits]]
Le second cas est celui des LFSR non-affine. Lors de la réinitialisation, il faut que toutes les bascules soient réinitialisées à 0, sauf une qui est mise à 1. La bascule en question doit disposer d'une entrée S (''Set'') qui met la bascule à 1 quand elle est activée. Cela garantit que le registre est réinitialisé avec un zéro codé en ''one-hot''.
[[File:Overbeck Counter 4bit.svg|centre|vignette|upright=2|Compteur en anneau de 4 bits]]
Une autre solution est de mettre un multiplexeur juste avant l'entrée du registre à décalage. Cette solution marché bien dans le sens où elle permet d'initialiser le registre avec une valeur arbitraire, qui est insérée dans le registre en plusieurs cycles. Pour les LFSR, le multiplexeur est connecté soit au bit calculé par les portes XOR, soit par une entrée servant uniquement de l'initialisation.
[[File:Initialisation d'un LFSR.jpg|centre|vignette|upright=2|Initialisation d'un LFSR]]
===La période d'un LSFR===
Si le compteur est initialisé correctement, il passera d'une valeur à l'autre et finira par repasser par une valeur qu'il aura déjà parcourue et son fonctionnement se reproduira à l'identique comparé à son passage antérieur, il bouclera. Il parcourt un nombre N de valeurs à chaque cycle, ce nombre étant appelé la '''période du compteur'''.
La période d'un LSFR dépend fortement de la fonction utilisée pour calculer le bit de sortie. Dans le meilleur des cas, le LSFR passera par toutes les valeurs que le registre peut prendre, sauf une : suivant le registre, le zéro ou sa valeur maximale sont interdits. Si un LSFR passe par zéro, il y reste bloqué définitivement. La raison à cela est simple : un XOR sur des zéros donnera toujours 0. Le même raisonnement peut être tenu pour les registres à rétroaction affine, sauf que cette fois-ci, c'est la valeur maximale stockable dans le registre qui est fautive.
Tout le chalenge consiste donc à trouver quels sont les LSFR dont la période est maximale : ceux dont la période vaut <math>2^n - 1</math>. Qu'on se rassure, quelle que soit la longueur du registre, il en existe au moins un : cela se prouve mathématiquement.
<noinclude>[[File:LFSR-F4.GIF|centre|vignette|upright=2|Exemple avec un registre à rétroaction linéaire de 4 bits.]]</noinclude>
==La génération de nombres pseudo-aléatoires==
Les LSFRs peuvent aussi être utilisés pour générer des nombres "aléatoires". Je dis aléatoires entre guillemets, car ils ne sont pas vraiment aléatoires, mais s'en rapprochent suffisamment pour être considérés comme tels. Pour mettre en avant cela, on parle aussi de nombres "pseudo-aléatoires". De nombreuses situations demandent de générer des nombres pseudo-aléatoire de manière matérielle. Cela peut servir pour sélectionner une ligne de cache à remplacer lors d'un défaut de cache, pour implémenter des circuits cryptographiques, pour calculer la durée d'émission sur un bus Ethernet à la suite d'une collision, et j'en passe.
Les méthodes que nous allons voir produisent un nombre pseudo-aléatoire un bit à la fois, à quelques exceptions près. Les circuits que nous allons voir fournissent un bit sur leur sortie et ce bit varie de manière assez aléatoire. Les bits en sortie du circuit sont accumulés dans un registre à décalage normal, pour former un nombre aléatoire. Nous appellerons ce registre : l'accumulateur.
===L'usage de registres à décalage à rétroaction===
[[File:Nonlinear-combo-generator.png|thumb|Nonlinear-combo-generator]]
Un LSFR seul ne fournit pas un aléatoire digne de ce nom, mais il est possible de combiner plusieurs LSFR pour obtenir une meilleure approximation de l'aléatoire. Avec cette technique, plusieurs registres à décalages à rétroaction sont reliés à un circuit combinatoire non-linéaire. Ce circuit prendra en entrée un (ou plusieurs) bit de chaque registre à décalage à rétroaction, et combinera ces bits pour fournir un bit de sortie.
[[File:A5-1 GSM cipher.svg|centre|thumb|upright=2|Exemple avec trois LSFR différents, de tailles différentes : le bit envoyé à l'accumulateur est un XOR du bit sortant des trois LSFR.]]
Pour rendre le tout encore plus aléatoire, il est possible de cadencer les LSFR à des fréquences différentes. Cette technique est utilisée dans les générateurs ''stop-and-go'', ''alternative step'', et à ''shrinking''.
* Le générateur ''alternative step'' utilise trois LSFR. Le premier commande un multiplexeur qui choisit la sortie parmi les deux restants.
* Le générateur ''stop-and-go'' utilise deux LSFR. Le premier est relié à l'entrée d'horloge du second et le bit de sortie du second est utilisé comme résultat. Une technique similaire était utilisée dans les processeurs VIA C3, pour l'implémentation de leurs instructions cryptographiques.
* Le ''shrinking generator'' utilise deux LSFR cadencés à des vitesses différentes. Si le bit de sortie du premier vaut 1, alors le bit de sortie du second est utilisé comme résultat. Par contre, si le bit de sortie du premier vaut 0, aucun bit n'est fourni en sortie, le bit de sortie du second registre est oublié.
===L'aléatoire généré par des ''timers'' ou des compteurs d'horloge===
Au-delà des LSFR, il est possible d'utiliser des compteurs pour générer du pseudo-aléatoire. Par exemple, une technique très simple utilise un simple ''timer''. Si on a besoin d'un nombre pseudo-aléatoire, il suffit de lire le ''timer'' et d'utiliser le nombre lu comme nombre pseudo-aléatoire. Si le délai entre deux demandes est irrégulier, le résultat semblera aléatoire. Mais il s'agit là d'une technique assez peu fiable dans le monde réel et seules quelques applications bien spécifiques se satisfont de cette méthode.
Une solution un peu plus fiable utilise ce qu'on appelle la '''dérive de l'horloge'''. Il faut savoir qu'un signal d'horloge n'est jamais vraiment très précis. Une horloge censée tourner à 1 Ghz ne tournera pas en permanence à 1Ghz exactement, mais verra sa fréquence varier de quelques Hz ou Khz de manière irrégulière. Ces variations peuvent venir de variations aléatoires de température, des variations de tension, des perturbations électromagnétiques, ou à des phénomènes assez compliqués qui peuvent se produire dans tout circuit électrique (comme le ''shot noise'').
L'idée la plus simple utilise deux horloges : une horloge lente et une horloge rapide, dont la fréquence est un multiple de l'autre. Par exemple, on peut choisir une fréquence de 1 Mhz et une autre de 100 Hz : la fréquence la plus grande est égale à 10000 fois l'autre. La dérive d'horloge fera son œuvre, les deux horloges seront très légèrement désynchronisées en permanence, et cette désynchronisation peut être utilisée pour produire des nombres aléatoires. Par exemple, on peut compter le nombre de cycles d'horloge produit par l'horloge rapide durant une période de l'horloge lente. Si ce nombre est pair, on produit un bit aléatoire qui vaut 1 , il vaut 0 si ce nombre est pair. Pour information, c'est exactement cette technique qui était utilisée dans l'''Intel 82802 Firmware Hub''.
Il existe d'autres solutions matérielles qui utilisent le bruit thermique. Tous les circuits électroniques de l'univers sont soumis à de microscopiques variations de température, dues à l'agitation thermique des atomes. Plus la température est élevée, plus les atomes qui composent les fils métalliques des circuits s'agitent. Vu que les particules d'un métal contiennent des charges électriques, ces vibrations font naître des variations de tensions assez infimes. Il suffit d'amplifier ces variations pour obtenir un résultat capable de représenter un zéro ou un 1. Ce principe a été utilisé sur des anciens processeurs Intel qui géraient l'instruction RDRAND, une instruction qui produisait un nombre aléatoire.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les compteurs et timers
| prevText=Les compteurs et timers
| next=Les circuits de décalage et de rotation
| nextText=Les circuits de décalage et de rotation
}}
</noinclude>
n7tpb6gpq2ksews24bvpm5n8p4x3gvz
768620
768619
2026-06-25T15:22:06Z
Mewtow
31375
Mewtow a déplacé la page [[Fonctionnement d'un ordinateur/Les registres à décalage à rétroaction linéaire]] vers [[Fonctionnement d'un ordinateur/Les registres à décalage et les LSFR]]
768619
wikitext
text/x-wiki
Les '''registres à décalage''' sont des registres dont le contenu est décalé d'un cran vers la gauche ou la droite sur commande. Nous aurons à les réutiliser plus tard dans ce cours, notamment dans la section sur les circuits de génération de nombres aléatoires, ou dans certains circuits liés au cache. Les registres à décalage sont presque tous synchrones et ce chapitre ne parlera que ce ces derniers. L'animation suivante illustre le fonctionnement d'un registre à décalage qui décale son contenu d'un cran vers la droite à chaque cycle d'horloge.
[[File:4-Bit PISO Shift Register Seq.gif|centre|vignette|upright=2|Registre à décalage.]]
On peut classer les registres selon le caractère de l'entrée et de la sortie, qui peut être parallèle (entrée de plusieurs bits) ou série (entrée d'un seul bit).
* Sur les registres simples, les entrées et sorties pour les données sont toujours parallèles. Pour un registre de N bits, il y a une entrée d'écriture de N bits et une sortie de N bits. C'est la raison pour laquelle ils sont appelés des '''registres à entrées et sorties parallèles'''.
* Sur les registres à '''entrée et sortie série''', on peut mettre à jour un bit à la fois, de même qu'on ne peut en récupérer qu'un à la fois. Ces registres servent essentiellement à mettre en attente des bits tout en gardant leur ordre : un bit envoyé en entrée ressortira sur la sortie après plusieurs commandes de mise à jour sur l'entrée Enable.
* Les registres à décalage à '''entrée série et sortie parallèle''' sont similaires aux précédents : on peut ajouter un nouveau bit en commandant l'entrée Enable et les anciens bits sont alors décalés d'un cran. Par contre, on peut récupérer (lire) tous les bits en une seule fois. Ils permettent notamment de reconstituer un nombre qui est envoyé bit par bit sur un fil (un bus série).
* Enfin, il reste les registres à '''entrée parallèle et sortie série'''. Ces registres sont utiles quand on veut transmettre un nombre sur un fil : on peut ainsi envoyer les bits un par un.
[[File:Td5bfig1.png|centre|vignette|upright=2|Classification des registres à décalage.]]
Pour résumer, on distingue quatre types de registres (à décalage ou non), qui portent les noms de PIPO, PISO, SIPO et SISO. Les noms peuvent sembler barbares, mais il y a une logique derrière ces termes.La lettre P est pour parallèle, la lettre S est pour série. La lettre I signifie ''Input'', ce qui veut dire entrée en anglais, la lettre O est pour ''Output'', la sortie en anglais.
{|class="wikitable"
|+ Classification des registres
|-
!
! Entrée parallèle
! Entrée série
|-
! Sortie parallèle
| PIPO (registre simple)
| SIPO
|-
! Sortie série
| PISO
| SISO
|}
==L'intérieur d'un registre à décalage==
Tous les registres sont conçus en plaçant plusieurs bascules les unes à la suite des autres, que ce soit pour les registres simples ou les registres à décalage. La seule différence tient dans la manière dont les bascules sont reliées. Toutes les bascules sont reliées à l'entrée d'horloge, l'entrée Enable, l'entrée Reset, ou aux autres entrées de commandes. Mais c'est une autre paire de manche pour les entrées/sorties de données.
Dans un registre simple, les bascules sont indépendantes et ne sont pas reliées entre elles.
[[File:Register.svg|centre|vignette|upright=3|Registre simple.]]
À l'inverse, dans les registres à décalage, il existe des connexions entre bascules. Plus précisément, les bascules sont reliées les unes à la suite des autres, elles forment une chaîne de bascules reliées deux à deux. Et les connexions entre bascules sont les mêmes que l'on parle d'un registre à décalage de type SIPO, PISO ou SISO.
[[File:Shift-Register.svg|centre|vignette|upright=3|Exemple de registre à décalage]]
Outre le fait que les bascules sont reliées de la même manière, les autres connexions sont les mêmes dans tous les registres. L'entrée d'horloge (non-représentée dans les schémas qui vont suivre) est envoyée à toutes les bascules. Même chose pour l'entrée Enable, qui est reliée aux entrées E de toutes les bascules. La différence entre ces registres tient dans les endroits où se trouvent les entrées et les sorties du registre.
{|
|+ Implémentation des registres avec des bascules.
|-
|[[File:Registre à entrée parallèle et sortie série.png|vignette|upright=2|Registre à entrée et sortie série.]]
|[[File:Registre à lecture et écriture parallèle.png|vignette|upright=2|Registre à entrée et sortie parallèle.]]
|-
|[[File:Registre à entrée et sortie série.png|vignette|upright=2|Registre à entrée série et sortie parallèle.]]
|[[File:Registre à entrée série et sortie parallèle.png|vignette|upright=2|Registre à entrée parallèle et sortie série.]]
|}
==Les compteurs basés sur des registres à décalage==
Les registres à décalage peuvent être utilisés pour implémenter des compteurs. Les compteurs en question peuvent être des compteurs classiques, qui comptent en binaire. De tels compteurs sont appelés des compteurs asynchrones, bien que ce soit un abus de langage. Une autre utilisation est celle des compteurs ''one hot'', qui ne comptent pas en binaire normal. Voyons ces deux utilisations dans ce qui suit.
===Les mal-nommés ''compteurs asynchrones''===
Les '''compteurs asynchrones''' n'utilisent pas de circuit incrémenteur. A la place, ils sont fabriqués à partir d'un registre à décalage. Ils utilisent moins de portes logiques que les compteurs synchrones. Et ils ne sont pas forcément plus difficiles à comprendre. Précisons cependant qu'ils sont très mal nommés, à savoir que ce sont bien des circuits synchrones.
Pour comprendre comment fonctionne un compteur asynchrone, il faut regarder la séquence des premiers entiers :
* 000 ;
* 001 ;
* 010 ;
* 011 ;
* 100 ;
* 101 ;
* 110 ;
* 111.
Il faut remarquer que le bit de poids faible s'inverse à chaque cycle d'horloge. Pour les colonnes suivantes, le bit s'inverse quand le bit de la colonne précédente passe de 1 à 0, lors d'un front descendant sur la colonne précédente. Maintenant que l'on sait cela, on peut créer un compteur avec des bascules T (elles inversent leur contenu à chaque cycle d'horloge). La première colonne inverse son contenu à chaque cycle, elle correspond donc à une bascule T reliée directement à l'horloge. Les autres colonnes utilisent des bascules T activées sur front descendant.
: Attention, la bascule la plus à gauche stocke le bit de poids faible, pas celui de poids fort. Cela sera pareil dans tous les schémas qui suivront.
[[File:AsyncCounter Alternativ 2.svg|centre|vignette|upright=2|Compteur asynchrone de 4 bits.]]
Le compteur précédent ne peut pas être réinitialisé, ce qui pose des problèmes pour implémenter des compteurs modulo. Pour cela, il faut que les bascules du compteur aient une entrée de réinitialisation ''Reset'', qui les force à se remettre à zéro. Il suffit alors de connecter ensemble les entrées ''Reset'' des bascules à l'entrée ''Reset'' du compteur.
[[File:AsyncCounter mod 8 with RST .svg|centre|vignette|upright=2|Compteur réinitialisable.]]
Implémenter un compteur modulo demande d'ajouter un comparateur qui détecte quand la valeur maximale est atteinte, afin de commander l'entrée de réinitialisation. Un tel circuit est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Modulo-5-Zähler.svg|centre|vignette|upright=2|Compteur modulo 10.]]
Il est aussi possible d'utiliser des bascules D à la place des bascules T simplifiées. En effet, une bascule T simplifiée est identique à une bascule D dont on boucle la sortie /Q sur l'entrée de données. Cette implémentation permet d'ailleurs de réinitialiser le compteur à une valeur non-nulle. Pour cela, l'entrée de chaque bascule D est précédée d'un multiplexeur, qui choisit entre le bit calculé par le compteur et celui présenté sur l'entrée de ré-initialisation. Quand l'entrée ''Reset'' est activée, les multiplexeurs connectent les bascules aux bits sur l'entrée de ré-initialisation. Dans le cas contraire, le compteur fonctionne normalement, les multiplexeurs connectant l'entrée de chaque bascule à sa sortie.
[[File:Compteur asynchrone, avec initialisation.jpg|centre|vignette|upright=2|Compteur asynchrone, avec initialisation.]]
Pour finir, la sortie de débordement n'est autre que la sortie de la dernière bascule, celle qui contient le bit de poids fort.
===Les compteurs ''one-hot''===
Les '''compteurs ''one-hot''''' sont appelés ainsi, car ils permettent de compter dans une représentation des nombres appelée la représentation ''one-hot''. Pour rappel, dans une telle représentation, un seul bit est à 1 pendant que les autres sont à 0. Les entiers sont codés de la manière suivante : le nombre N est encodé en mettant le énième bit à 1, avec la condition que l'on commence à compteur à partir de zéro. Il est important de remarquer que dans cette représentation, le zéro est n'est PAS codé en mettant tous les bits à 0, la valeur 0000...0000 est une valeur interdite. À la place, le zéro est codé en mettant le bit de poids faible à 1. Pour N bits, on peut encoder seulement N valeurs, dont le zéro.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! ''One-hot''
|-
| 0 || 000 || 00000001
|-
| 1 || 001 || 00000010
|-
| 2 || 010 || 00000100
|-
| 3 || 011 || 00001000
|-
| 4 || 100 || 00010000
|-
| 5 || 101 || 00100000
|-
| 6 || 110 || 01000000
|-
| 7 || 111 || 10000000
|}
Un compteur en représentation ''one-hot'' contient un nombre codé de cette manière, qui est incrémenté ou décrémenté si besoin. Pour donner un exemple, la séquence d'un compteur en anneau de 4 bits est :
* 0001 (0) ;
* 0010 (1) ;
* 0100 (2) ;
* 1000 (3) .
Un compteur ''one-hot'' basique est composé d'un registre à décalage dont on boucle la sortie sur son entrée. En faisant cela, on garantit que le registre revient à zéro lors d'un débordement, zéro étant codé avec un 1 dans le bit de poids faible. Au passage, si vous ne mettez que des 0 dans un compteur en anneau, il restera bloqué pour toujours : décaler une suite de 0 donnera la même suite de 0. Initialiser un compteur ''one-hot'' demande donc quelques subtilités qu'on détaillera plus bas.
[[File:Compteur en anneau de 4 bits.jpg|centre|vignette|upright=2|Compteur en anneau de 4 bits]]
Un compteur ''one-hot'' de N bits peut prendre N valeurs différentes, qui ont toutes un seul bit à 1. Pour 5 bits, la séquence est illustrée ci-dessous.
[[File:Lauflicht 5 LED.gif|centre|vignette|Compteur en anneau de 5 bits.]]
Faire des comparaisons avec ce type de compteur est très simple : le compteur contient la valeur N si le énième bit est à 1. Pas besoin d'utiliser de circuit comparateur, juste de lire un bit. Par contre, ce compteur n'est pas très économe en bascules. Imaginons que l'on veut un compteur qui compte jusqu'à une valeur N arbitraire : un compteur binaire normal utilisera environ <math>\log_2{(N)}</math> bascules, alors qu'un compteur ''one-hot'' demande N bascules. Mais si N est assez petit, l'économie de bascules est assez faible, alors que l'économie de circuits comparateurs/incrémenteurs l'est beaucoup plus.
Il y a peu d'applications qui utilisent des compteurs en anneau. Ils étaient autrefois utilisés dans les tous premiers ordinateurs, notamment ceux qui géraient une représentation des nombres spécifique appelée la '''''Bi-quinary coded decimal'''''. De nos jours, de tels compteurs sont utilisés dans les séquenceurs de processeurs, mais aussi dans les séquenceurs de certains périphériques, ou dans les circuits séquentiels simples qui se résument à des machines à états. Ils sont alors utilisés car très rapides, parfaitement adaptés au stockage de petites valeur, et surtout : ils n'ont pas besoin de circuit comparateur pour connaitre la valeur stockée dedans. Nous n'allons pas rentrer dans le détail de leurs utilisations, car nous en reparlerons dans la suite du cours.
==Les registres à décalage à rétroaction linéaire==
Les '''registres à décalage à rétroaction linéaire''' sont des registres à décalages un peu particuliers. Le terme anglais pour de tels registres est ''Linear Feedback Shift Register'', ce qui s’abrège en LFSR. Nous utiliserons cette abréviation dans ce qui suit pour simplifier grandement l'écriture.
Les LFSR sont appelés ainsi pour plusieurs raisons. Déjà, registre à décalage implique qu'ils sont fabriqués avec un registre à décalage. À rétroaction indique que l'on boucle la sortie sur l'entrée. Le terme combinaison linéaire demande quelques explications. L'idée est qu'entre la sortie et l'entrée, il y a un circuit combinatoire qui s'intercale entre la sortie et l'entrée, qui calcule ce qu'il faut mettre sur l'entrée, à partir du contenu du registre
Pour simplifier, cela veut dire qu'on multiplie chaque bit par 0 ou 1, avant d'additionner le tout. Dans ce calcul, on ne garde qu'un seul bit du résultat, vu que l'entrée du registre à décalage ne fait qu'un bit. Par simplicité, on ne garde que le bit de poids faible. Or, il s'avère que cela simplifie grandement les calculs, car cela permet de remplacer les additions par une simple opération XOR.
: <math>1 \times a_n \oplus ... + 1 \oplus a_3 + 1 \oplus a_1</math>
Le résultat est ce que l'on appelle un '''''LFSR de Fibonacci''''', ou encore un LFSR classique, qui celui qui colle le mieux avec la définition.
[[File:LFSR-F16.gif|centre|vignette|upright=2|Registre à décalage à rétroaction de Fibonnaci.]]
Les '''registres à décalages à rétroaction affine''' sont identique aux précédents à une différence près : le bit calculé est inversé avant d'être inséré dans le registre. Un tel circuit est donc composé de portes NXOR, comparé à son comparse linéaire, composé à partir de portes XOR. Petite remarque : si je prends un registre à rétroaction linéaire et un registre à rétroaction affine avec les mêmes coefficients sur les mêmes bits, le résultat du premier sera égal à l'inverse de l'autre.
Les '''LSFR de Gallois''' sont un peu l'inverse des LFSR vus juste avant. Au lieu d'utiliser un registre à décalage SIPO, ils utilisent un registre à décalage PISO. Pour faire la différence, nous appellerons ces derniers les LFSR PISO, et les premiers LFSR SIPO. Avec les LFSR PISO, on prend le bit sortant et on en déduit plusieurs bits à partir d'un circuit combinatoire, qui sont chacun insérés dans le registre à décalage à un endroit bien précis. Bien sûr, la fonction qui calcule des différents bits à partir du bit d'entrée conserve les mêmes propriétés que celle utilisée pour les LFSR : elle se calcule avec uniquement des portes XOR, ou NXOR pour leur variante affine.
Leur avantage est qu'ils sont plus rapides, car il n'y a qu'une seule porte logique entre la sortie et une entrée du registre à décalage, contre potentiellement plusieurs avec les LFSR SIPO. Notons que tout comme les LFSR qui ne peuvent pas mémoriser un 0, de tels registres à décalage à rétroaction ne peuvent pas avoir la valeur maximale stockable dans le registre. Cette valeur gèle le registre à cette valeur, dans le sens où le résultat au cycle suivant sera identique. Mais cela ne pose pas de problèmes pour l'initialisation du compteur.
[[File:LFSR-G16.svg|centre|vignette|upright=2|Registre à décalage à rétroaction de Galois.]]
Quelques propriétés sont communes à tous les LSFRs. Un LSFR est déterministe : pour le même résultat en entrée, il donnera toujours le même résultat en sortie. De plus, il ne peut contenir qu'un nombre fini de valeurs. Et ces deux propriétés sont source de comportements qu'on va détailler ci-dessous.
===L'initialisation d'un LSFR===
Les LSFR ne peuvent pas être initialisés à une valeur arbitraire, en raison de la présence d'une '''valeur interdite'''. Concrètement, une fois que le décompte arrive sur une valeur interdite, le compteur reste bloqué sur cette valeur. La valeur suivante, calculée par le compteur, est cette valeur elle-même.
Le premier cas est celui où le compteur peut être initialisé avec zéro sans que cela ne pose problème. Sur de tels LSFRs, la réinitialisation se fait comme pour n'importe quel registre/compteur. A savoir que les entrées de reset des bascules sont toutes connectées ensemble, au même signal de reset.
[[File:Johnson Counter 4bit.svg|centre|vignette|upright=2|Compteur de Johnson de 4 bits]]
Le second cas est celui des LFSR non-affine. Lors de la réinitialisation, il faut que toutes les bascules soient réinitialisées à 0, sauf une qui est mise à 1. La bascule en question doit disposer d'une entrée S (''Set'') qui met la bascule à 1 quand elle est activée. Cela garantit que le registre est réinitialisé avec un zéro codé en ''one-hot''.
[[File:Overbeck Counter 4bit.svg|centre|vignette|upright=2|Compteur en anneau de 4 bits]]
Une autre solution est de mettre un multiplexeur juste avant l'entrée du registre à décalage. Cette solution marché bien dans le sens où elle permet d'initialiser le registre avec une valeur arbitraire, qui est insérée dans le registre en plusieurs cycles. Pour les LFSR, le multiplexeur est connecté soit au bit calculé par les portes XOR, soit par une entrée servant uniquement de l'initialisation.
[[File:Initialisation d'un LFSR.jpg|centre|vignette|upright=2|Initialisation d'un LFSR]]
===La période d'un LSFR===
Si le compteur est initialisé correctement, il passera d'une valeur à l'autre et finira par repasser par une valeur qu'il aura déjà parcourue et son fonctionnement se reproduira à l'identique comparé à son passage antérieur, il bouclera. Il parcourt un nombre N de valeurs à chaque cycle, ce nombre étant appelé la '''période du compteur'''.
La période d'un LSFR dépend fortement de la fonction utilisée pour calculer le bit de sortie. Dans le meilleur des cas, le LSFR passera par toutes les valeurs que le registre peut prendre, sauf une : suivant le registre, le zéro ou sa valeur maximale sont interdits. Si un LSFR passe par zéro, il y reste bloqué définitivement. La raison à cela est simple : un XOR sur des zéros donnera toujours 0. Le même raisonnement peut être tenu pour les registres à rétroaction affine, sauf que cette fois-ci, c'est la valeur maximale stockable dans le registre qui est fautive.
Tout le chalenge consiste donc à trouver quels sont les LSFR dont la période est maximale : ceux dont la période vaut <math>2^n - 1</math>. Qu'on se rassure, quelle que soit la longueur du registre, il en existe au moins un : cela se prouve mathématiquement.
<noinclude>[[File:LFSR-F4.GIF|centre|vignette|upright=2|Exemple avec un registre à rétroaction linéaire de 4 bits.]]</noinclude>
==La génération de nombres pseudo-aléatoires==
Les LSFRs peuvent aussi être utilisés pour générer des nombres "aléatoires". Je dis aléatoires entre guillemets, car ils ne sont pas vraiment aléatoires, mais s'en rapprochent suffisamment pour être considérés comme tels. Pour mettre en avant cela, on parle aussi de nombres "pseudo-aléatoires". De nombreuses situations demandent de générer des nombres pseudo-aléatoire de manière matérielle. Cela peut servir pour sélectionner une ligne de cache à remplacer lors d'un défaut de cache, pour implémenter des circuits cryptographiques, pour calculer la durée d'émission sur un bus Ethernet à la suite d'une collision, et j'en passe.
Les méthodes que nous allons voir produisent un nombre pseudo-aléatoire un bit à la fois, à quelques exceptions près. Les circuits que nous allons voir fournissent un bit sur leur sortie et ce bit varie de manière assez aléatoire. Les bits en sortie du circuit sont accumulés dans un registre à décalage normal, pour former un nombre aléatoire. Nous appellerons ce registre : l'accumulateur.
===L'usage de registres à décalage à rétroaction===
[[File:Nonlinear-combo-generator.png|thumb|Nonlinear-combo-generator]]
Un LSFR seul ne fournit pas un aléatoire digne de ce nom, mais il est possible de combiner plusieurs LSFR pour obtenir une meilleure approximation de l'aléatoire. Avec cette technique, plusieurs registres à décalages à rétroaction sont reliés à un circuit combinatoire non-linéaire. Ce circuit prendra en entrée un (ou plusieurs) bit de chaque registre à décalage à rétroaction, et combinera ces bits pour fournir un bit de sortie.
[[File:A5-1 GSM cipher.svg|centre|thumb|upright=2|Exemple avec trois LSFR différents, de tailles différentes : le bit envoyé à l'accumulateur est un XOR du bit sortant des trois LSFR.]]
Pour rendre le tout encore plus aléatoire, il est possible de cadencer les LSFR à des fréquences différentes. Cette technique est utilisée dans les générateurs ''stop-and-go'', ''alternative step'', et à ''shrinking''.
* Le générateur ''alternative step'' utilise trois LSFR. Le premier commande un multiplexeur qui choisit la sortie parmi les deux restants.
* Le générateur ''stop-and-go'' utilise deux LSFR. Le premier est relié à l'entrée d'horloge du second et le bit de sortie du second est utilisé comme résultat. Une technique similaire était utilisée dans les processeurs VIA C3, pour l'implémentation de leurs instructions cryptographiques.
* Le ''shrinking generator'' utilise deux LSFR cadencés à des vitesses différentes. Si le bit de sortie du premier vaut 1, alors le bit de sortie du second est utilisé comme résultat. Par contre, si le bit de sortie du premier vaut 0, aucun bit n'est fourni en sortie, le bit de sortie du second registre est oublié.
===L'aléatoire généré par des ''timers'' ou des compteurs d'horloge===
Au-delà des LSFR, il est possible d'utiliser des compteurs pour générer du pseudo-aléatoire. Par exemple, une technique très simple utilise un simple ''timer''. Si on a besoin d'un nombre pseudo-aléatoire, il suffit de lire le ''timer'' et d'utiliser le nombre lu comme nombre pseudo-aléatoire. Si le délai entre deux demandes est irrégulier, le résultat semblera aléatoire. Mais il s'agit là d'une technique assez peu fiable dans le monde réel et seules quelques applications bien spécifiques se satisfont de cette méthode.
Une solution un peu plus fiable utilise ce qu'on appelle la '''dérive de l'horloge'''. Il faut savoir qu'un signal d'horloge n'est jamais vraiment très précis. Une horloge censée tourner à 1 Ghz ne tournera pas en permanence à 1Ghz exactement, mais verra sa fréquence varier de quelques Hz ou Khz de manière irrégulière. Ces variations peuvent venir de variations aléatoires de température, des variations de tension, des perturbations électromagnétiques, ou à des phénomènes assez compliqués qui peuvent se produire dans tout circuit électrique (comme le ''shot noise'').
L'idée la plus simple utilise deux horloges : une horloge lente et une horloge rapide, dont la fréquence est un multiple de l'autre. Par exemple, on peut choisir une fréquence de 1 Mhz et une autre de 100 Hz : la fréquence la plus grande est égale à 10000 fois l'autre. La dérive d'horloge fera son œuvre, les deux horloges seront très légèrement désynchronisées en permanence, et cette désynchronisation peut être utilisée pour produire des nombres aléatoires. Par exemple, on peut compter le nombre de cycles d'horloge produit par l'horloge rapide durant une période de l'horloge lente. Si ce nombre est pair, on produit un bit aléatoire qui vaut 1 , il vaut 0 si ce nombre est pair. Pour information, c'est exactement cette technique qui était utilisée dans l'''Intel 82802 Firmware Hub''.
Il existe d'autres solutions matérielles qui utilisent le bruit thermique. Tous les circuits électroniques de l'univers sont soumis à de microscopiques variations de température, dues à l'agitation thermique des atomes. Plus la température est élevée, plus les atomes qui composent les fils métalliques des circuits s'agitent. Vu que les particules d'un métal contiennent des charges électriques, ces vibrations font naître des variations de tensions assez infimes. Il suffit d'amplifier ces variations pour obtenir un résultat capable de représenter un zéro ou un 1. Ce principe a été utilisé sur des anciens processeurs Intel qui géraient l'instruction RDRAND, une instruction qui produisait un nombre aléatoire.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les compteurs et timers
| prevText=Les compteurs et timers
| next=Les circuits de décalage et de rotation
| nextText=Les circuits de décalage et de rotation
}}
</noinclude>
n7tpb6gpq2ksews24bvpm5n8p4x3gvz
768628
768620
2026-06-25T15:25:28Z
Mewtow
31375
768628
wikitext
text/x-wiki
Les '''registres à décalage''' sont des registres dont le contenu est décalé d'un cran vers la gauche ou la droite sur commande. Il seront beaucoup utilisés dans ce cours, bien que moins que les registres normaux. Les registres à décalage sont presque tous synchrones et ce chapitre ne parlera que ce ces derniers. L'animation suivante illustre le fonctionnement d'un registre à décalage qui décale son contenu d'un cran vers la droite à chaque cycle d'horloge.
[[File:4-Bit PISO Shift Register Seq.gif|centre|vignette|upright=2|Registre à décalage.]]
On peut classer les registres selon le caractère de l'entrée et de la sortie, qui peut être parallèle (entrée de plusieurs bits) ou série (entrée d'un seul bit).
* Sur les registres simples, les entrées et sorties pour les données sont toujours parallèles. Pour un registre de N bits, il y a une entrée d'écriture de N bits et une sortie de N bits. C'est la raison pour laquelle ils sont appelés des '''registres à entrées et sorties parallèles'''.
* Sur les registres à '''entrée et sortie série''', on peut mettre à jour un bit à la fois, de même qu'on ne peut en récupérer qu'un à la fois. Ces registres servent essentiellement à mettre en attente des bits tout en gardant leur ordre : un bit envoyé en entrée ressortira sur la sortie après plusieurs commandes de mise à jour sur l'entrée Enable.
* Les registres à décalage à '''entrée série et sortie parallèle''' sont similaires aux précédents : on peut ajouter un nouveau bit en commandant l'entrée Enable et les anciens bits sont alors décalés d'un cran. Par contre, on peut récupérer (lire) tous les bits en une seule fois. Ils permettent notamment de reconstituer un nombre qui est envoyé bit par bit sur un fil (un bus série).
* Enfin, il reste les registres à '''entrée parallèle et sortie série'''. Ces registres sont utiles quand on veut transmettre un nombre sur un fil : on peut ainsi envoyer les bits un par un.
[[File:Td5bfig1.png|centre|vignette|upright=2|Classification des registres à décalage.]]
Pour résumer, on distingue quatre types de registres (à décalage ou non), qui portent les noms de PIPO, PISO, SIPO et SISO. Les noms peuvent sembler barbares, mais il y a une logique derrière ces termes.La lettre P est pour parallèle, la lettre S est pour série. La lettre I signifie ''Input'', ce qui veut dire entrée en anglais, la lettre O est pour ''Output'', la sortie en anglais.
{|class="wikitable"
|+ Classification des registres
|-
!
! Entrée parallèle
! Entrée série
|-
! Sortie parallèle
| PIPO (registre simple)
| SIPO
|-
! Sortie série
| PISO
| SISO
|}
==L'intérieur d'un registre à décalage==
Tous les registres sont conçus en plaçant plusieurs bascules les unes à la suite des autres, que ce soit pour les registres simples ou les registres à décalage. La seule différence tient dans la manière dont les bascules sont reliées. Toutes les bascules sont reliées à l'entrée d'horloge, l'entrée Enable, l'entrée Reset, ou aux autres entrées de commandes. Mais c'est une autre paire de manche pour les entrées/sorties de données.
Dans un registre simple, les bascules sont indépendantes et ne sont pas reliées entre elles.
[[File:Register.svg|centre|vignette|upright=3|Registre simple.]]
À l'inverse, dans les registres à décalage, il existe des connexions entre bascules. Plus précisément, les bascules sont reliées les unes à la suite des autres, elles forment une chaîne de bascules reliées deux à deux. Et les connexions entre bascules sont les mêmes que l'on parle d'un registre à décalage de type SIPO, PISO ou SISO.
[[File:Shift-Register.svg|centre|vignette|upright=3|Exemple de registre à décalage]]
Outre le fait que les bascules sont reliées de la même manière, les autres connexions sont les mêmes dans tous les registres. L'entrée d'horloge (non-représentée dans les schémas qui vont suivre) est envoyée à toutes les bascules. Même chose pour l'entrée Enable, qui est reliée aux entrées E de toutes les bascules. La différence entre ces registres tient dans les endroits où se trouvent les entrées et les sorties du registre.
{|
|+ Implémentation des registres avec des bascules.
|-
|[[File:Registre à entrée parallèle et sortie série.png|vignette|upright=2|Registre à entrée et sortie série.]]
|[[File:Registre à lecture et écriture parallèle.png|vignette|upright=2|Registre à entrée et sortie parallèle.]]
|-
|[[File:Registre à entrée et sortie série.png|vignette|upright=2|Registre à entrée série et sortie parallèle.]]
|[[File:Registre à entrée série et sortie parallèle.png|vignette|upright=2|Registre à entrée parallèle et sortie série.]]
|}
==Les compteurs basés sur des registres à décalage==
Les registres à décalage peuvent être utilisés pour implémenter des compteurs. Les compteurs en question peuvent être des compteurs classiques, qui comptent en binaire. De tels compteurs sont appelés des compteurs asynchrones, bien que ce soit un abus de langage. Une autre utilisation est celle des compteurs ''one hot'', qui ne comptent pas en binaire normal. Voyons ces deux utilisations dans ce qui suit.
===Les mal-nommés ''compteurs asynchrones''===
Les '''compteurs asynchrones''' n'utilisent pas de circuit incrémenteur. A la place, ils sont fabriqués à partir d'un registre à décalage. Ils utilisent moins de portes logiques que les compteurs synchrones. Et ils ne sont pas forcément plus difficiles à comprendre. Précisons cependant qu'ils sont très mal nommés, à savoir que ce sont bien des circuits synchrones.
Pour comprendre comment fonctionne un compteur asynchrone, il faut regarder la séquence des premiers entiers :
* 000 ;
* 001 ;
* 010 ;
* 011 ;
* 100 ;
* 101 ;
* 110 ;
* 111.
Il faut remarquer que le bit de poids faible s'inverse à chaque cycle d'horloge. Pour les colonnes suivantes, le bit s'inverse quand le bit de la colonne précédente passe de 1 à 0, lors d'un front descendant sur la colonne précédente. Maintenant que l'on sait cela, on peut créer un compteur avec des bascules T (elles inversent leur contenu à chaque cycle d'horloge). La première colonne inverse son contenu à chaque cycle, elle correspond donc à une bascule T reliée directement à l'horloge. Les autres colonnes utilisent des bascules T activées sur front descendant.
: Attention, la bascule la plus à gauche stocke le bit de poids faible, pas celui de poids fort. Cela sera pareil dans tous les schémas qui suivront.
[[File:AsyncCounter Alternativ 2.svg|centre|vignette|upright=2|Compteur asynchrone de 4 bits.]]
Le compteur précédent ne peut pas être réinitialisé, ce qui pose des problèmes pour implémenter des compteurs modulo. Pour cela, il faut que les bascules du compteur aient une entrée de réinitialisation ''Reset'', qui les force à se remettre à zéro. Il suffit alors de connecter ensemble les entrées ''Reset'' des bascules à l'entrée ''Reset'' du compteur.
[[File:AsyncCounter mod 8 with RST .svg|centre|vignette|upright=2|Compteur réinitialisable.]]
Implémenter un compteur modulo demande d'ajouter un comparateur qui détecte quand la valeur maximale est atteinte, afin de commander l'entrée de réinitialisation. Un tel circuit est juste un comparateur avec une constante, que vous savez déjà fabriquer à cet endroit du cours.
[[File:Modulo-5-Zähler.svg|centre|vignette|upright=2|Compteur modulo 10.]]
Il est aussi possible d'utiliser des bascules D à la place des bascules T simplifiées. En effet, une bascule T simplifiée est identique à une bascule D dont on boucle la sortie /Q sur l'entrée de données. Cette implémentation permet d'ailleurs de réinitialiser le compteur à une valeur non-nulle. Pour cela, l'entrée de chaque bascule D est précédée d'un multiplexeur, qui choisit entre le bit calculé par le compteur et celui présenté sur l'entrée de ré-initialisation. Quand l'entrée ''Reset'' est activée, les multiplexeurs connectent les bascules aux bits sur l'entrée de ré-initialisation. Dans le cas contraire, le compteur fonctionne normalement, les multiplexeurs connectant l'entrée de chaque bascule à sa sortie.
[[File:Compteur asynchrone, avec initialisation.jpg|centre|vignette|upright=2|Compteur asynchrone, avec initialisation.]]
Pour finir, la sortie de débordement n'est autre que la sortie de la dernière bascule, celle qui contient le bit de poids fort.
===Les compteurs ''one-hot''===
Les '''compteurs ''one-hot''''' sont appelés ainsi, car ils permettent de compter dans une représentation des nombres appelée la représentation ''one-hot''. Pour rappel, dans une telle représentation, un seul bit est à 1 pendant que les autres sont à 0. Les entiers sont codés de la manière suivante : le nombre N est encodé en mettant le énième bit à 1, avec la condition que l'on commence à compteur à partir de zéro. Il est important de remarquer que dans cette représentation, le zéro est n'est PAS codé en mettant tous les bits à 0, la valeur 0000...0000 est une valeur interdite. À la place, le zéro est codé en mettant le bit de poids faible à 1. Pour N bits, on peut encoder seulement N valeurs, dont le zéro.
{| class="wikitable" border="1"
|-
! Décimal !! Binaire !! ''One-hot''
|-
| 0 || 000 || 00000001
|-
| 1 || 001 || 00000010
|-
| 2 || 010 || 00000100
|-
| 3 || 011 || 00001000
|-
| 4 || 100 || 00010000
|-
| 5 || 101 || 00100000
|-
| 6 || 110 || 01000000
|-
| 7 || 111 || 10000000
|}
Un compteur en représentation ''one-hot'' contient un nombre codé de cette manière, qui est incrémenté ou décrémenté si besoin. Pour donner un exemple, la séquence d'un compteur en anneau de 4 bits est :
* 0001 (0) ;
* 0010 (1) ;
* 0100 (2) ;
* 1000 (3) .
Un compteur ''one-hot'' basique est composé d'un registre à décalage dont on boucle la sortie sur son entrée. En faisant cela, on garantit que le registre revient à zéro lors d'un débordement, zéro étant codé avec un 1 dans le bit de poids faible. Au passage, si vous ne mettez que des 0 dans un compteur en anneau, il restera bloqué pour toujours : décaler une suite de 0 donnera la même suite de 0. Initialiser un compteur ''one-hot'' demande donc quelques subtilités qu'on détaillera plus bas.
[[File:Compteur en anneau de 4 bits.jpg|centre|vignette|upright=2|Compteur en anneau de 4 bits]]
Un compteur ''one-hot'' de N bits peut prendre N valeurs différentes, qui ont toutes un seul bit à 1. Pour 5 bits, la séquence est illustrée ci-dessous.
[[File:Lauflicht 5 LED.gif|centre|vignette|Compteur en anneau de 5 bits.]]
Faire des comparaisons avec ce type de compteur est très simple : le compteur contient la valeur N si le énième bit est à 1. Pas besoin d'utiliser de circuit comparateur, juste de lire un bit. Par contre, ce compteur n'est pas très économe en bascules. Imaginons que l'on veut un compteur qui compte jusqu'à une valeur N arbitraire : un compteur binaire normal utilisera environ <math>\log_2{(N)}</math> bascules, alors qu'un compteur ''one-hot'' demande N bascules. Mais si N est assez petit, l'économie de bascules est assez faible, alors que l'économie de circuits comparateurs/incrémenteurs l'est beaucoup plus.
Il y a peu d'applications qui utilisent des compteurs en anneau. Ils étaient autrefois utilisés dans les tous premiers ordinateurs, notamment ceux qui géraient une représentation des nombres spécifique appelée la '''''Bi-quinary coded decimal'''''. De nos jours, de tels compteurs sont utilisés dans les séquenceurs de processeurs, mais aussi dans les séquenceurs de certains périphériques, ou dans les circuits séquentiels simples qui se résument à des machines à états. Ils sont alors utilisés car très rapides, parfaitement adaptés au stockage de petites valeur, et surtout : ils n'ont pas besoin de circuit comparateur pour connaitre la valeur stockée dedans. Nous n'allons pas rentrer dans le détail de leurs utilisations, car nous en reparlerons dans la suite du cours.
==Les registres à décalage à rétroaction linéaire==
Les '''registres à décalage à rétroaction linéaire''' sont des registres à décalages un peu particuliers. Le terme anglais pour de tels registres est ''Linear Feedback Shift Register'', ce qui s’abrège en LFSR. Nous utiliserons cette abréviation dans ce qui suit pour simplifier grandement l'écriture.
Les LFSR sont appelés ainsi pour plusieurs raisons. Déjà, registre à décalage implique qu'ils sont fabriqués avec un registre à décalage. À rétroaction indique que l'on boucle la sortie sur l'entrée. Le terme combinaison linéaire demande quelques explications. L'idée est qu'entre la sortie et l'entrée, il y a un circuit combinatoire qui s'intercale entre la sortie et l'entrée, qui calcule ce qu'il faut mettre sur l'entrée, à partir du contenu du registre
Pour simplifier, cela veut dire qu'on multiplie chaque bit par 0 ou 1, avant d'additionner le tout. Dans ce calcul, on ne garde qu'un seul bit du résultat, vu que l'entrée du registre à décalage ne fait qu'un bit. Par simplicité, on ne garde que le bit de poids faible. Or, il s'avère que cela simplifie grandement les calculs, car cela permet de remplacer les additions par une simple opération XOR.
: <math>1 \times a_n \oplus ... + 1 \oplus a_3 + 1 \oplus a_1</math>
Le résultat est ce que l'on appelle un '''''LFSR de Fibonacci''''', ou encore un LFSR classique, qui celui qui colle le mieux avec la définition.
[[File:LFSR-F16.gif|centre|vignette|upright=2|Registre à décalage à rétroaction de Fibonnaci.]]
Les '''registres à décalages à rétroaction affine''' sont identique aux précédents à une différence près : le bit calculé est inversé avant d'être inséré dans le registre. Un tel circuit est donc composé de portes NXOR, comparé à son comparse linéaire, composé à partir de portes XOR. Petite remarque : si je prends un registre à rétroaction linéaire et un registre à rétroaction affine avec les mêmes coefficients sur les mêmes bits, le résultat du premier sera égal à l'inverse de l'autre.
Les '''LSFR de Gallois''' sont un peu l'inverse des LFSR vus juste avant. Au lieu d'utiliser un registre à décalage SIPO, ils utilisent un registre à décalage PISO. Pour faire la différence, nous appellerons ces derniers les LFSR PISO, et les premiers LFSR SIPO. Avec les LFSR PISO, on prend le bit sortant et on en déduit plusieurs bits à partir d'un circuit combinatoire, qui sont chacun insérés dans le registre à décalage à un endroit bien précis. Bien sûr, la fonction qui calcule des différents bits à partir du bit d'entrée conserve les mêmes propriétés que celle utilisée pour les LFSR : elle se calcule avec uniquement des portes XOR, ou NXOR pour leur variante affine.
Leur avantage est qu'ils sont plus rapides, car il n'y a qu'une seule porte logique entre la sortie et une entrée du registre à décalage, contre potentiellement plusieurs avec les LFSR SIPO. Notons que tout comme les LFSR qui ne peuvent pas mémoriser un 0, de tels registres à décalage à rétroaction ne peuvent pas avoir la valeur maximale stockable dans le registre. Cette valeur gèle le registre à cette valeur, dans le sens où le résultat au cycle suivant sera identique. Mais cela ne pose pas de problèmes pour l'initialisation du compteur.
[[File:LFSR-G16.svg|centre|vignette|upright=2|Registre à décalage à rétroaction de Galois.]]
Quelques propriétés sont communes à tous les LSFRs. Un LSFR est déterministe : pour le même résultat en entrée, il donnera toujours le même résultat en sortie. De plus, il ne peut contenir qu'un nombre fini de valeurs. Et ces deux propriétés sont source de comportements qu'on va détailler ci-dessous.
===L'initialisation d'un LSFR===
Les LSFR ne peuvent pas être initialisés à une valeur arbitraire, en raison de la présence d'une '''valeur interdite'''. Concrètement, une fois que le décompte arrive sur une valeur interdite, le compteur reste bloqué sur cette valeur. La valeur suivante, calculée par le compteur, est cette valeur elle-même.
Le premier cas est celui où le compteur peut être initialisé avec zéro sans que cela ne pose problème. Sur de tels LSFRs, la réinitialisation se fait comme pour n'importe quel registre/compteur. A savoir que les entrées de reset des bascules sont toutes connectées ensemble, au même signal de reset.
[[File:Johnson Counter 4bit.svg|centre|vignette|upright=2|Compteur de Johnson de 4 bits]]
Le second cas est celui des LFSR non-affine. Lors de la réinitialisation, il faut que toutes les bascules soient réinitialisées à 0, sauf une qui est mise à 1. La bascule en question doit disposer d'une entrée S (''Set'') qui met la bascule à 1 quand elle est activée. Cela garantit que le registre est réinitialisé avec un zéro codé en ''one-hot''.
[[File:Overbeck Counter 4bit.svg|centre|vignette|upright=2|Compteur en anneau de 4 bits]]
Une autre solution est de mettre un multiplexeur juste avant l'entrée du registre à décalage. Cette solution marché bien dans le sens où elle permet d'initialiser le registre avec une valeur arbitraire, qui est insérée dans le registre en plusieurs cycles. Pour les LFSR, le multiplexeur est connecté soit au bit calculé par les portes XOR, soit par une entrée servant uniquement de l'initialisation.
[[File:Initialisation d'un LFSR.jpg|centre|vignette|upright=2|Initialisation d'un LFSR]]
===La période d'un LSFR===
Si le compteur est initialisé correctement, il passera d'une valeur à l'autre et finira par repasser par une valeur qu'il aura déjà parcourue et son fonctionnement se reproduira à l'identique comparé à son passage antérieur, il bouclera. Il parcourt un nombre N de valeurs à chaque cycle, ce nombre étant appelé la '''période du compteur'''.
La période d'un LSFR dépend fortement de la fonction utilisée pour calculer le bit de sortie. Dans le meilleur des cas, le LSFR passera par toutes les valeurs que le registre peut prendre, sauf une : suivant le registre, le zéro ou sa valeur maximale sont interdits. Si un LSFR passe par zéro, il y reste bloqué définitivement. La raison à cela est simple : un XOR sur des zéros donnera toujours 0. Le même raisonnement peut être tenu pour les registres à rétroaction affine, sauf que cette fois-ci, c'est la valeur maximale stockable dans le registre qui est fautive.
Tout le chalenge consiste donc à trouver quels sont les LSFR dont la période est maximale : ceux dont la période vaut <math>2^n - 1</math>. Qu'on se rassure, quelle que soit la longueur du registre, il en existe au moins un : cela se prouve mathématiquement.
<noinclude>[[File:LFSR-F4.GIF|centre|vignette|upright=2|Exemple avec un registre à rétroaction linéaire de 4 bits.]]</noinclude>
==La génération de nombres pseudo-aléatoires==
Les LSFRs peuvent aussi être utilisés pour générer des nombres "aléatoires". Je dis aléatoires entre guillemets, car ils ne sont pas vraiment aléatoires, mais s'en rapprochent suffisamment pour être considérés comme tels. Pour mettre en avant cela, on parle aussi de nombres "pseudo-aléatoires". De nombreuses situations demandent de générer des nombres pseudo-aléatoire de manière matérielle. Cela peut servir pour sélectionner une ligne de cache à remplacer lors d'un défaut de cache, pour implémenter des circuits cryptographiques, pour calculer la durée d'émission sur un bus Ethernet à la suite d'une collision, et j'en passe.
Les méthodes que nous allons voir produisent un nombre pseudo-aléatoire un bit à la fois, à quelques exceptions près. Les circuits que nous allons voir fournissent un bit sur leur sortie et ce bit varie de manière assez aléatoire. Les bits en sortie du circuit sont accumulés dans un registre à décalage normal, pour former un nombre aléatoire. Nous appellerons ce registre : l'accumulateur.
===L'usage de registres à décalage à rétroaction===
[[File:Nonlinear-combo-generator.png|thumb|Nonlinear-combo-generator]]
Un LSFR seul ne fournit pas un aléatoire digne de ce nom, mais il est possible de combiner plusieurs LSFR pour obtenir une meilleure approximation de l'aléatoire. Avec cette technique, plusieurs registres à décalages à rétroaction sont reliés à un circuit combinatoire non-linéaire. Ce circuit prendra en entrée un (ou plusieurs) bit de chaque registre à décalage à rétroaction, et combinera ces bits pour fournir un bit de sortie.
[[File:A5-1 GSM cipher.svg|centre|thumb|upright=2|Exemple avec trois LSFR différents, de tailles différentes : le bit envoyé à l'accumulateur est un XOR du bit sortant des trois LSFR.]]
Pour rendre le tout encore plus aléatoire, il est possible de cadencer les LSFR à des fréquences différentes. Cette technique est utilisée dans les générateurs ''stop-and-go'', ''alternative step'', et à ''shrinking''.
* Le générateur ''alternative step'' utilise trois LSFR. Le premier commande un multiplexeur qui choisit la sortie parmi les deux restants.
* Le générateur ''stop-and-go'' utilise deux LSFR. Le premier est relié à l'entrée d'horloge du second et le bit de sortie du second est utilisé comme résultat. Une technique similaire était utilisée dans les processeurs VIA C3, pour l'implémentation de leurs instructions cryptographiques.
* Le ''shrinking generator'' utilise deux LSFR cadencés à des vitesses différentes. Si le bit de sortie du premier vaut 1, alors le bit de sortie du second est utilisé comme résultat. Par contre, si le bit de sortie du premier vaut 0, aucun bit n'est fourni en sortie, le bit de sortie du second registre est oublié.
===L'aléatoire généré par des ''timers'' ou des compteurs d'horloge===
Au-delà des LSFR, il est possible d'utiliser des compteurs pour générer du pseudo-aléatoire. Par exemple, une technique très simple utilise un simple ''timer''. Si on a besoin d'un nombre pseudo-aléatoire, il suffit de lire le ''timer'' et d'utiliser le nombre lu comme nombre pseudo-aléatoire. Si le délai entre deux demandes est irrégulier, le résultat semblera aléatoire. Mais il s'agit là d'une technique assez peu fiable dans le monde réel et seules quelques applications bien spécifiques se satisfont de cette méthode.
Une solution un peu plus fiable utilise ce qu'on appelle la '''dérive de l'horloge'''. Il faut savoir qu'un signal d'horloge n'est jamais vraiment très précis. Une horloge censée tourner à 1 Ghz ne tournera pas en permanence à 1Ghz exactement, mais verra sa fréquence varier de quelques Hz ou Khz de manière irrégulière. Ces variations peuvent venir de variations aléatoires de température, des variations de tension, des perturbations électromagnétiques, ou à des phénomènes assez compliqués qui peuvent se produire dans tout circuit électrique (comme le ''shot noise'').
L'idée la plus simple utilise deux horloges : une horloge lente et une horloge rapide, dont la fréquence est un multiple de l'autre. Par exemple, on peut choisir une fréquence de 1 Mhz et une autre de 100 Hz : la fréquence la plus grande est égale à 10000 fois l'autre. La dérive d'horloge fera son œuvre, les deux horloges seront très légèrement désynchronisées en permanence, et cette désynchronisation peut être utilisée pour produire des nombres aléatoires. Par exemple, on peut compter le nombre de cycles d'horloge produit par l'horloge rapide durant une période de l'horloge lente. Si ce nombre est pair, on produit un bit aléatoire qui vaut 1 , il vaut 0 si ce nombre est pair. Pour information, c'est exactement cette technique qui était utilisée dans l'''Intel 82802 Firmware Hub''.
Il existe d'autres solutions matérielles qui utilisent le bruit thermique. Tous les circuits électroniques de l'univers sont soumis à de microscopiques variations de température, dues à l'agitation thermique des atomes. Plus la température est élevée, plus les atomes qui composent les fils métalliques des circuits s'agitent. Vu que les particules d'un métal contiennent des charges électriques, ces vibrations font naître des variations de tensions assez infimes. Il suffit d'amplifier ces variations pour obtenir un résultat capable de représenter un zéro ou un 1. Ce principe a été utilisé sur des anciens processeurs Intel qui géraient l'instruction RDRAND, une instruction qui produisait un nombre aléatoire.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les compteurs et timers
| prevText=Les compteurs et timers
| next=Les circuits de décalage et de rotation
| nextText=Les circuits de décalage et de rotation
}}
</noinclude>
cp8cc6cqcgmjkefnj9n95fo8jwknu3z
Fonctionnement d'un ordinateur/L'interface électrique entre circuits intégrés et bus
0
81888
768606
729890
2026-06-25T14:57:27Z
Mewtow
31375
/* Transformer une sortie totem-pole en sortie trois états */
768606
wikitext
text/x-wiki
Les circuits intégrés sont connectés au monde extérieur, par l'intermédiaire de leurs broches. Broches qui peuvent servir d'entrée ou de sortie. Nous allons étudier les sorties des circuits intégrés, car il y a des choses importantes à dire dessus. Dans ce chapitre, nous allons voir qu'il existe trois types de sorties différentes. L'intérêt est qu'interconnecter des circuits intégrés entre eux demande de savoir comment ces sorties fonctionnent. Nous détaillerons les interconnexions dans les chapitres sur les bus et les liaisons point à point, où les acquis du présent chapitre seront réutilisés. De plus, la section sur le OU câblé à la fin du chapitre sera utile dans le chapitre sur les mémoires ROM.
==Les trois types de sorties : totem-pole, trois états et à drain ouvert==
Les sorties des circuits intégrés peuvent se classer en plusieurs types, selon leur fonctionnement. Pour les sorties basées sur des transistors, on distingue principalement les sorties totem-pole, les sorties à drain ouvert et les sorties trois-état. Et les trois donnent des bus très différents.
Les '''sorties totem-pole''' sont les plus communes pour les circuits CMOS. Ce sont des sorties qui sont connectées à deux transistors : un qui relie la sortie à la masse, et un autre qui la relie à la tension d'alimentation. En technologie CMOS, elles sont équivalentes à des sorties connectées à une porte logique. Elles sont toujours connectées soit à la masse, soit à la tension d'alimentation.
Les '''sorties trois-état''' peuvent prendre trois états, comme leur nom l'indique. Soit elles sont connectées à la masse, soit elles sont reliées à la tension d'alimentation, soit elles ne sont connectées ni à l'une ni à l'autre. Si les deux premiers cas correspondent à un 0 et à un 1, l'état déconnecté ne correspond à aucun des deux. Il s'agit d'un état utilisé quand on souhaite déconnecter ou connecter à la demande certains composants dans un circuit.
[[File:TTL Open collector.svg|vignette|upright=0.5|Sortie à collecteur ouvert, équivalent en technologie TTL d'une sortie à drain ouvert.]]
Les '''sorties à drain/collecteur ouvert''' sont soit connectées à la masse, soit connectées à rien. La sortie peut être mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour utiliser une sortie à drain ouvert, il faut relier la sortie à la tension d'alimentation à travers une résistance, appelée ''résistance de rappel''. Il existe aussi une variante, où la sortie peut être mise à 1 par le circuit intégré, ou être déconnectée, mais ne peut pas être mise à 0 sans intervention extérieure. Ici on connecte la sortie à la masse, et non à la tension d'alimentation.
[[File:Animated open drain output.gif|centre|vignette|upright=2|Sortie à drain ouvert.]]
Les sorties à drain ouvert et les sorties trois-états sont très utilisés quand il s'agit de connecter plusieurs circuits intégrés entre eux. Vous comprendrez en quoi ces sorties sont utiles quand nous parlerons des mémoires et des bus de communication, et nous en reparlerons longuement dans le chapitre sur les bus électroniques. Nous verrons que de nombreux bus exigent que les circuits branchés dessus aient des entrées-sorties trois-états, ou en drain/collecteur ouvert.
===Transformer une sortie totem-pole en sortie trois états===
Il est possible de fabriquer une sortie trois-états à partir d'une sortie totem-pole normale. Pour cela, il faut placer une porte logique modifiée juste avant la sortie totem-pole. Cette porte logique est une porte OUI améliorée appelée '''tampon trois-état'''. Elle 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 dans l'état normal (0 ou 1).
{|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
|}
Pour simplifier, on peut voir ceux-ci comme des interrupteurs :
* si on envoie un 0 sur l'entrée de commande, ces circuits trois états se comportent comme un interrupteur ouvert ;
* si on envoie un 1 sur l'entrée de commande, ces circuits trois états se comportent comme une porte OUI.
[[File:Tristate buffer corrected.svg|centre|vignette|upright=1.5|Tampon trois-états.]]
Les tampons trois-états ressemblent aux portes à transmission, à un détail près : ce sont des composants actifs, qui régénèrent le signal d'entrée. Là où les portes à transmission sont électriquement équivalentes à un interrupteur, ce n'est pas le cas des tampons trois-états. Les tampons trois-états sont reliés à la tension d'alimentation et à la masse, ils amplifient un peu le signal d'entrée si besoin.
Un tampon trois-état est parfois implémenté avec le circuit ci-dessous. Son fonctionnement est simple à expliquer. Si le bit de commande vaut 0, la sortie des deux portes vaut 0 et les deux transistors sont ouverts. Si le bit de commande vaut 1, les deux sorties des portes ET sont l'inverse l'une de l'autre. Si le bit d'entrée est à 1, le transistor du haut se ferme et met un 1 en sortie, alors que le transistor du bas s'ouvre. Si le bit d'entrée est à 0, c'est l'inverse, la sortie est reliée à la masse et sort un 0. Si le bit de commande est à 0, la sortie des deux portes sort un 0, les deux transistors se ferment.
[[File:Circuit trois état, implémentation possible.jpg|centre|vignette|upright=2|Circuit trois état, implémentation possible]]
===Transformer une sortie totem-pole en sortie à collecteur ouvert===
Il est possible de fabriquer une sortie à collecteur ouvert à partir d'une sortie totem-pole normale. Pour cela, il faut placer un transistor en aval de la sortie normale. Les sorties à drain ouvert utilisent un transistor MOS, les sorties à collecteur ouvert utilisent un transistor bipolaire au lieu d'un transistor MOS. Le tout est illustré ci-dessous.
La sortie est mise à 0 ou 1 selon que le transistor est ouvert ou fermé. Si le transistor est ouvert, la sortie est connectée à la tension d'alimentation, ce qui fait que la sortie est à 1. Si le transistor est fermé, la tension d'alimentation est reliée à la masse, la tension d'alimentation est alors aux bornes de la résistance, et la sortie est donc au niveau de la masse : elle est à 0.
[[File:Open Collector Pull Up.png|centre|vignette|Implémentation d'une sortie à collecteur ouvert, équivalent en technologie TTL d'une sortie à drain ouvert.]]
Pour la variante où la sortie est soit à 1 ou déconnectée, on peut procéder de la même manière, en plaçant un transistor en aval de la sortie. Mais il est aussi possible d'utiliser un autre composant que le transistor : une diode. Une '''diode''' est un composant qui ne laisse passer le courant que dans un sens : de l'entrée vers la sortie, mais pas dans l'autre sens. La diode est dite bloquée quand elle ne laisse pas passer le courant, passante quand le courant passe. La diode est passante si on met une tension suffisante sur l'entrée, bloquée sinon. En clair, la diode recopie un 1 présenté sur l'entrée, mais déconnecte la sortie quand on présente un 0 sur l'entrée.
==Le ET câblé et le OU câblé avec des sorties à drain ouvert==
Les sorties à drain ouvert ont une particularité assez sympathique, qui permet d'implémenter une porte ET simplement en croisant des fils. Il suffit de connecter ces sorties au même fil et de relier celui-ci à la tension d'alimentation à travers une résistance. On obtient alors un ''ET câblé'', qui fait un ET entre plusieurs sorties d'un circuit intégré. Il est illustré ci-dessous.
La tension d'alimentation est reliée au fil à travers une résistance, ce qui permet d'imposer un 1 sur la sortie, à condition que les sorties en collecteur ouvert soient coopératives. Si toutes les sorties sont à 1, elles sont déconnectées, et la sortie est connectée à la résistance de rappel : le circuit sort un 1. Par contre, si une seule sortie sort un 0, elle connectera la tension d'alimentation à la masse et mettra la sortie à 0. C'est le comportement attendu d'une porte ET.
[[File:Open-collector buffers sharing output wire.svg|centre|vignette|upright=1|Et câblé.]]
Pour comprendre comment cela fonctionne, rappelons qu'une sortie en collecteur ouvert est connectée à un transistor relié à la masse. En explicitant ce transistor dans les schémas du dessus, on obtient le schéma ci-dessous. Vous remarquerez qu'il ressemble très fortement au schéma d'une porte logique NOR en technologie NMOS, même le transistor NMOS est remplacé par un transistor bipolaire.
[[File:ET ou OU cable.png|centre|vignette|upright=1|ET ou OU cable]]
Le ''OU câblé'' fonctionne sur le même principe, avec cependant deux grosses différences. Premièrement, les sorties en collecteur ouvert doivent soit imposer un 1 sur la sortie, soit la déconnecter. C'est le fonctionnement inverse à celui vu précédemment. Deuxièmement, la résistance est reliée à la masse, ce qui permet d'imposer un 0 sur la sortie si les sorties en collecteur ouvert soient coopératives. Si toutes les sorties sont à 0, elles sont déconnectées, et la sortie est connectée à masse à travers la résistance de rappel : le circuit sort un 0. Par contre, si une seule sortie sort un 1, elle impose le 1 sur la sortie. C'est le comportement attendu d'un OU.
[[File:Two open-emitter buffers shared-output.svg|centre|vignette|upright=1|OU câblé.]]
En théorie, beaucoup de circuits peuvent se simplifier en utilisant des OU/ET câblés. C'en est au point où de nombreux circuits que nous allons voir dans la suite de ce cours pourraient se simplifier grâce à ces montages. Mais ils sont peu utilisés en pratique, surtout sur les circuits CMOS.
===Les multiplexeurs fabriqués avec un OU câblé===
Un exemple d'utilisation est la fabrication de multiplexeurs. Pour rappel, un multiplexeur est composé d'un décodeur combiné à une couche de portes ET suivies par une porte OU à plusieurs entrées.
[[File:Multiplexeur 2 vers 4 conçu à partir d'un décodeur.png|centre|vignette|upright=1.5|Multiplexeur 2 vers 4 conçu à partir d'un décodeur.]]
[[File:Mux mit OpenCollector und Wired-OR.svg|vignette|Multiplexeur conçu avec un OU câblé.]]
Sur les vieux circuits et avec les vielles technologies de fabrication, il était intéressant de remplacer la porte OU finale par une porte OU câblée. Utiliser un ou câblé permettait aussi de remplacer les portes ET par des portes à transmission, plus simples.
Un OU câblé peut se faire de plusieurs manières, mais la plus commune demande que les sorties des portes logiques ET soient de type collecteur ouvert, à savoir qu'elles fournissent seulement un 1, et déconnectent leur sortie quand elles doivent sortir un 0 (ou inversement). De plus, il faut relier le fil soit à la masse (à la tension d'alimentation) à travers une résistance. Le circuit illustré ci-dessous utilise une méthode similaire. Le OU câblé est en réalité un circuit équivalent à une porte NAND réalisée avec un ET câblé. Le ET câblé est plus simple à fabriquer, mais le circuit utilise une porte logique en plus.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits intégrés
| prevText=Les circuits intégrés
| next=L'architecture de base d'un ordinateur
| nextText=L'architecture de base d'un ordinateur
}}{{autocat}}
</noinclude>
986r90dio8wnc41scxmivczvci4ex76
La sécurité du citoyen dans son logement
0
82221
768646
745368
2026-06-25T17:40:15Z
DavidL
1746
+ deux classements CDU possibles
768646
wikitext
text/x-wiki
<noinclude>{{NavDébut|book={{PAGENAME}}|page=Introduction|pageText=Démarrer}}</noinclude>
{{Page de garde|image=Minneapolis neighborhood Cooper (52102230363).jpg|description=
La maison est censée être un lieu sûr, mais elle peut être exposée à divers risques : cambriolages, incendies, accidents domestiques ou encore usurpation d’identité. Adopter de bonnes pratiques permet de se protéger soi-même, sa famille et ses biens.
|avancement=Ébauche
|cdu=
* {{CDU item|6/64|643}}
* {{CDU item|6/69|699.8}}
|versions=
{{Moteur}}
{{Version imprimable}}
}}
Pour lire ce livre, commencez par [[La_sécurité_du_citoyen_dans_son_logement/Introduction|l'introduction]]
{{nouveau livre}}
== Contenu du livre ==
{{/Sommaire}}
== À qui s'adresse ce livre ? ==
Ce livre s'adresse à tous les citoyens. Et doit évoluer (cf. § Contribuer).
== Objectifs pédagogiques ==
L'objectif est de partager les bonnes pratiques en matière de sécurité. Sensibiliser les citoyens aux multiples techniques malveillantes et risques qui peuvent entourer le logement
== Contribuer ==
Le squelette est déjà là, et n’hésitez pas à rajouter du contenu et faire grossir certaines sections pour que chacun puisse profiter de l'information la plus complète. Il ne doit pas forcément être teinté France mais plus général.
Les références vers des organismes d’État ou associations lié(e)s au domaine concerné sont appréciées.
<!-- [[Catégorie:]] -->
{{Autocat}}
nqm4jt6v1am65n96l8uh37rmb9gxeuo
Dictionnaire de philosophie/Intelligence animale
0
83039
768682
767706
2026-06-26T04:31:01Z
PandaMystique
119061
Bot : ajout du paramètre lecture=oui au modèle {{DicoPhilo}}
768682
wikitext
text/x-wiki
{{DicoPhilo|Intelligence animale|lecture=oui}}
L{{'}}'''intelligence animale''' désigne l’ensemble des capacités par lesquelles des [[Dictionnaire de philosophie/Animal|animaux]] non humains perçoivent leur environnement, apprennent de l’expérience, résolvent des problèmes, ajustent leur conduite à des situations nouvelles et, dans certains cas, communiquent, anticipent ou coopèrent. Ainsi entendue, elle ne se confond ni avec la simple sensibilité (la faculté d’éprouver), ni avec [[Dictionnaire de philosophie/Conscience|la conscience]] (le fait qu’il y ait « quelque chose que cela fait » d’être tel animal), ni avec le seul comportement adaptatif, dont elle suppose certains aspects sans s’y réduire. Cette notion occupe, depuis l’Antiquité, une place centrale dans les débats philosophiques, en soulevant des questions difficiles sur la nature de la cognition, la spécificité de l’humain par rapport à l’animal, ainsi que sur les implications éthiques de nos rapports aux autres êtres sensibles. Longtemps formulée dans le cadre d’une philosophie de l’âme, elle est aujourd’hui reprise par l’éthologie et les sciences cognitives, qui en renouvellent les termes sur une base expérimentale.
== Les fondements aristotéliciens ==
=== L’âme sensitive et ses facultés ===
[[Fichier:Aristotle Altemps Inv8575.jpg|vignette|Aristote (buste, copie romaine d’un original grec). Le ''De anima'' fonde l’étude du vivant sur une hiérarchie de fonctions de l’âme.]]
Pour [[Dictionnaire de philosophie/Aristote|Aristote]] (384-322 av. J.-C.), l’intelligence animale s’inscrit dans une conception hiérarchique du vivant fondée sur la théorie de [[Dictionnaire de philosophie/Âme|l’âme]]. Dans le ''De anima'', il distingue trois niveaux ou fonctions de l’âme, qui ne constituent pas nécessairement trois âmes séparées<ref>Aristote, ''De anima'', II, 2, 413b4-7.</ref> : la fonction végétative, responsable de la nutrition et de la reproduction (commune aux plantes, aux animaux et aux humains), la fonction sensitive, qui rend possibles la perception et le mouvement (propre aux animaux et aux humains), et la fonction intellective ou rationnelle, réservée à l’être humain<ref>Aristote, ''De anima'', II, 3, 414b28-31.</ref>.
L’âme sensitive confère aux animaux un ensemble de capacités cognitives qui ne se réduisent pas à de simples réactions mécaniques. La perception (''aisthēsis'') est la faculté qui distingue les animaux des plantes<ref>Aristote, ''De anima'', 412b16-17.</ref>. Tout animal possède au minimum le sens du toucher, condition minimale de l’animalité, tandis que les animaux « supérieurs » disposent de l’ensemble des cinq sens externes<ref>Aristote, ''De anima'', II, 2, 413b4-7.</ref>. Aristote souligne que la perception n’est pas une simple réception passive d’impressions sensibles, mais comporte déjà une forme de discrimination : percevoir, c’est distinguer, reconnaître, apprécier des différences pertinentes<ref>Aristote, ''De anima'', III, 2, 428a11-12.</ref>.
Ainsi, même si les animaux ne possèdent pas le ''logos'' rationnel, ils ne sont pas pour autant assimilés à des automates. La perception sensible fonde un mode propre de rapport au monde, orienté vers la recherche de la nourriture, la fuite du danger, la reproduction, etc., qui suppose une certaine structuration de l’expérience.
=== Les sens internes et la mémoire animale ===
Au-delà des cinq sens externes, Aristote décrit des facultés perceptives internes que la tradition scolastique systématisera plus tard sous le nom de « sens internes » : les éléments en sont présents chez lui, mais leur mise en système est surtout médiévale. Le ''sens commun'' (''koinē aisthēsis'') coordonne les informations issues des différents sens et permet de saisir les qualités communes comme le mouvement, la forme ou la grandeur<ref>Aristote, ''De anima'', III, 1, 425a14-20.</ref>. L’[[Dictionnaire de philosophie/Imagination|''imagination'']] (''phantasia'') conserve les images des objets perçus en leur absence et joue un rôle crucial dans la genèse du mouvement animal<ref>Aristote, ''De anima'', III, 3, 428a6-11.</ref> : un animal peut se mettre en route vers une proie qu’il ne voit plus, parce qu’il en garde une image sensible.
La mémoire (''mnēmē'') est, pour Aristote, une faculté que les animaux supérieurs partagent avec l’être humain. Dans ''De memoria et reminiscentia'', il explique que la mémoire résulte de perceptions répétées et permet aux animaux de reconnaître des situations analogues<ref>Aristote, ''De memoria et reminiscentia'', 449b24-450a7.</ref>. L’animal se souvient de l’endroit où il a trouvé de la nourriture, de la configuration d’un territoire, d’un danger déjà rencontré. Aristote distingue cependant la simple mémoire de la réminiscence (''anamnēsis''), qui suppose un effort délibéré de recherche du souvenir et demeure, selon lui, propre à l’homme<ref>Aristote, ''De memoria et reminiscentia'', 453a6-14.</ref>. Cette distinction, on le verra, anticipe un débat très actuel : celui de savoir si certains animaux disposent d’une mémoire des événements vécus comparable à notre souvenir épisodique.
=== L’expérience animale et l’induction primitive ===
Dans un passage célèbre des ''Seconds Analytiques'', Aristote montre comment, à partir de perceptions multiples, se forme la mémoire, et de mémoires nombreuses émerge l’expérience (''empeiria'')<ref>Aristote, ''Seconds Analytiques'', II, 19, 99b34-100a9.</ref>. Cette expérience, que partagent l’homme et les animaux supérieurs, constitue une forme d’« induction primitive » qui permet de saisir des régularités dans la nature (par exemple, tel son annonce tel danger, tel chemin mène à tel lieu).
Cependant, Aristote maintient une différence structurale entre cette expérience animale et la [[Dictionnaire de philosophie/Connaissance|connaissance]] proprement rationnelle. Seul l’homme, en vertu de l’intellect (''nous''), est capable d’[[Dictionnaire de philosophie/Abstraction|abstraction]] : il peut dégager l’universel en tant qu’universel, c’est-à-dire saisir des essences stables et formuler des [[Dictionnaire de philosophie/Définition|définitions]]<ref>Aristote, ''Métaphysique'', I, 1, 980a27-981a7.</ref>. Les animaux restent, quant à eux, attachés au particulier : leur rapport au monde, si élaboré soit-il, demeure sans conceptualisation explicite.
== Thomas d’Aquin et la synthèse scolastique ==
=== L’âme comme forme substantielle unique ===
[[Fichier:Thomas Aquinas by Carlo Crivelli.png|vignette|Thomas d’Aquin (Carlo Crivelli, XV{{e}} siècle). Il reconnaît à l’animal une ''vis aestimativa'', estimation sensible des biens et des dangers.]]
Thomas d’Aquin (1225-1274) reprend et approfondit la psychologie aristotélicienne dans une perspective chrétienne. L’âme est, pour lui, la forme substantielle unique du corps vivant, principe d’unité et d’organisation de l’organisme<ref>Thomas d’Aquin, ''Summa Theologiae'', I, q. 76, a. 4.</ref>. Chez les animaux non humains, cette âme sensitive est inséparable du corps et périt avec lui, alors que chez l’être humain, l’âme intellectuelle possède des opérations qui ne dépendent d’aucun organe corporel et est, de ce fait, immortelle<ref>Thomas d’Aquin, ''Summa Theologiae'', I, q. 75, a. 3.</ref>.
=== Les puissances cognitives de l’âme animale ===
Thomas distingue avec précision les différentes puissances de l’âme sensitive. Les cinq sens externes appréhendent les qualités sensibles propres (couleur, son, saveur, odeur, qualités tactiles) et les sensibles communs (mouvement, repos, nombre, figure, grandeur)<ref>Thomas d’Aquin, ''Summa Theologiae'', I, q. 78, a. 3.</ref>. Ces perceptions sont ordinairement fiables lorsqu’elles portent sur leurs objets propres, chaque sens étant naturellement proportionné à ce qui lui convient ; elles peuvent néanmoins tromper en cas de défaut de l’organe, de mauvaises conditions perceptives ou d’erreur dans le jugement qui accompagne la sensation<ref>Thomas d’Aquin, ''In De anima'', II, lect. 13, n. 384.</ref>.
Les sens internes comprennent, selon Thomas, quatre puissances distinctes. Le ''sens commun'' unifie les perceptions des différents sens externes et permet à l’animal de percevoir qu’il perçoit<ref>Thomas d’Aquin, ''Summa Theologiae'', I, q. 78, a. 4, ad 2.</ref>. L’imagination (ou fantaisie) conserve les formes sensibles en l’absence de l’objet. La mémoire sensitive retient ces formes en tant que passées. Enfin, la faculté la plus notable, du point de vue de l’intelligence animale, est la ''vis aestimativa'' chez les animaux (appelée ''vis cogitativa'' ou ''ratio particularis'' chez l’homme)<ref>Thomas d’Aquin, ''Summa Theologiae'', I, q. 78, a. 4.</ref>.
=== La ''vis aestimativa'' : une forme d’intelligence pratique ===
La ''vis aestimativa'' permet aux animaux de saisir, au-delà des qualités sensibles, des « intentions » ou significations pratiques qui ne sont pas directement données aux sens, mais qui sont vitalement pertinentes. Thomas illustre cette faculté par l’exemple classique de la brebis qui fuit le loup, non en raison de sa couleur ou de sa forme, mais parce qu’elle le perçoit comme un ennemi naturel<ref>Thomas d’Aquin, ''Summa Theologiae'', I, q. 78, a. 4.</ref>. De même, l’oiseau ramasse des brindilles non pour leur agrément sensoriel, mais en tant que matériau approprié à la construction du nid.
Chez l’animal, cette faculté estimative opère « par instinct », c’est-à-dire en vertu de dispositions naturelles innées, tandis que chez l’homme, la ''cogitative'' prolonge cette fonction en opérant une comparaison effective entre intentions particulières, à la frontière du raisonnable<ref>Thomas d’Aquin, ''Summa Theologiae'', I, q. 78, a. 4, ad 5.</ref>. Thomas reconnaît ainsi une forme d’intelligence pratique aux animaux, tout en continuant de soutenir que leur cognition demeure enfermée dans le registre du particulier et n’accède pas à l’universel abstrait.
=== Mémoire, expérience et apprentissage animal ===
Thomas accorde une grande importance à la mémoire animale. Reprenant Aristote, il distingue la mémoire simple (''memoria''), commune aux hommes et aux animaux, de la réminiscence (''reminiscentia''), propre à l’homme et impliquant une recherche ordonnée du souvenir<ref>Thomas d’Aquin, ''Summa Theologiae'', I, q. 78, a. 4.</ref>. Les animaux peuvent se souvenir spontanément d’expériences passées et ajuster leur comportement en conséquence : éviter un lieu où ils ont été blessés, choisir un chemin déjà emprunté, reconnaître un individu.
Thomas admet que certains animaux supérieurs, par la souplesse de leur comportement face à des situations nouvelles, « participent en quelque manière à la raison » (''participant aliquid de ratione'')<ref>Thomas d’Aquin, ''In Ethicorum'', VI, lect. 7, n. 1197.</ref>. Cette participation reste toutefois analogique : elle ne confère pas aux animaux une rationalité au sens strict, mais atteste une continuité graduelle dans l’ordre des capacités cognitives.
== Du Moyen Âge à la Renaissance : continuité et rationalité animale ==
=== Continuité ou discontinuité ? ===
[[Fichier:The Great Chain of Being (1579).jpg|vignette|La ''scala naturae'' ou « grande chaîne des êtres » (gravure de 1579). Les animaux y occupent une position médiane, entre les plantes et l’homme.]]
Du Moyen Âge à la Renaissance, les philosophes discutent avec vigueur de la frontière exacte entre l’intelligence humaine et l’intelligence animale. Les uns, fidèles à la tradition aristotélicienne et thomiste, insistent sur une discontinuité de nature : seul l’homme posséderait l’intellect capable d’abstraire l’universel du particulier. D’autres, s’appuyant davantage sur les observations naturalistes et certains thèmes néoplatoniciens, soulignent au contraire les continuités et les zones intermédiaires.
La ''scala naturae'' (échelle des êtres), élaborée à partir de motifs platoniciens et aristotéliciens puis transformée par les traditions néoplatoniciennes et chrétiennes, devient un schème conceptuel dominant<ref>Arthur O. Lovejoy, ''The Great Chain of Being'', Harvard University Press, 1936, p. 24-66.</ref>. Les animaux y occupent une position médiane entre les plantes et les humains, participant à la sensibilité et au mouvement, mais non à la raison. Reste à savoir si cette échelle est continue, chaque degré se fondant dans le suivant, ou si elle est marquée par des seuils qualitatifs, notamment entre l’animal et l’homme.
Des naturalistes comme Julius Caesar Scaliger ou Jean Bodin, ainsi que des auteurs inspirés par Ramus tels que Wilhelm Adolf Scribonius, penchent vers une approche continuiste. Scribonius, dans sa ''Physica et sphaerica doctrina'' (1600), suggère que, de même que certains animaux vivent sur terre et d’autres dans l’eau, certains sont rationnels et d’autres irrationnels<ref>Ann Blair, « Natural Philosophy », dans Katherine Park et Lorraine Daston (dir.), ''The Cambridge History of Science, vol. 3: Early Modern Science'', Cambridge University Press, 2006, p. 369.</ref>. La frontière entre raison et absence de raison tend alors à se déplacer vers l’intérieur du règne animal lui-même.
=== L’argument sceptique : de Plutarque à Sextus Empiricus ===
À la discontinuité aristotélicienne répond, dès l’Antiquité, un courant [[Dictionnaire de philosophie/Scepticisme|sceptique]] qui conteste le privilège humain. Plutarque (vers 46-125) y consacre plusieurs traités. Dans le ''Gryllos'', il met en scène un compagnon d’Ulysse changé en porc par Circé et qui refuse de redevenir homme, soutenant que les bêtes l’emportent en tempérance et en courage ; dans ''De l’intelligence des animaux'', il accumule les exemples de ruse et d’apprentissage<ref>Plutarque, ''Gryllos'' (« Que les bêtes brutes usent de la raison ») et ''De sollertia animalium'' (« De l’intelligence des animaux »), dans les ''Œuvres morales''.</ref>. Sextus Empiricus (vers 160-210) déplace la question sur le terrain logique : il prête au chien de chasse une forme de raisonnement par disjonction lorsque, arrivé à un carrefour et ayant flairé sans succès deux des pistes, il s’engage dans la troisième sans même la renifler, comme s’il concluait que la proie n’étant passée ni par l’une ni par l’autre, elle a forcément emprunté la dernière<ref>Sextus Empiricus, ''Esquisses pyrrhoniennes'', I, 62-72.</ref>. Ces arguments n’établissent pas que les bêtes raisonnent ; ils établissent qu’on ne saurait, sans examen, leur en refuser la capacité. C’est ce doute méthodique que la Renaissance va réactiver.
=== L’argument provocateur de Hieronymus Rorarius ===
Un cas particulièrement significatif est celui de Hieronymus Rorarius (1485-1556) qui, impressionné par les performances d’un chien lors d’un dîner en 1539, rédige un traité soutenant que « la raison se trouve souvent mieux chez les bêtes que chez les hommes » (''rationem saepenumero melius in beluis quam in hominibus reperiri'')<ref>Hieronymus Rorarius et Gabriel Naudé, ''Quod animalia bruta ratione utantur melius homine'', Paris, 1648, p. 19.</ref>. Publié seulement en 1648 par Gabriel Naudé, ce texte aura une postérité notable. Pierre Bayle lui consacre un long article dans son ''Dictionnaire historique et critique'' (1697), en utilisant les arguments de Rorarius comme contrepoint ironique aux thèses cartésiennes de l’animal-machine<ref>Dennis Des Chene, « Animal as Category: Bayle’s “Rorarius” », dans Justin E. H. Smith (dir.), ''The Problem of Animal Generation in Early Modern Philosophy'', Cambridge University Press, 2006, p. 215.</ref>.
Rorarius ne se contente pas de signaler des performances animales spectaculaires ; il renverse la perspective en faisant de l’homme, par ses passions, ses erreurs et ses préjugés, un être souvent moins rationnel dans ses conduites que certains animaux dans les leurs. Cet argument polémique met en lumière la dimension critique que peut prendre la réflexion sur l’intelligence animale : penser l’animal, c’est aussi, en retour, interroger l’illusion de notre supériorité.
=== Montaigne et la présomption humaine ===
La réflexion de Rorarius n’est pas isolée ; elle réactive le courant sceptique antique<ref>Sur cette tradition et ses prolongements, voir Richard Sorabji, ''Animal Minds and Human Morals: The Origins of the Western Debate'', Cornell University Press, 1993.</ref>. C’est Montaigne (1533-1592) qui lui donne sa forme la plus mémorable. Dans l’« Apologie de Raymond Sebond », il retourne contre l’homme l’accusation d’irrationalité : c’est par une « présomption » que nous nous attribuons des facultés refusées aux bêtes, alors que nous ignorons ce qui se passe en elles. Une phrase a fait fortune : « Quand je me joue à ma chatte, qui sait si elle passe son temps de moi plus que je ne fais d’elle ? »<ref>Michel de Montaigne, ''Essais'', II, 12, « Apologie de Raymond Sebond ».</ref>
L’ironie est ici un instrument d’analyse. En suggérant que la chatte pourrait bien le tenir, lui, pour son passe-temps, Montaigne ne nie pas la différence entre l’homme et l’animal ; il met en garde contre la facilité avec laquelle nous projetons nos catégories. Multipliant les exemples de ruse, de communication et de prévoyance animales, il propose une vision continuiste qui fait de l’homme « un animal entre les animaux ». Cette position prépare, par contraste, la réaction cartésienne : c’est en partie pour rompre avec ce continuisme sceptique que Descartes voudra tracer une frontière nette.
=== Capacités inductives et comportements sophistiqués ===
Plusieurs auteurs médiévaux et renaissants, commentateurs d’Aristote ou observateurs du monde naturel, reconnaissent aux animaux supérieurs des capacités cognitives complexes. Les auteurs anciens et renaissants accumulent des observations sur l’orientation, la ruse, la mémoire, la construction du nid, la chasse ou les comportements sociaux des animaux<ref>Voir notamment Aristote, ''Historia animalium'', VIII.</ref>. Les sciences contemporaines reformuleront plus tard ces faits en termes de cartes spatiales, d’usage d’outils, de cognition sociale ou de planification. Ces observations empiriques invitent à nuancer l’idée d’un simple « instinct » aveugle.
La question devient alors de savoir si ces comportements relèvent uniquement de programmes innés rigides, ou s’ils impliquent une forme d’apprentissage, d’ajustement, voire de délibération pratique. La frontière entre instinct et intelligence se brouille, préparant les débats modernes sur l’« intelligence » comme capacité d’adaptation à des situations nouvelles.
== La rupture cartésienne : l’animal-machine ==
=== La théorie de Descartes ===
[[Fichier:Frans Hals - Portret van René Descartes.jpg|vignette|René Descartes (d’après Frans Hals). La thèse de l’animal-machine refuse la pensée aux bêtes et suscite aussitôt les objections de Gassendi et de More.]]
[[Dictionnaire de philosophie/René Descartes|René Descartes]] (1596-1650) opère une rupture franche avec l’héritage aristotélicien en refusant d’attribuer aux animaux une âme sensitive au sens scolastique. Dans le ''Discours de la méthode'' (1637) et les ''Méditations métaphysiques'' (1641), il établit un dualisme strict entre la substance pensante (''res cogitans''), caractérisée par la pensée, et la substance étendue (''res extensa''), purement matérielle et soumise aux lois mécaniques<ref>René Descartes, ''Discours de la méthode'', V, AT VI, 46-60.</ref>.
Dans l’explication cartésienne de leur comportement, les animaux sont traités comme des corps organisés soumis aux lois mécaniques, sans qu’on leur attribue une pensée au sens strict : ce sont des automates sophistiqués, des « machines » biologiques dont les conduites s’expliquent par des mécanismes corporels. Dans le ''Traité de l’homme'', Descartes affirme pouvoir rendre compte de toutes les fonctions vitales sans recourir à aucune « âme végétative ou sensitive »<ref>René Descartes, ''L’Homme'', AT XI, 202.</ref>. Les mouvements, les cris, les réactions des animaux sont interprétés comme les effets de la circulation des « esprits animaux » dans des conduits nerveux, selon un modèle hydraulique.
Descartes propose deux critères destinés à distinguer l’homme de l’animal-machine<ref>René Descartes, ''Discours de la méthode'', V, AT VI, 56-59.</ref> : d’une part, l’usage universel et créateur du langage articulé, les animaux n’ayant que des cris ou signaux liés à des états particuliers ; d’autre part, la capacité d’adaptation rationnelle à un éventail pratiquement illimité de situations, alors que les animaux ne peuvent agir que dans le cadre restreint de leurs dispositions naturelles. Ces deux critères, le langage et la souplesse rationnelle, seront précisément ceux que l’éthologie contemporaine viendra réexaminer.
=== La question de la sensibilité animale ===
L’interprétation exacte de la position cartésienne demeure l’objet de débats. Selon une lecture forte, Descartes nierait purement et simplement toute sensibilité aux animaux, réduisant leurs réactions à des mouvements mécaniques dénués de toute expérience subjective. D’autres commentateurs, au contraire, soutiennent qu’il rejette surtout l’ancienne notion d’« âme sensitive », sans nécessairement exclure qu’il y ait chez l’animal une forme de sensation sans réflexion.
Le texte le plus précis se trouve dans la correspondance. Dans sa lettre à Henry More du 5 février 1649, Descartes ne refuse pas aux bêtes « le sentiment, autant qu’il dépend des organes du corps » ; il leur dénie en revanche la pensée (''cogitatio''), c’est-à-dire la conscience réfléchie qui, chez nous, accompagne la sensation<ref>René Descartes à Henry More, 5 février 1649, AT V, 278 ; voir aussi à Newcastle, 23 novembre 1646, AT IV, 573-576, et l’analyse de John Cottingham, « “A Brute to the Brutes?”: Descartes’ Treatment of Animals », ''Philosophy'', vol. 53, n° 206, 1978, p. 551-559.</ref>. La distinction entre la simple affectation sensorielle et la conscience de l’affection constitue l’un des points les plus discutés de sa doctrine. Elle marque en tout cas la coupure avec la tradition antérieure : la sensibilité animale, si elle existe, ne relève plus d’une subjectivité analogue à la nôtre.
=== Critiques et réponses ===
Les contemporains de Descartes ont rapidement critiqué la théorie de l’animal-machine. Pierre Gassendi (1592-1655), dans ses ''Cinquièmes Objections'' aux ''Méditations'', invoque de nombreuses observations empiriques pour défendre la sensibilité et une forme d’intelligence chez les animaux<ref>Pierre Gassendi, ''Cinquièmes Objections'', AT VII, 263-287.</ref>. Henry More (1614-1687) rejette le réductionnisme cartésien et maintient l’existence d’une âme sensitive distincte de l’âme rationnelle humaine et de l’Esprit universel<ref>Henry More, ''The Immortality of the Soul'', 1662.</ref>.
Même des auteurs proches du mécanisme, comme le médecin et anatomiste Thomas Willis (1621-1675), refusent de suivre Descartes jusqu’au bout. Willis identifie les esprits animaux à une sorte d’âme sensitive aristotélicienne et leur attribue l’ensemble des fonctions cognitives et affectives des animaux et des humains, réservant à l’âme rationnelle les opérations intellectuelles proprement dites<ref>Robert G. Frank, « Thomas Willis and his Circle: Brain and Mind in Seventeenth-Century Medicine », dans G. S. Rousseau (dir.), ''The Languages of Psyche'', University of California Press, 1990, p. 107-146.</ref>.
== La réhabilitation moderne de la sensibilité animale ==
[[Fichier:Charles Darwin by Julia Margaret Cameron, c. 1868.jpg|vignette|Charles Darwin (photographie de Julia Margaret Cameron, vers 1868). La continuité évolutive fait de l’écart mental entre l’homme et l’animal « une différence de degré et non de nature ».]]
À partir du XVIII{{e}} siècle, on assiste à une réhabilitation progressive de la sensibilité et de l’intelligence animales. John Locke (1632-1704), tout en maintenant que la raison abstractive est propre à l’homme, reconnaît aux animaux des capacités d’association d’idées et d’apprentissage par expérience<ref>John Locke, ''Essai sur l’entendement humain'', II, XI.</ref>. David Hume (1711-1776) va plus loin en soutenant que les animaux raisonnent, comme nous, par induction et par association d’idées à partir de l’habitude<ref>David Hume, ''Enquête sur l’entendement humain'', IX.</ref>. Condillac, dans son ''Traité des animaux'' (1755), critique à la fois la machine cartésienne et l’instinct conçu comme savoir inné, et crédite les bêtes d’une expérience sensible qui les instruit.
Le tournant vient surtout de Darwin. Dans ''La Filiation de l’homme'' (1871), il affirme que la différence mentale entre l’homme et les animaux supérieurs est « une différence de degré et non de nature »<ref>Charles Darwin, ''The Descent of Man, and Selection in Relation to Sex'', Londres, John Murray, 1871, vol. I, chap. III (''La Filiation de l’homme''). Le texte original : « the difference in mind between man and the higher animals … is certainly one of degree and not of kind ».</ref> ; ''L’Expression des émotions chez l’homme et les animaux'' (1872) prolonge cette thèse sur le terrain des affects. Son disciple George Romanes inaugure la psychologie comparée, mais sur la base d’anecdotes peu contrôlées, ce qui appellera bientôt une réaction méthodologique. L’éthologie qui se constitue au XIX{{e}} et au XX{{e}} siècle fournit une abondance de données sur les capacités cognitives animales : apprentissage complexe, résolution de problèmes, utilisation et fabrication d’outils, formes sophistiquées de communication, reconnaissance de soi dans le miroir chez certaines espèces, et même ce qui ressemble, chez des primates, des cétacés ou des oiseaux corvidés, à des traditions transmises socialement. Ces découvertes obligent à reconfigurer les questions classiques : qu’est-ce que « comprendre » pour un animal ? Jusqu’où peut-on parler de « langage », de « mémoire autobiographique », de « planification » à leur propos ?
== L’esprit animal devant la science contemporaine ==
=== Le problème des autres esprits ===
Avant de demander ce qu’un animal sait, il faut se demander comment nous pourrions le savoir. La difficulté n’est pas propre aux bêtes : chacun n’a un accès direct qu’à ses propres états mentaux et prête aux autres une vie intérieure par analogie avec la sienne. Avec l’animal, l’analogie se distend. Thomas Nagel l’a fixée dans une question demeurée célèbre : nous pouvons décrire le sonar de la chauve-souris, mais nous ne saurons jamais « ce que cela fait » d’être une chauve-souris percevant le monde par l’écho, car notre imagination reste prisonnière de notre propre type d’expérience<ref>Thomas Nagel, « What Is It Like to Be a Bat? », ''The Philosophical Review'', vol. 83, n° 4, 1974, p. 435-450.</ref>.
La philosophie contemporaine de l’esprit animal part de cet obstacle sans le tenir pour infranchissable. Nous inférons l’existence d’autres esprits, humains comme animaux, à partir du comportement, de la physiologie et de l’histoire évolutive ; la question n’est pas de savoir si l’inférence est certaine, mais quelles inférences sont les mieux justifiées<ref>Kristin Andrews, ''The Animal Mind: An Introduction to the Philosophy of Animal Cognition'', 2{{e}} éd., Routledge, 2020, chap. 1.</ref>. L’argument par analogie (les animaux nous ressemblent par le corps et le comportement, donc sans doute par l’expérience) trouve un appui dans la continuité darwinienne : un ancêtre commun récent rend plausible l’attribution de capacités apparentées. Mais l’analogie a ses limites, et c’est tout l’enjeu des méthodes développées au XX{{e}} siècle que de discipliner ces inférences.
=== Anthropomorphisme, canon de Morgan et éthologie cognitive ===
Deux erreurs symétriques guettent l’étude de l’esprit animal. L’anthropomorphisme prête sans contrôle des pensées humaines à la bête. Son inverse, que le primatologue Frans de Waal nomme l’anthropodéni, refuse par principe toute continuité, au risque d’ignorer ce que l’animal a de commun avec nous. Pour éviter le premier écueil, le psychologue Conwy Lloyd Morgan formule en 1894 un principe de parcimonie, le « canon de Morgan » : aucune action animale ne doit être interprétée comme l’effet d’une faculté supérieure si elle peut l’être par une faculté qui occupe un rang inférieur dans l’échelle psychologique<ref>Conwy Lloyd Morgan, ''An Introduction to Comparative Psychology'', Londres, Walter Scott, 1894, p. 53 ; sur le sens et les usages du canon, voir Andrews, ''The Animal Mind'', chap. 3.</ref>.
Ce canon a souvent été lu comme un mot d’ordre béhavioriste, interdisant de parler d’esprit chez l’animal. C’est un contresens : Morgan tenait au contraire que les poussins, les chiens et les humains ont tous un esprit, et son canon n’est qu’une règle de prudence épistémique, non une négation des états mentaux<ref>Andrews, ''The Animal Mind'', chap. 3.</ref>. Le béhaviorisme proprement dit viendra plus tard, avec les boîtes à problèmes d’Edward Thorndike, où le chat n’apprend à s’échapper que par essais et erreurs, sans « éclair » de compréhension, ce dont Thorndike tire sa « loi de l’effet ». Le tournant vient de Donald Griffin : son petit livre ''The Question of Animal Awareness'' (1976) réhabilite l’étude de la conscience, de l’intention et de la planification chez l’animal et fonde un domaine nouveau, l’éthologie cognitive, qui assume d’explorer ce que la psychologie expérimentale avait déclaré tabou<ref>Donald R. Griffin, ''The Question of Animal Awareness'', Rockefeller University Press, 1976 ; sur la naissance de l’éthologie cognitive, voir l’introduction de Marc Bekoff, Colin Allen et Gordon M. Burghardt (dir.), ''The Cognitive Animal: Empirical and Theoretical Perspectives on Animal Cognition'', MIT Press, 2002.</ref>.
=== Mémoire et voyage mental dans le temps ===
[[Fichier:Western Scrub-jay at the San Francisco Botanical Garden - Sarah Stierch.jpg|vignette|Le geai buissonnier (''Aphelocoma''). Il retient le lieu, la nature et l’ancienneté de ses caches, une mémoire dite « de type épisodique ».]]
La distinction aristotélicienne entre la mémoire et la réminiscence, on l’a dit, se rejoue aujourd’hui. Le psychologue Endel Tulving avait opposé la mémoire sémantique (le savoir général : « les guêpes piquent ») à la mémoire épisodique (le souvenir d’un événement personnellement vécu, avec son moment et son lieu), et tenu cette dernière pour le propre de l’homme. Or une expérience devenue classique a ébranlé cette certitude. Nicholas Clayton et Anthony Dickinson ont montré que le geai buissonnier se souvient non seulement de l’endroit où il a caché de la nourriture, mais aussi de sa nature et du temps écoulé : il récupère d’abord les larves périssables tant qu’elles sont fraîches, mais se tourne vers les graines durables lorsqu’un délai assez long les aurait laissées pourrir<ref>Nicholas S. Clayton et Anthony Dickinson, « Episodic-like memory during cache recovery by scrub jays », ''Nature'', vol. 395, 1998, p. 272-274 ; voir aussi Allison B. Kaufman, Josep Call et James C. Kaufman (dir.), ''The Cambridge Handbook of Animal Cognition'', Cambridge University Press, 2021, partie II.</ref>. Cette mémoire du « quoi, où, quand » remplit le critère objectif du souvenir épisodique. Les chercheurs parlent prudemment de mémoire « de type épisodique » (''episodic-like''), faute de pouvoir établir qu’elle s’accompagne du sentiment de revivre la scène, que Tulving jugeait essentiel.
La question se prolonge vers l’avenir. Planifier suppose de se projeter hors du présent. Or des grands singes choisissent et conservent un outil en vue d’un usage différé, et le geai stocke en fonction de besoins anticipés. On débat de savoir si ces conduites attestent une réelle anticipation ou des mécanismes plus simples d’apprentissage : c’est la controverse sur l’animal prétendument « prisonnier du présent ». Mais le partage tranché qui réservait à l’homme tout rapport réfléchi au temps n’a pas résisté à l’expérimentation.
=== Fabrication d’outils et cognition du nombre ===
L’usage d’outils n’est plus tenu pour un privilège humain depuis longtemps. Le chimpanzé Sultan, étudié par Wolfgang Köhler, empilait des caisses pour atteindre une banane suspendue<ref>Yves Christen, ''L’animal est-il une personne ?'', Flammarion, coll. « Champs sciences », 2009. L’ouvrage offre une synthèse en français de la plupart des résultats discutés dans cette section.</ref> ; les chimpanzés de Gombe « pêchent » les termites avec une tige effeuillée. Plus frappante encore est la fabrication d’un outil neuf. Le corbeau calédonien façonne des crochets ; en laboratoire, la femelle Betty a spontanément plié un fil de fer droit pour en faire un crochet et extraire un petit seau de nourriture d’un tube, sans modèle préalable<ref>Alex A. S. Weir, Jackie Chappell et Alex Kacelnik, « Shaping of hooks in New Caledonian crows », ''Science'', vol. 297, 2002, p. 981.</ref>. D’autres corvidés résolvent le problème du corbeau de la fable, en laissant tomber des cailloux pour faire monter le niveau de l’eau et atteindre un appât flottant.
La cognition du nombre soulève des questions voisines. Des macaques rhésus apprennent à ordonner des collections de un à neuf selon leur quantité, manifestant une compétence numérique ordinale<ref>Elizabeth M. Brannon et Herbert S. Terrace, « The Evolution and Ontogeny of Ordinal Numerical Ability », dans Bekoff, Allen et Burghardt (dir.), ''The Cognitive Animal'', chap. 26.</ref> ; le perroquet gris Alex employait des étiquettes numériques pour dénombrer des objets ; même des poissons discriminent des quantités. La plupart de ces performances reposent sur un « sens du nombre » approximatif, partagé par de nombreuses espèces. Reste la question, héritée d’Aristote, de savoir si l’animal saisit là un [[Dictionnaire de philosophie/Concept|concept]] du nombre ou seulement des indices perceptifs : la frontière entre la discrimination et l’abstraction demeure le lieu du débat.
=== Communication et langage ===
[[Fichier:Vervet monkey (Chlorocebus pygerythrus rufoviridis) juvenile, Semliki Wildlife Reserve.jpg|vignette|Le singe vert (''Chlorocebus''). Ses cris d’alarme distincts pour le léopard, l’aigle et le serpent forment un cas de signal « référentiel ».]]
C’est ici que se joue le premier critère cartésien. Dans la nature, certaines espèces produisent des signaux qui semblent désigner des objets. Le singe vert émet des cris d’alarme distincts pour le léopard, l’aigle et le serpent, et chaque cri déclenche la fuite appropriée : grimper, lever les yeux, scruter le sol. La diffusion de l’enregistrement, en l’absence du prédateur, suffit à provoquer la bonne réaction, ce qui indique un signal « référentiel »<ref>Robert M. Seyfarth, Dorothy L. Cheney et Peter Marler, « Monkey Responses to Three Different Alarm Calls: Evidence of Predator Classification and Semantic Communication », ''Science'', vol. 210, 1980, p. 801-803.</ref>. Les expériences d’habituation de Klaus Zuberbühler suggèrent même que les singes réagissent à la signification du cri et non à sa seule sonorité, ce qui rapproche ces appels d’une référence conceptuelle<ref>Christen, ''L’animal est-il une personne ?'' décrit ces travaux ; pour l’analyse philosophique de la communication animale, voir Andrews, ''The Animal Mind'', chap. 6.</ref>.
Les tentatives d’enseigner un langage humain à des animaux ont donné des résultats spectaculaires et controversés. La chimpanzée Washoe (chez les Gardner) et le bonobo Kanzi (chez Sue Savage-Rumbaugh) parviennent à associer des centaines de signes ou de symboles à des sens, et leur compréhension dépasse souvent leur production. Le perroquet Alex (chez Irene Pepperberg) nommait couleurs, formes, matières et nombres, et savait répondre « aucun ». Mais Herbert Terrace, après avoir entraîné le chimpanzé Nim Chimpsky, a soutenu que ces « phrases » restaient dépourvues de syntaxe et tenaient surtout de l’imitation sollicitée. Le bilan est nuancé : les animaux disposent de capacités référentielles et de compréhension riches, mais la syntaxe récursive et ouverte, cet « instrument universel » que Descartes réservait à l’homme, n’a pas été démontrée. Le premier critère cartésien s’en trouve moins réfuté que précisé : l’écart subsiste, mais il est gradué, non abyssal.
=== Conscience de soi et théorie de l’esprit ===
La conscience que Descartes refusait aux bêtes a fait l’objet d’un test ingénieux. En 1970, Gordon Gallup marque d’une teinture inodore le front d’un chimpanzé puis lui présente un miroir : après une phase où il traite le reflet en congénère, l’animal porte la main à la marque sur son propre corps, signe qu’il s’y reconnaît<ref>Gordon G. Gallup, « Chimpanzees: Self-Recognition », ''Science'', vol. 167, 1970, p. 86-87.</ref>. Ce « test de la marque » est réussi par les grands singes, puis, moyennant des adaptations, par un dauphin, une éléphante d’Asie et une pie ; les singes inférieurs y échouent généralement<ref>Sur cinq décennies de recherches, voir James R. Anderson et David L. Butler, « Mirror Self-Recognition: Five Decades of Primate Research », dans Kaufman, Call et Kaufman (dir.), ''The Cambridge Handbook of Animal Cognition'', chap. 18.</ref>. Sa portée reste discutée : reconnaissance d’un corps propre ou conscience d’un soi au sens plein ?
Plus délicate encore est la « théorie de l’esprit », la capacité d’attribuer à autrui des états mentaux, dont David Premack et Guy Woodruff ont posé la question fondatrice en 1978 : « Le chimpanzé a-t-il une théorie de l’esprit ? »<ref>David Premack et Guy Woodruff, « Does the Chimpanzee Have a Theory of Mind? », ''Behavioral and Brain Sciences'', vol. 1, n° 4, 1978, p. 515-526.</ref>. Les premières études, conduites dans des contextes coopératifs, donnèrent des résultats décevants. Le progrès vint des dispositifs de compétition : un chimpanzé subordonné se dirige vers la nourriture que le dominant ne peut pas voir, montrant qu’il tient compte de ce que l’autre perçoit. Le corbeau freux, étudié par Thomas Bugnyar, recache sa réserve lorsqu’un rival a pu l’observer, et règle sa conduite sur la présence ou l’absence d’un témoin<ref>Sur la cognition sociale des corvidés, voir Thomas Bugnyar, « Raven Social Cognition and Behavior », dans Kaufman, Call et Kaufman (dir.), ''The Cambridge Handbook of Animal Cognition'', chap. 14. Frans de Waal et Laurie Santos ont rapporté des résultats convergents chez le chimpanzé et le capucin : voir Christen, ''L’animal est-il une personne ?''</ref>. Trente ans après la question de Premack et Woodruff, le bilan dressé par Josep Call et Michael Tomasello est mesuré : les grands singes comprennent les buts, la perception et le savoir d’autrui, mais rien n’établit qu’ils saisissent la fausse croyance. Leur compréhension relèverait d’une psychologie de la « perception et du but » plutôt que d’une lecture des croyances et des désirs au sens plein.
=== Métacognition : savoir que l’on sait ===
Une forme plus subtile de conscience de soi consiste à surveiller ses propres états cognitifs, à savoir que l’on sait ou que l’on ignore. Robert Hampton a montré qu’un macaque rhésus, à qui l’on offre de passer ou non un test de mémoire, choisit plus souvent d’y renoncer (contre une petite récompense assurée) à mesure que le délai s’allonge et que sa mémoire devient incertaine : tout se passe comme s’il savait qu’il ne se souvient plus<ref>Andrews, ''The Animal Mind'', chap. 4, qui discute les travaux de R. Hampton (2001) sur le macaque et de J. D. Smith (1995) sur le dauphin.</ref>. Un dauphin hésite de manière caractéristique au voisinage du seuil de difficulté, et des résultats comparables ont été obtenus chez le rat. Le psychologue J. David Smith y voit un « analogue fonctionnel » de la conscience humaine. Les sceptiques objectent que le sentiment d’incertitude pourrait n’être qu’épiphénoménal, ou que ces conduites s’expliquent par des mécanismes associatifs de premier ordre, sans accès réfléchi. Le débat reste ouvert, mais il porte désormais sur des données précises et non sur des principes a priori.
=== Traditions et cultures animales ===
Peut-on parler de culture animale ? Une étude collective dirigée par Andrew Whiten a recensé une trentaine de comportements (techniques d’outils, toilettage, parades) qui varient d’une communauté de chimpanzés à l’autre sans que l’écologie ni la génétique en rendent compte : ce sont des traditions locales, apprises et transmises<ref>Andrew Whiten, Jane Goodall, William C. McGrew ''et al.'', « Cultures in Chimpanzees », ''Nature'', vol. 399, 1999, p. 682-685.</ref>. On connaît d’autres exemples : les macaques japonais qui lavent les patates douces, les traditions de chant et de chasse des cétacés, les dialectes du chant des oiseaux. La culture est ici définie de façon minimale, comme un comportement socialement appris et propre à un groupe. Cette diversité complique l’idée, défendue par certains, selon laquelle l’individuation et la culture seraient le propre de l’homme<ref>Christen, ''L’animal est-il une personne ?'', discute longuement la variabilité individuelle et culturelle, contre la thèse de l’homogénéité comportementale des espèces.</ref> : la frontière, là encore, se déplace.
=== Émotions, empathie et sens moral ===
Reste la question, héritée de Thomas d’Aquin, de savoir si l’animal peut être sujet de justice. Les travaux sur les émotions sociales suggèrent l’existence, chez certaines espèces, de dispositions proto-morales : empathie et consolation chez les grands singes, étudiées par Frans de Waal. L’expérience la plus discutée concerne l’aversion à l’iniquité : un singe capucin qui acceptait volontiers une rondelle de concombre la refuse dès qu’il voit un partenaire recevoir un grain de raisin, plus apprécié, pour la même tâche<ref>Sarah F. Brosnan et Frans B. M. de Waal, « Monkeys Reject Unequal Pay », ''Nature'', vol. 425, 2003, p. 297-299. L’interprétation de ce refus comme « sens de l’équité » a été contestée, notamment par C. Wynne et J. Henrich, qui y voient plutôt une réaction de frustration devant une meilleure récompense disponible.</ref>. Les éthologues Marc Bekoff et Jessica Pierce ont parlé, à propos de ces conduites, d’une « justice sauvage » (''wild justice''). Le débat reste vif : sens réel de l’équité, ou simple frustration ? Quoi qu’il en soit, la recherche contemporaine pose la question, longtemps impensable, d’émotions morales sans agentivité morale pleine, et invite à distinguer le fait d’être un sujet moral de celui d’être un patient moral, c’est-à-dire un être envers qui l’on a des devoirs<ref>Sur la dimension morale, voir Andrews, ''The Animal Mind'', chap. 9.</ref>.
=== Vers une reconnaissance élargie de la conscience animale ===
L’accumulation de ces résultats a déplacé le consensus scientifique sur une question plus ancienne encore, celle de la conscience animale. En 2012, au terme de la conférence Francis Crick à Cambridge, un groupe de neuroscientifiques signe la ''Déclaration de Cambridge sur la conscience'' : l’absence de néocortex n’empêche pas un organisme d’éprouver des états affectifs, et les humains ne sont pas seuls à posséder les substrats neurologiques de la conscience, que partagent les mammifères, les oiseaux et d’autres animaux, dont le poulpe<ref>''The Cambridge Declaration on Consciousness'', proclamée le 7 juillet 2012 lors de la Francis Crick Memorial Conference, Churchill College, Cambridge.</ref>. La ''Déclaration de New York sur la conscience animale'' (2024) prolonge ce mouvement dans le sens d’une prudence élargie : elle juge l’appui scientifique solide pour les mammifères et les oiseaux, et tient pour une possibilité réaliste la conscience chez tous les vertébrés et chez de nombreux invertébrés, dont les céphalopodes, les crustacés décapodes et les insectes<ref>''The New York Declaration on Animal Consciousness'', 19 avril 2024, New York University. Plusieurs auteurs cités ici en sont signataires, dont Kristin Andrews, Colin Allen, Gordon Burghardt, Irene Pepperberg et Nicola Clayton.</ref>.
Ces textes n’affirment pas que ces animaux pensent au sens où nous pensons. Ils soutiennent une thèse plus mesurée et lourde de conséquences : la sensibilité et la conscience ne sont pas le privilège d’une espèce, et leur possibilité même, lorsqu’elle est sérieuse, engage notre responsabilité dans les décisions qui concernent ces êtres. On mesure le chemin parcouru depuis l’animal-machine : la charge de la preuve tend, en un sens, à se déplacer.
== Enjeux conceptuels : concept, croyance, langage ==
Par-delà les expériences, un problème proprement philosophique commande tous les autres : les animaux ont-ils des concepts et des [[Dictionnaire de philosophie/Croyance|croyances]], ou seulement des représentations non conceptuelles ? La réponse la plus restrictive vient de Donald Davidson. Selon lui, avoir une croyance suppose d’avoir le concept de croyance, donc de comprendre qu’une croyance peut être vraie ou fausse, ce qui exige le langage ; une créature sans langage ne saurait donc avoir de croyances<ref>Donald Davidson, « Thought and Talk », dans Samuel Guttenplan (dir.), ''Mind and Language'', Oxford University Press, 1975, p. 7-23.</ref>. L’argument a été contesté. Sa prémisse, que la croyance exige le concept de croyance, paraît trop forte : de jeunes enfants qui parlent semblent avoir des croyances sans posséder ce concept, et l’on ne voit pas pourquoi il en irait autrement de l’animal<ref>Pour la discussion de l’argument de Davidson et des réponses de Hans-Johann Glock, voir Andrews, ''The Animal Mind'', chap. 5.</ref>. La voie moyenne consiste à créditer les animaux d’une pensée non linguistique, ou de concepts en un sens plus mince (discriminatifs, pratiques) que la capacité humaine à former des concepts explicites, recombinables et universels, celle-là même qu’Aristote réservait au ''nous''.
On peut alors revenir aux deux critères de Descartes. Le langage : les animaux disposent de la référence et de la compréhension, mais non, semble-t-il, d’une syntaxe ouverte et récursive. La souplesse rationnelle : ils résolvent des problèmes, planifient, élaborent des stratégies sociales, mais dans des limites plus étroites que les nôtres. Le verdict contemporain n’est donc ni l’animal-machine ni la raison pleinement humaine, mais une continuité de degré assortie de différences réelles, conformément à l’intuition de Darwin. Plusieurs questions restent au cœur des débats, à la croisée de la philosophie de l’esprit, de la philosophie du langage, de l’éthique et des sciences cognitives :
* Les animaux possèdent-ils des ''concepts'' ou se limitent-ils à des représentations non conceptuelles ?
* Certaines espèces sont-elles capables de ''métacognition'', c’est-à-dire de savoir qu’elles savent ou qu’elles ignorent quelque chose ?
* Peut-on attribuer à certains animaux une ''théorie de l’esprit'', au sens d’une capacité à attribuer à autrui des croyances, des désirs ou des intentions ?
* Dans quelle mesure des comportements de planification (stockage de nourriture, usage différé d’outils, anticipation de comportements sociaux) impliquent-ils une délibération au sens fort ?
== Implications éthiques ==
Que devons-nous aux animaux ? Les réponses se laissent ranger en trois familles. La première ne reconnaît qu’un devoir indirect : nous n’aurions pas d’obligations envers les bêtes elles-mêmes, mais la cruauté à leur égard nous dégraderait et nous disposerait à la violence entre humains. La deuxième fonde un devoir direct sur la sensibilité : si un être peut souffrir, sa souffrance compte pour elle-même, indépendamment de sa rationalité. La troisième, plus exigeante, attribue à certains animaux des droits, voire un statut de personne non humaine. L’histoire qui précède éclaire ce partage : la tradition aristotélico-thomiste et l’héritage cartésien relèvent surtout de la première position, tandis que la reconnaissance contemporaine de la sensibilité et de la conscience nourrit les deux autres.
=== Le statut moral dans la tradition philosophique ===
Les conceptions philosophiques de l’intelligence animale ont de lourdes implications éthiques. La tradition aristotélico-thomiste, tout en reconnaissant aux animaux des capacités cognitives élaborées, tend à les exclure de la communauté morale au sens strict. Thomas d’Aquin soutient que, dépourvus de raison, les animaux ne sont pas des sujets de justice proprement dite, même si la vertu de tempérance ou la miséricorde peuvent inviter à modérer notre usage d’eux<ref>Thomas d’Aquin, ''Summa Theologiae'', II-II, q. 64, a. 1.</ref>. Le devoir de ne pas les maltraiter est alors souvent interprété comme un devoir indirect : maltraiter les animaux endurcirait le cœur humain et prédisposerait à la cruauté envers les hommes.
À l’inverse, la théorie cartésienne de l’animal-machine, en accentuant la différence entre l’homme et les bêtes et en semblant leur dénier toute sensibilité, a parfois été perçue comme légitimant une exploitation sans limites. Nicolas Malebranche (1638-1715) en tire une conclusion fameuse : « Les animaux mangent sans plaisir, ils crient sans douleur, ils croissent sans le savoir ; ils ne désirent rien, ils ne craignent rien, ils ne connaissent rien »<ref>Nicolas Malebranche, ''De la recherche de la vérité'', livre IV, 2{{e}} partie, chap. 7.</ref>. Le raisonnement de Malebranche est d’ordre théologique : un Dieu juste ne saurait infliger de souffrance à des créatures innocentes ; or les bêtes, n’ayant pas péché, ne méritent pas de souffrir ; il faut donc qu’elles ne sentent rien. Cette position, souvent jugée choquante, a suscité de nombreuses réactions critiques et a contribué à faire de la question de la sensibilité animale un enjeu moral majeur.
=== Vers une extension de la considération morale ===
Dès 1789, Jeremy Bentham avait déplacé la question. À propos des animaux, écrivait-il, « la question n’est pas : Peuvent-ils raisonner ? ni : Peuvent-ils parler ? mais : Peuvent-ils souffrir ? »<ref>Jeremy Bentham, ''An Introduction to the Principles of Morals and Legislation'' (1789), chap. XVII, note.</ref>. Cette formule, qui congédie le critère même de l’intelligence au profit de la sensibilité, commande une part de l’éthique animale contemporaine, de l’utilitarisme de Peter Singer à la théorie des droits de Tom Regan. La reconnaissance de capacités cognitives et affectives sophistiquées chez de nombreuses espèces alimente ces arguments en faveur d’une extension de la considération morale. Elle incite à reconsidérer nos pratiques d’élevage intensif, d’expérimentation animale et d’exploitation des ressources animales en général. Les discussions philosophiques classiques sur l’intelligence animale nourrissent ainsi les débats contemporains sur les droits des animaux, le statut de personne non humaine, ou encore les obligations de justice envers les êtres sensibles.
En ce sens, la question de l’intelligence animale ne relève pas seulement d’une curiosité théorique : elle engage notre compréhension de ce que nous sommes, de la place de l’homme dans le vivant et des formes de vie que nous jugeons légitimes d’imposer aux autres animaux.
== Notes et références ==
{{references|colonnes=2}}
== Bibliographie ==
=== Sources primaires ===
* Aristote, ''De anima'' (''Sur l’âme''), trad. fr. Richard Bodéüs, GF-Flammarion, 1993
* Aristote, ''De memoria et reminiscentia'', dans ''Parva Naturalia''
* Aristote, ''Seconds Analytiques'', trad. fr. Pierre Pellegrin, GF-Flammarion, 2005
* Aristote, ''Historia animalium'' (''Histoire des animaux''), trad. fr. Pierre Louis, Les Belles Lettres, 1964-1969
* Thomas d’Aquin, ''Summa Theologiae'', édition Léonine, Rome, 1888-1906
* Thomas d’Aquin, ''Sententia libri De anima'', édition Léonine, tome 45/1, Rome-Paris, 1984
* Thomas d’Aquin, ''Quaestiones Disputatae de Veritate'', édition Léonine, tome 22, Rome, 1975
* Michel de Montaigne, ''Essais'', II, 12, « Apologie de Raymond Sebond » (1580-1592) ([[s:fr:Essais|texte sur Wikisource]])
* René Descartes, ''Discours de la méthode'' (1637), dans ''Œuvres'', éd. Adam-Tannery, Paris, Vrin ([[s:fr:Discours de la méthode|texte sur Wikisource]])
* René Descartes, ''Méditations métaphysiques'' (1641), dans ''Œuvres'', éd. Adam-Tannery, Paris, Vrin
* René Descartes, ''L’Homme'' (posthume, 1662), dans ''Œuvres'', éd. Adam-Tannery, tome XI
* Nicolas Malebranche, ''De la recherche de la vérité'' (1674-1675), éd. Geneviève Rodis-Lewis, Vrin ([[s:fr:De la recherche de la vérité|texte sur Wikisource]])
* Pierre Gassendi, ''Cinquièmes Objections'' aux ''Méditations'' de Descartes (1641)
* Henry More, ''The Immortality of the Soul'' (1659)
* Thomas Willis, ''De anima brutorum'' (''Deux discours sur l’âme des bêtes'', 1672)
* Hieronymus Rorarius et Gabriel Naudé, ''Quod animalia bruta ratione utantur melius homine'', Paris, 1648
* John Locke, ''Essai sur l’entendement humain'' (1689), trad. fr. Pierre Coste ([[s:fr:Essai philosophique concernant l’entendement humain|texte sur Wikisource]])
* David Hume, ''Enquête sur l’entendement humain'' (1748), trad. fr. Michel Malherbe
* Étienne Bonnot de Condillac, ''Traité des animaux'' (1755) ([[s:fr:Traité des animaux|texte sur Wikisource]])
* Jeremy Bentham, ''An Introduction to the Principles of Morals and Legislation'' (1789)
* Charles Darwin, ''The Descent of Man, and Selection in Relation to Sex'' (1871) et ''The Expression of the Emotions in Man and Animals'' (1872) (trad. fr. [[s:fr:La Descendance de l’homme et la sélection sexuelle|''La Descendance de l’homme'' sur Wikisource]])
=== Études sur l’Antiquité et le Moyen Âge ===
* Balme, David M., "Aristotle’s Use of Division and Differentiae", dans Allan Gotthelf and James G. Lennox (eds.), ''Philosophical Issues in Aristotle’s Biology'', Cambridge University Press, 1987
* Cohoe, Caleb, "Why the Intellect Cannot Have a Bodily Organ: Thomas Aquinas on Phantasia and Intellectus", ''Vivarium'', vol. 54, 2016, pp. 347-374
* Cory, Therese Scarpelli, "Embodied vs. Non-Embodied Modes of Knowing in Aquinas: Different Universals, Different Intelligible Species, Different Intellects", ''American Catholic Philosophical Quarterly'', vol. 94, n° 1, 2020, pp. 27-58
* De Haan, Daniel, "The Interaction of Noetic and Psychosomatic Operations in Thomas Aquinas", ''Philosophical Investigations'', vol. 6, n° 2, 2018, pp. 1-37
* Deely, John N., "Animal Intelligence and Concept-Formation", ''The Thomist'', vol. 35, 1971, pp. 43-93
* Gasser-Wingate, Marc, "Aristotle on Intelligent Perception", ''Philosophers' Imprint'', vol. 22, n° 21, 2022, pp. 1-26
* Kahn, Nathan, "Aquinas and Aristotelians on Whether the Soul Has Parts", ''American Catholic Philosophical Quarterly'', vol. 91, n° 1, 2017, pp. 1-23
* Koons, Robert, "Aristotle’s Infallible Perception", ''Ancient Philosophy'', vol. 39, 2019, pp. 125-152
* Lagerlund, Henrik (ed.), ''Encyclopedia of Medieval Philosophy: Philosophy between 500 and 1500'', 2e éd., Springer, 2020
* Lovejoy, Arthur O., ''The Great Chain of Being: A Study of the History of an Idea'', Harvard University Press, 1936
* Pasnau, Robert, ''Thomas Aquinas on Human Nature'', Cambridge University Press, 2002
* Shields, Christopher, ''Aristotle: De Anima'', Clarendon Press, Oxford, 2016
* Sorabji, Richard, ''Animal Minds and Human Morals: The Origins of the Western Debate'', Cornell University Press, 1993
* Vogler, Candace, "The Intellectual Animal", ''New Blackfriars'', vol. 100, n° 1090, 2019, pp. 663-676
* White, A. Leo, "Instinct and Custom", ''The Thomist'', vol. 66, 2002, pp. 577-605
=== Études sur l’époque moderne ===
* Blair, Ann, "Natural Philosophy", dans Katherine Park and Lorraine Daston (eds.), ''The Cambridge History of Science, Vol. 3: Early Modern Science'', Cambridge University Press, 2006
* Cottingham, John, "'A Brute to the Brutes?': Descartes' Treatment of Animals", ''Philosophy'', vol. 53, n° 206, 1978, pp. 551-559
* Des Chene, Dennis, "Animal as Category: Bayle’s 'Rorarius'", dans Justin E.H. Smith (ed.), ''The Problem of Animal Generation in Early Modern Philosophy'', Cambridge University Press, 2006
* Frank, Robert G., "Thomas Willis and his Circle: Brain and Mind in Seventeenth-Century Medicine", dans G.S. Rousseau (ed.), ''The Languages of Psyche'', University of California Press, 1990
* Harrison, Peter, "The Virtues of Animals in Seventeenth-Century Thought", ''Journal of the History of Ideas'', vol. 59, n° 3, 1998, pp. 463-484
* Klestinec, Cynthia, "A History of Anatomy Theaters in Sixteenth-Century Padua", ''Journal of the History of Medicine and Allied Sciences'', vol. 59, n° 3, 2004, pp. 375-412
* Newman, Lex, "Descartes on the Method of Analysis", dans Georges J.D. Moyal (ed.), ''René Descartes: Critical Assessments'', vol. II, Routledge, 2001
* Park, Katherine, "The Organic Soul", dans Charles B. Schmitt et al. (eds.), ''The Cambridge History of Renaissance Philosophy'', Cambridge University Press, 1988, pp. 464-484
* Sutton, John, ''Philosophy and Memory Traces: Descartes to Connectionism'', Cambridge University Press, 1998
=== Philosophie contemporaine et éthologie cognitive ===
* Andrews, Kristin, ''The Animal Mind: An Introduction to the Philosophy of Animal Cognition'', 2e éd., Routledge, 2020
* Bekoff, Marc, Allen, Colin et Burghardt, Gordon M. (dir.), ''The Cognitive Animal: Empirical and Theoretical Perspectives on Animal Cognition'', MIT Press, 2002
* Brosnan, Sarah F. et de Waal, Frans B. M., "Monkeys Reject Unequal Pay", ''Nature'', vol. 425, 2003, pp. 297-299
* Call, Josep et Tomasello, Michael, "Does the Chimpanzee Have a Theory of Mind? 30 Years Later", ''Trends in Cognitive Sciences'', vol. 12, n° 5, 2008, pp. 187-192
* Christen, Yves, ''L’animal est-il une personne ?'', Flammarion, coll. « Champs sciences », 2009
* Clayton, Nicholas S. et Dickinson, Anthony, "Episodic-like Memory During Cache Recovery by Scrub Jays", ''Nature'', vol. 395, 1998, pp. 272-274
* Davidson, Donald, "Thought and Talk" (1975), dans ''Inquiries into Truth and Interpretation'', Oxford University Press, 1984
* de Waal, Frans, ''Good Natured: The Origins of Right and Wrong in Humans and Other Animals'', Harvard University Press, 1996
* Gallup, Gordon G., "Chimpanzees: Self-Recognition", ''Science'', vol. 167, 1970, pp. 86-87
* Griffin, Donald R., ''The Question of Animal Awareness: Evolutionary Continuity of Mental Experience'', Rockefeller University Press, 1976
* Kaufman, Allison B., Call, Josep et Kaufman, James C. (dir.), ''The Cambridge Handbook of Animal Cognition'', Cambridge University Press, 2021
* Morgan, Conwy Lloyd, ''An Introduction to Comparative Psychology'', Londres, Walter Scott, 1894
* Nagel, Thomas, "What Is It Like to Be a Bat?", ''The Philosophical Review'', vol. 83, n° 4, 1974, pp. 435-450
* Pepperberg, Irene M., ''The Alex Studies: Cognitive and Communicative Abilities of Grey Parrots'', Harvard University Press, 1999
* Premack, David et Woodruff, Guy, "Does the Chimpanzee Have a Theory of Mind?", ''Behavioral and Brain Sciences'', vol. 1, n° 4, 1978, pp. 515-526
* Regan, Tom, ''The Case for Animal Rights'', University of California Press, 1983
* Savage-Rumbaugh, Sue et Lewin, Roger, ''Kanzi: The Ape at the Brink of the Human Mind'', Wiley, 1994
* Seyfarth, Robert M., Cheney, Dorothy L. et Marler, Peter, "Monkey Responses to Three Different Alarm Calls", ''Science'', vol. 210, 1980, pp. 801-803
* Singer, Peter, ''Animal Liberation'', New York Review/Random House, 1975
* Weir, Alex A. S., Chappell, Jackie et Kacelnik, Alex, "Shaping of Hooks in New Caledonian Crows", ''Science'', vol. 297, 2002, p. 981
* Whiten, Andrew, Goodall, Jane, McGrew, William C. ''et al.'', "Cultures in Chimpanzees", ''Nature'', vol. 399, 1999, pp. 682-685
* ''The Cambridge Declaration on Consciousness'', Cambridge, 7 juillet 2012
* ''The New York Declaration on Animal Consciousness'', New York University, 19 avril 2024
== Voir aussi ==
* [[Dictionnaire de philosophie/Âme|Âme]]
* [[Dictionnaire de philosophie/Aristote|Aristote]]
* [[Dictionnaire de philosophie/Thomas d'Aquin|Thomas d'Aquin]]
* [[Dictionnaire de philosophie/René Descartes|René Descartes]]
* [[Dictionnaire de philosophie/Éthique animale|Éthique animale]]
* [[Dictionnaire de philosophie/Cognition animale|Cognition animale]]
* [[Dictionnaire de philosophie/Conscience|Conscience]]
* [[Dictionnaire de philosophie/Perception|Perception]]
== Liens externes ==
* Œuvres des principaux auteurs cités sur Wikisource : [[s:fr:Auteur:Aristote|Aristote]], [[s:fr:Auteur:Thomas d’Aquin|Thomas d’Aquin]], [[s:fr:Auteur:Plutarque|Plutarque]], [[s:fr:Auteur:Sextus Empiricus|Sextus Empiricus]], [[s:fr:Auteur:Michel de Montaigne|Montaigne]], [[s:fr:Auteur:René Descartes|Descartes]], [[s:fr:Auteur:Nicolas Malebranche|Malebranche]], [[s:fr:Auteur:John Locke|Locke]], [[s:fr:Auteur:David Hume|Hume]], [[s:fr:Auteur:Étienne Bonnot de Condillac|Condillac]], [[s:fr:Auteur:Jeremy Bentham|Bentham]], [[s:fr:Auteur:Charles Darwin|Darwin]]
[[Catégorie:Philosophie de l'esprit]]
{{autocat}}
gr1iaqxn5ag9ej5i4ohq0vsac2964a9
Dictionnaire de philosophie/Personne non-humaine
0
83054
768691
760846
2026-06-26T04:32:31Z
PandaMystique
119061
Bot : ajout du paramètre lecture=oui au modèle {{DicoPhilo}}
768691
wikitext
text/x-wiki
{{DicoPhilo|Personne non-humaine|lecture=oui}}
==Définition et évolution du concept==
La notion de « personne non-humaine » désigne dans le débat philosophique contemporain tout être vivant qui, sans appartenir à l'espèce ''Homo sapiens'', pourrait posséder les attributs moraux et juridiques traditionnellement associés à la personnalité. Cette catégorie englobe principalement les animaux dotés de capacités cognitives complexes, mais s'étend également aux entités collectives (écosystèmes, rivières)<ref>UNESCO, ''Universal Declaration on Bioethics and Human Rights'', 2005</ref>, voire aux intelligences artificielles selon certains courants transhumanistes<ref>U.S. Transhumanist Party, ''Transhumanist Bill of Rights Version 3.0'', 2018</ref>.
Le développement de cette notion découle d'une triple évolution : l'accumulation de données scientifiques sur les capacités cognitives animales, la remise en question de l'anthropocentrisme philosophique hérité des Lumières, et l'émergence de technologies biomédicales qui brouillent les frontières traditionnelles entre espèces<ref>WHO Expert Advisory Committee, ''Human genome editing: a framework for governance'', Genève, WHO, 2021</ref>. La question acquiert une dimension pratique urgente avec le développement de l'édition génomique (CRISPR-Cas9) et les propositions transhumanistes d'amélioration humaine, qui imposent de redéfinir les critères de la personnalité.
==Les fondements philosophiques du statut moral==
Le débat philosophique sur le statut moral des personnes non-humaines s'articule autour de plusieurs positions théoriques majeures développées principalement depuis les années 1970.
===La tradition utilitariste : Singer et l'antispécisme===
Peter Singer, dans ''Animal Liberation'' (1975), propose une extension du principe utilitariste d'égale considération des intérêts au-delà des frontières d'espèce<ref>Peter Singer, ''Animal Liberation'', New York, Random House, 1975 ; trad. fr. Louise Rousselle, ''La Libération animale'', Paris, Payot, 2012</ref>. Pour Singer, la capacité à souffrir constitue le critère déterminant du statut moral : « la douleur et la souffrance sont mauvaises et doivent être prévenues ou minimisées, peu importe l'espèce de l'être qui les ressent »<ref>Singer cité dans ''The Case for Animal Personhood'', Famous Trials, 2001</ref>. Cette position implique que la discrimination fondée sur l'appartenance à une espèce (le « spécisme ») est aussi arbitraire et injustifiable que le racisme ou le sexisme<ref>Peter Singer, ''Practical Ethics'', Cambridge, Cambridge University Press, 1993, 2e édition</ref>.
Singer adopte un « utilitarisme des préférences » qui va au-delà de l'hédonisme classique. Il ne suffit pas de considérer les plaisirs et les peines, mais il faut prendre en compte l'ensemble des préférences ou intérêts des êtres concernés<ref>David DeGrazia, ''Taking Animals Seriously: Mental Life and Moral Status'', Cambridge, Cambridge University Press, 1996, p. 35-47</ref>. Cette approche conduit Singer à reconnaître des différences morales entre êtres sensibles : un animal doté de conscience de soi, capable d'anticiper son avenir et d'avoir des aspirations possède des intérêts plus complexes qu'un être simplement conscient<ref>Singer cité dans Jacqueline Laing, ''Animal Rights Theory and Utilitarianism: Relative Normative Guidance'', Animal Law Review, vol. 3, 1996, p. 107-153</ref>. Paradoxalement, cela amène Singer à accepter qu'il puisse être moralement permis de tuer des animaux élevés dans de bonnes conditions s'ils ne possèdent pas de « désirs pour l'avenir » ou de « conscience mentale continue », leur mort pouvant être « compensée » par la création d'un nouvel animal ayant une vie également plaisante<ref>Peter Singer, ''Animal Liberation'', 2e édition, 1989, cité dans Animal Law, 1996</ref>.
Cette position a suscité de nombreuses critiques. Certains y voient une contradiction interne : si Singer refuse le spécisme, pourquoi les capacités cognitives seraient-elles moralement pertinentes pour déterminer l'importance de la vie d'un être<ref>Jacqueline Laing critique l'inconsistance de Singer, selon Animal Law, 1996</ref> ? De plus, l'approche utilitariste de Singer ne reconnaît pas de droits au sens fort : si l'expérimentation sur un animal (ou même un humain non consentant) produisait des bénéfices suffisants, elle pourrait être justifiée<ref>Singer reconnaît cette implication dans le débat avec Richard Posner, 2001</ref>.
===L'approche déontologique : Regan et les droits inhérents===
Tom Regan, dans ''The Case for Animal Rights'' (1983), développe une théorie déontologique qui s'oppose à l'instrumentalisation des animaux<ref>Tom Regan, ''The Case for Animal Rights'', Berkeley, University of California Press, 1983 ; 2e éd. 2004</ref>. Regan s'inspire de Kant tout en s'en démarquant : alors que Kant réserve le statut de fin en soi aux êtres rationnels, Regan l'étend à tous les « sujets d'une vie » (''subjects-of-a-life'')<ref>Tom Regan, ''The Case for Animal Rights'', Berkeley, University of California Press, 1983, p. 243-248</ref>.
Selon Regan, un être est « sujet d'une vie » s'il possède des croyances et des désirs, une perception, une mémoire et un sens du futur (y compris de son propre futur), une vie émotionnelle avec des sentiments de plaisir et de douleur, des intérêts-préférences et des intérêts-bien-être, la capacité d'initier des actions en vue de réaliser ses désirs et buts, une identité psychophysique dans le temps, et un bien-être individuel au sens où sa vie expérientielle peut aller bien ou mal pour lui<ref>Tom Regan, ''The Case for Animal Rights'', University of California Press, 1983, p. 243</ref>. Tous les êtres qui satisfont ce critère possèdent une valeur inhérente égale et ne doivent jamais être traités simplement comme des moyens en vue d'autres fins.
L'argument de Regan est nettement déontologique plutôt que conséquentialiste : si un individu possède un droit moral à ne pas être utilisé comme simple moyen, ce droit ne doit pas être sacrifié même si les conséquences de son respect sont considérées comme désavantageuses<ref>Tom Regan, ''The Case for Animal Rights'', dans Peter Singer (dir.), ''In Defense of Animals'', New York, Basil Blackwell, 1985</ref>. Regan écrit que les défenseurs des droits des animaux veulent « des cages vides, pas de plus grandes »<ref>Citation de Regan dans ''Abolitionism (animal rights)'', Wikipedia, consulté en 2025</ref>. Contrairement aux welfaristes qui cherchent à améliorer les conditions de vie des animaux utilisés, Regan et les abolitionnistes visent l'arrêt complet de l'exploitation animale.
La théorie de Regan soulève néanmoins plusieurs difficultés. Premièrement, le critère de « sujet d'une vie » reste contesté sur le plan empirique : quels animaux possèdent véritablement un sens de leur futur propre ? Deuxièmement, même si tous les sujets-d'une-vie ont une valeur inhérente égale, cela signifie-t-il qu'ils ont exactement les mêmes droits ou des droits équivalents mais différents selon leurs besoins spécifiques ? Troisièmement, dans les cas de conflit inévitable (par exemple entre prédateurs et proies), comment arbitrer entre des droits égaux ?
===L'approche contractualiste révisée : Rowlands et les agents moraux patients===
Mark Rowlands tente d'étendre le contractualisme à la question animale en distinguant agents moraux et patients moraux. Traditionnellement, seuls les agents moraux (capables de respecter des principes moraux) sont inclus dans le contrat social. Rowlands propose qu'un contrat juste doive aussi prendre en compte les intérêts des patients moraux (êtres vulnérables incapables de réciprocité). Cette approche reconnaît que beaucoup d'êtres (animaux, jeunes enfants, personnes sévèrement handicapées) méritent une considération morale sans être capables de participer activement au contrat social.
===Le tournant des capacités : Nussbaum et Sen===
Martha Nussbaum propose d'étendre aux animaux sensibles l'approche par les capacités qu'elle a développée avec Amartya Sen pour penser la justice humaine<ref>Martha C. Nussbaum, ''Frontiers of Justice: Disability, Nationality, Species Membership'', Cambridge (MA), Harvard University Press, 2006</ref>. Contrairement aux approches contractualistes traditionnelles qui excluent les êtres non rationnels, l'approche par les capacités part des besoins réels des individus pour définir les seuils de fonctionnement nécessaires à une vie digne.
Nussbaum identifie dix capacités centrales dont les animaux sensibles devraient pouvoir jouir : la vie, la santé corporelle, l'intégrité corporelle, les sens/imagination/pensée, les émotions, la raison pratique, l'affiliation, l'interaction avec d'autres espèces, le jeu, et le contrôle sur son environnement<ref>Martha Nussbaum, ''Frontiers of Justice'', 2006, p. 76-78</ref>. Chaque espèce devrait pouvoir développer ses capacités caractéristiques jusqu'à un seuil minimum. Nussbaum affirme également que les animaux possèdent une dignité propre : « chaque animal sensible devrait pouvoir vivre une vie florissante avec le type de dignité approprié à l'espèce à laquelle il appartient »<ref>Martha Nussbaum, « Should We Ascribe Capabilities to Sentient Animals ? », ''De Ethica'', vol. 3, n° 1, 2016, p. 61-76</ref>.
Cette approche a suscité plusieurs critiques. Certains soulignent que le concept de capacité suppose normalement une possibilité de choix entre différents fonctionnements, ce qui n'a guère de sens pour les animaux<ref>Critique du concept de capacité appliqué aux animaux, « Should We Ascribe Capabilities to Animals ? », ''De Ethica'', 2016</ref>. D'autres notent que Nussbaum maintient une différence entre nos relations morales avec les animaux et avec les humains : les devoirs envers les humains sont contraignants parce que ceux-ci peuvent faire valoir des revendications légitimes en faisant appel à une loi morale partagée, tandis que nos devoirs envers les animaux découlent de notre engagement envers notre propre animalité, qui devrait nous conduire à les considérer aussi comme ayant des revendications légitimes.
===Christine Korsgaard et la révision kantienne===
Christine Korsgaard, dans ''Fellow Creatures'' (2018), propose une révision ambitieuse de l'éthique kantienne pour y inclure les animaux<ref>Christine M. Korsgaard, ''Fellow Creatures: Our Obligations to the Other Animals'', Oxford, Oxford University Press, 2018</ref>. Elle argumente que notre agentivité rationnelle nous engage à considérer moralement toutes les créatures pour lesquelles les choses peuvent être bonnes ou mauvaises. Nous valorisons nos fins comme absolument bonnes non pas en raison d'une intuition mystique sur ce qui est intrinsèquement précieux, mais parce que les atteindre est bon pour nous. Par souci de cohérence, nous devons reconnaître toutes les autres créatures pour lesquelles les choses peuvent être bonnes ou mauvaises comme des fins en soi<ref>Reconstruction de l'argument de Korsgaard dans Frédéric Müller, « Korsgaard's Duties towards Animals: Two Difficulties », ''Philosophical Quarterly'', vol. 72, n° 4, 2022, p. 911-931</ref>.
Néanmoins, Korsgaard maintient que « nos relations morales avec les autres animaux ont une base et une forme différentes de nos relations morales avec d'autres personnes »<ref>Christine Korsgaard citée dans Frédéric Müller, « Korsgaard's Duties towards Animals: Two Difficulties », 2022</ref>. Les devoirs envers les humains rationnels sont contraignants parce qu'ils peuvent faire valoir des revendications légitimes en appelant à une loi morale partagée. Les devoirs envers les animaux sont contraignants parce que nous avons un engagement envers notre propre statut de fin-en-soi en tant qu'animaux, ce qui devrait nous amener à considérer les animaux comme ayant aussi des revendications légitimes.
Plusieurs difficultés subsistent. Premièrement, Korsgaard ne révise pas vraiment la conception kantienne selon laquelle les devoirs directs envers autrui requièrent une contrainte morale mutuelle : ce qu'elle appelle devoirs « envers » (''toward'') les animaux sont simplement des devoirs kantiens « à l'égard » (''regarding'') des animaux, verbalement rempaquetés<ref>Critique de Müller : les devoirs ''toward'' ne sont que des devoirs ''regarding'', 2022, p. 922</ref>. Deuxièmement, l'approche de Korsgaard ne montre pas de manière convaincante que les devoirs à l'égard des animaux ont le même poids que nos devoirs envers les êtres humains.
==Perspectives critiques et alternatives==
===La critique du modèle de l'agent moral autonome : Kittay et l'éthique du care===
Eva Feder Kittay, philosophe féministe et mère d'une fille gravement handicapée cognitive, développe une critique profonde des théories morales centrées sur l'autonomie et l'agentivité<ref>Eva Feder Kittay, « The Ethics of Care, Dependence, and Disability », ''Ratio Juris'', vol. 24, n° 1, 2011, p. 49-58</ref>. Selon Kittay, placer l'individu autonome au premier plan, éclipser l'importance de notre dépendance mutuelle, et faire des échanges réciproques entre égaux le modèle de l'interaction éthique n'est pas souhaitable pour construire une éthique de l'inclusion<ref>Eva Feder Kittay, « The Ethics of Care, Dependence, and Disability », 2011, p. 51</ref>.
Kittay propose de reconnaître que la dépendance est inévitable et normale dans la vie humaine : nous sommes tous vulnérables à des périodes de dépendance (petite enfance, vieillesse, maladie, handicap). Au lieu de voir la dépendance comme une limitation, nous devrions la reconnaître comme une opportunité de connexion. L'éthique du care met l'accent sur trois dimensions du soin : le travail de soin (''labor''), l'attitude de sollicitude, et la vertu de prendre soin<ref>Trois dimensions du care selon Kittay, 2011, p. 52</ref>.
Cette approche est particulièrement pertinente pour penser notre rapport aux animaux. Comme les personnes gravement handicapées cognitives, beaucoup d'animaux sont vulnérables, dépendants (surtout les animaux domestiques), et incapables de réciprocité dans les termes habituels de l'échange moral. Une éthique du care permet de valoriser ces relations asymétriques sans les réduire à l'instrumentalisation.
===Claudia Passos-Ferreira et l'empathie imaginative===
Claudia Passos-Ferreira développe une approche phénoménologique de notre rapport aux personnes gravement handicapées intellectuellement qui peut être étendue aux animaux<ref>Claudia Passos-Ferreira, « Empathizing with the Intellectually Disabled », ''Handbook of Bioethical Decisions, vol. I'', Cham, Springer, 2023, p. 287-302</ref>. Elle distingue l'empathie perceptuelle (basée sur des indices directement observables) et l'empathie imaginative (simulation projective de la perspective affective d'autrui).
Pour Passos-Ferreira, même si les personnes gravement handicapées cognitives ne possèdent pas un point de vue réflexif (capacité à réfléchir sur leurs actions, intérêts et préférences), elles possèdent un point de vue pré-réflexif constitué de leurs capacités sensorielles, émotionnelles et motivationnelles. Ce point de vue pré-réflexif suffit pour justifier un respect fondé sur l'identification.
De même, les animaux possèdent un point de vue pré-réflexif avec lequel nous pouvons nous identifier par empathie. L'empathie imaginative est une compétence qui peut être développée et affinée par la pratique. Les soignants, vétérinaires et chercheurs peuvent apprendre à mieux imaginer les états mentaux des animaux dont ils s'occupent.
===La phénoménologie animale : Veit et Browning===
Walter Veit et Heather Browning appliquent la phénoménologie à la santé et à la souffrance animales<ref>Walter Veit et Heather Browning, « Phenomenology Applied to Animal Health and Suffering », dans James Giordano et Evan Micah Thompson (dir.), ''Phenomenology of Bioethics: Technoethics and Lived-Experience'', Cham, Springer, 2021, p. 93-108</ref>. Ils s'inspirent du travail de Havi Carel sur la phénoménologie de la maladie humaine et l'étendent aux animaux. Leur approche s'appuie sur une phénoménologie incarnée (''embodied phenomenology'') héritée de Merleau-Ponty : notre expérience consciente est ancrée dans notre corps et notre perception sensible.
Veit et Browning montrent comment des méthodes empiriques peuvent nous donner accès à la phénoménologie animale. L'évaluation comportementale qualitative (''Qualitative Behavioural Assessment'' - QBA) permet à des observateurs entraînés d'évaluer l'expérience globale d'un animal en intégrant son comportement et son langage corporel. Les tests de biais cognitif révèlent si un animal est dans un état émotionnel positif (optimisme) ou négatif (pessimisme) en observant comment il réagit à des stimuli ambigus.
L'exemple du complexe du membre fantôme chez les chiens après amputation illustre l'importance de la phénoménologie animale. Certains chiens montrent des changements de personnalité (augmentation de l'agressivité et de l'anxiété) après amputation, suggérant qu'ils continuent à éprouver leur corps « habituel » et ressentent frustration et angoisse de ne plus pouvoir vivre comme avant<ref>Menchetti et al. (2017) sur le membre fantôme chez les chiens, cité par Veit et Browning, 2021</ref>.
==La question de la personnalité juridique animale==
===Contexte et évolution du droit===
Depuis le droit romain, la catégorie juridique de la « personne » a été la plus fondamentale, permettant aux humains et à certaines entités de participer à la vie juridique et d'entrer dans des relations juridiques. Traditionnellement, les animaux ne sont pas considérés comme des personnes juridiques mais comme des biens (''property'') ou, dans certaines législations récentes, comme des « êtres sensibles » bénéficiant d'une protection particulière sans accéder au statut de personne.
La loi française de 2015 a modifié le Code civil pour définir les animaux comme « des êtres vivants doués de sensibilité », marquant une évolution sans pour autant leur attribuer la personnalité juridique ni créer un régime juridique nouveau : sauf disposition spéciale, les animaux restent soumis au régime des biens<ref>Loi française n° 2015-177 du 16 février 2015, citée dans Sofia Riot, « Legal Personhood of Animals (I) », ''Derecho Animal'', vol. 9, n° 2, 2018, p. 30-55</ref>.
===Les arguments en faveur de la personnalité juridique===
Les défenseurs de la personnalité juridique pour certains animaux avancent plusieurs arguments :
1. '''Argument des capacités cognitives''' : Les grands primates, les éléphants et les cétacés possèdent des capacités cognitives complexes (conscience de soi démontrée par le test du miroir, théorie de l'esprit, mémoire autobiographique, culture transmise, utilisation d'outils)<ref>David Favre et Randall S. Abate, « Legal Personhood for Animals: Has Science Made Its Case ? », ''Animals'', vol. 13, n° 14, 2023, article 2339</ref>. Ces capacités sont comparables à celles de jeunes enfants humains qui sont des personnes juridiques.
2. '''Argument de la cohérence juridique''' : Le droit a évolué au fil du temps pour étendre la personnalité juridique à des groupes initialement exclus (esclaves affranchis, femmes, entreprises, navires). L'extension aux animaux serait une progression logique.
3. '''Argument de l'inadéquation du régime de protection''' : Les lois sur le bien-être animal, qui traitent les animaux comme des biens protégés, privilégient systématiquement les intérêts humains sur ceux des animaux<ref>Peter Singer sur les limites du welfarisme, Famous Trials, 2001</ref>. La personnalité juridique offrirait une protection plus forte en reconnaissant des droits propres aux animaux.
4. '''Argument écologique''' : La reconnaissance de la personnalité juridique à des entités naturelles (rivières, forêts) crée un précédent pour étendre cette reconnaissance aux animaux.
===Les tentatives juridiques et leurs échecs===
Le Nonhuman Rights Project, organisation fondée par l'avocat Steven Wise en 1995, a lancé plusieurs actions en justice aux États-Unis demandant la reconnaissance du statut de personne juridique pour des chimpanzés et des éléphants détenus en captivité, en utilisant le mécanisme de l’''habeas corpus''<ref>Steven M. Wise et Nonhuman Rights Project, documentation disponible sur www.nonhumanrights.org</ref>. L’''habeas corpus'' est une procédure juridique traditionnellement utilisée par les prisonniers pour contester la légalité de leur détention.
Dans l'affaire ''Nonhuman Rights Project v. Lavery'' (État de New York, 2013-2018), le projet demandait la libération de quatre chimpanzés (Tommy, Kiko, Hercules et Leo) détenus dans des conditions jugées inappropriées. Les tribunaux ont systématiquement rejeté ces demandes, affirmant que la personnalité juridique est réservée aux êtres humains et que seule la législature, et non les tribunaux, peut étendre ce statut.
En janvier 2025, la Cour suprême du Colorado a rejeté la demande du Nonhuman Rights Project de transférer cinq éléphants âgés du zoo de Cheyenne Mountain vers un sanctuaire<ref>« The Right to Personhood: One Professor's Fight for Animals », University of Denver News, 17 février 2025</ref>. Le groupe alléguait que le confinement d'animaux de ce niveau d'intelligence produit « frustration chronique, stress, handicaps physiques et lésions cérébrales ». La Cour a statué que l'''habeas corpus'' ne s'applique pas aux « animaux non humains », seulement aux « personnes ».
Ces décisions judiciaires révèlent plusieurs arguments récurrents des tribunaux contre la personnalité juridique animale :
1. '''Séparation des pouvoirs''' : Les tribunaux estiment que des changements sociétaux aussi profonds doivent émaner de la législature, pas de décisions judiciaires.
2. '''Absence de devoirs''' : Traditionnellement, les personnes juridiques ont non seulement des droits mais aussi des devoirs. Les animaux ne peuvent assumer de devoirs ou de responsabilités légales<ref>Objection des devoirs discutée dans Simone Pezzetta, « Animals as Subjects or Citizens: Can Animals have rights without duties ? », ''Derecho Animal'', vol. 15, n° 1, 2025, p. 9-28</ref>.
3. '''Conséquences pratiques''' : Étendre la personnalité juridique aux animaux soulèverait d'immenses questions pratiques : Tous les animaux ou seulement certaines espèces ? Que faire des relations prédateur-proie ? L'élevage deviendrait-il illégal ? Les propriétaires d'animaux domestiques seraient-ils coupables de « séquestration » ?
4. '''Différence de nature''' : Les tribunaux maintiennent qu'il existe une différence fondamentale entre humains et animaux qui justifie des traitements juridiques distincts, même si cette différence n'est pas toujours clairement articulée.
===Approches alternatives et graduelles===
Face à ces échecs répétés, certains juristes proposent des approches plus graduelles<ref>Maneesha Deckha, « Transition rather than Revolution: The Gradual Road towards Animal Legal Personhood through the Legislature », ''Transnational Environmental Law'', vol. 11, n° 2, 2022, p. 313-337</ref> :
1. '''Renforcement des droits simples''' : Au lieu de viser immédiatement la personnalité juridique pleine et entière, améliorer progressivement les droits légaux existants des animaux pour les faire fonctionner de plus en plus comme les droits humains<ref>Visa A. J. Kurki, « Towards a Theory of Legal Animal Rights: Simple and Fundamental Rights », ''Oxford Journal of Legal Studies'', vol. 40, n° 3, 2020, p. 533-561</ref>.
2. '''Catégories intermédiaires''' : Créer une catégorie juridique intermédiaire entre « bien » et « personne », reconnaissant les animaux comme « êtres sensibles » avec un régime de protection spécifique.
3. '''Approche espèce par espèce''' : Commencer par reconnaître la personnalité juridique pour un nombre limité d'espèces aux capacités cognitives exceptionnellement développées (grands primates, éléphants, cétacés, corvidés).
4. '''Tutelle et représentation''' : Développer des mécanismes de représentation juridique pour les animaux sans nécessairement leur conférer la personnalité juridique complète, comme c'est le cas pour les enfants ou les personnes gravement handicapées.
==Les enjeux transhumanistes et les frontières de l'humain==
===Le défi transhumaniste===
Le mouvement transhumaniste, qui promeut l'amélioration technologique des êtres humains, introduit de nouvelles complexités dans le débat sur les personnes non-humaines. Le ''Transhumanist Bill of Rights 3.0'', adopté en 2018 par le U.S. Transhumanist Party, définit les « entités sensibles » (''sentient entities'') ayant droit au respect comme incluant non seulement les humains et les humains génétiquement modifiés, mais aussi les cyborges, les intelligences numériques, les animaux intellectuellement améliorés, et toute espèce végétale ou animale ayant été améliorée pour posséder la capacité de pensée intelligente<ref>U.S. Transhumanist Party, ''Transhumanist Bill of Rights Version 3.0'', 2018</ref>.
Cette extension du concept de personne soulève plusieurs questions philosophiques et éthiques fondamentales :
1. '''Qui décide des critères de personnalité ?''' Si les modifications génétiques et technologiques peuvent créer de nouveaux types d'entités cognitives, qui déterminera lesquelles méritent le statut de personne ?
2. '''Hiérarchie des personnes ?''' Y aura-t-il une hiérarchie entre personnes « naturelles » et personnes « améliorées » ? Entre personnes biologiques et personnes artificielles ?
3. '''Dissolution de la catégorie ?''' Si la personnalité peut être conférée à un nombre croissant d'entités différentes, la catégorie de « personne » ne risque-t-elle pas de perdre toute signification opératoire ?
===L'édition génomique et les chimères===
Le développement de l'édition génomique humaine (CRISPR-Cas9) et la création possible de chimères homme-animal posent des questions inédites sur les frontières de l'humanité. Le cadre de l'OMS pour la gouvernance de l'édition génomique mentionne explicitement les préoccupations concernant les chimères homme-animal et les « embryoïdes synthétiques » (''synthetic embryoid beings'')<ref>WHO Expert Advisory Committee on Developing Global Standards for Governance and Oversight of Human Genome Editing, ''Human genome editing: a framework for governance'', Genève, WHO, 2021</ref>.
Plusieurs questions éthiques émergent :
1. '''Statut moral des chimères''' : Une chimère contenant un pourcentage significatif de matériel génétique humain devrait-elle être considérée comme une personne humaine, une personne non-humaine, ou quelque chose d'entièrement nouveau<ref>Christopher Tollefsen et Patrick Lee, « Embryo Research Ethics », dans Thomas Schramme et Steven Edwards (dir.), ''Medical Research Ethics: Challenges in the 21st Century'', Cham, Springer, 2023, p. 1-17</ref> ?
2. '''Capacités cognitives humanisées''' : Si une chimère développait des capacités cognitives comparables à celles d'un humain (en raison de tissus cérébraux partiellement humains), devrait-elle bénéficier de protections équivalentes à celles des humains ?
3. '''Critère d'appartenance à l'espèce''' : À l'ère de l'édition génique, qu'est-ce qui détermine l'appartenance à l'espèce humaine ? Le pourcentage de gènes humains ? La généalogie évolutive ? Les capacités fonctionnelles ? L'apparence phénotypique<ref>Marco Stier, « What is Human Gene Editing ? », dans Britta van Beers, Daniela Bambara et Anna Manousi (dir.), ''Governance of Human Gene Editing and Transhumanism'', Cham, Springer, 2024, p. 63-86</ref> ?
Ces interrogations rejoignent des questions plus anciennes en philosophie de la biologie : qu'est-ce qu'une espèce ? Les trois facteurs généralement considérés (généalogie évolutive, similitude génomique, potentiel de reproduction inter-fertile) deviennent problématiques dans un contexte d'ingénierie génétique délibérée.
==Critiques et limites du concept de personne non-humaine==
===L'objection de l'absence de devoirs===
Une objection classique à l'extension de la personnalité aux animaux est qu'ils ne peuvent assumer de devoirs ou de responsabilités<ref>Simone Pezzetta, « Animals as Subjects or Citizens », ''Derecho Animal'', vol. 15, n° 1, 2025</ref>. La personnalité juridique implique traditionnellement non seulement des droits mais aussi des obligations. Comment un animal pourrait-il être tenu responsable de ses actes ?
Deux réponses sont généralement apportées :
1. '''L'argument des « humains marginaux »''' : De nombreux humains ne peuvent assumer de devoirs (nouveau-nés, personnes sévèrement handicapées cognitives, personnes en état végétatif) et pourtant sont des personnes juridiques avec des droits. L'incapacité de fait à assumer des obligations n'est donc pas un obstacle à être sujet de droits.
2. '''L'argument de la pertinence morale''' : L'incapacité à assumer des devoirs n'est pas moralement pertinente pour mériter des protections. Ce qui compte moralement est la capacité à avoir des intérêts, à souffrir, à être vulnérable aux préjudices.
===Le problème du monde naturel et de la prédation===
Si les animaux ont des droits au sens fort, que faire des relations prédateur-proie dans la nature ? Avons-nous l'obligation d'intervenir pour protéger les gazelles des lions, les souris des hiboux ?
Tom Regan répond que seuls les agents moraux (les humains) peuvent violer des droits. Les prédateurs non-humains ne violent pas les droits de leurs proies car ils ne sont pas des agents moraux<ref>Tom Regan, « Animal Rights and Environmental Ethics », texte disponible en ligne, 1983</ref>. Nous n'avons donc pas d'obligation générale d'intervenir dans les relations prédateur-proie naturelles, même si cela ne signifie pas qu'une intervention ne serait jamais justifiée (par exemple si nous avions créé artificiellement la situation).
Cette réponse reste controversée. Certains critiques, comme J. Baird Callicott, ont accusé la théorie des droits animaux de Regan d'impliquer logiquement « une politique d'extermination humaine des prédateurs ». Regan conteste cette interprétation excessive, mais la tension entre droits individuels des animaux et écologie des écosystèmes reste un défi théorique majeur.
===La question des degrés de statut moral===
David DeGrazia propose que le statut moral admette des degrés plutôt que d'être un statut binaire (personne/non-personne)<ref>David DeGrazia, « Moral Status as a Matter of Degree ? », ''Southern Journal of Philosophy'', vol. 46, n° 2, 2008, p. 181-198</ref>. Selon ce modèle, tous les êtres sensibles méritent une égale considération pour leurs intérêts comparables, mais certains de leurs intérêts peuvent différer en importance. Par exemple, un chimpanzé peut avoir un intérêt dans sa liberté de mouvement comparable à celui d'un humain, mais peut-être pas un intérêt aussi fort dans l'accès à l'éducation supérieure.
Cette approche gradualiste présente des avantages : elle permet de reconnaître les différences réelles entre espèces tout en maintenant un principe d'égale considération. Elle évite aussi le problème du « tout ou rien » : plutôt que de devoir trancher si un être est ou n'est pas une personne, on peut reconnaître des degrés de statut moral correspondant aux capacités réelles des différents êtres.
Néanmoins, l'approche gradualiste comporte aussi des risques : elle peut servir à justifier une dévalorisation systématique des intérêts animaux par rapport aux intérêts humains, reproduisant ainsi le spécisme qu'elle prétendait combattre. Comment s'assurer que les « degrés » de statut moral reflètent les capacités réelles des animaux plutôt que nos préjugés anthropocentriques ?
===Les limites d'une approche uniquement cognitive===
Plusieurs philosophes critiquent la tendance à fonder le statut moral uniquement sur les capacités cognitives. Cora Diamond, Mary Midgley et Alice Crary argumentent que l'approche dominante de l'éthique animale (''capacities-based approach'') déforme notre compréhension de nos relations avec les autres humains et ne fournit pas une base convaincante pour le traitement éthique des animaux<ref>Discussion de Diamond, Midgley et Crary dans ''Handbook of Bioethical Decisions, vol. I'', 2023, p. 45-68</ref>.
Diamond soutient que ce sont nos pratiques qui fondent notre compréhension de qui sont les êtres avec lesquels nous interagissons et qui fournissent les raisons de les traiter d'une certaine manière. Notre refus de manger des cadavres humains, par exemple, n'est pas basé sur le respect de leurs intérêts ou capacités (un cadavre n'a ni intérêt ni capacité), mais sur le fait qu'être humain signifie être quelque chose qui n'est pas destiné à être mangé.
Midgley critique l'approche dominante pour son attention inadéquate aux contextes, faits et détails de nos relations complexes avec les animaux. Abstraite des relations concrètes dans lesquelles elle prend son sens, la rhétorique des droits ou du respect devient opaque et ambiguë.
Ces critiques suggèrent qu'une éthique animale adéquate ne peut se fonder uniquement sur l'identification de capacités cognitives, mais doit intégrer la complexité de nos pratiques, émotions et relations avec les animaux.
==Conclusion et perspectives==
La question des personnes non-humaines reste au cœur des débats contemporains en philosophie morale, éthique animale, bioéthique et théorie juridique. Les vingt dernières années ont vu une intensification et une sophistication considérables de ces discussions, nourries par trois développements majeurs :
1. '''Les progrès des sciences cognitives et de l'éthologie''' ont considérablement enrichi notre compréhension des capacités mentales des animaux. L'accumulation de preuves concernant la conscience de soi, la mémoire épisodique, l'utilisation d'outils, la transmission culturelle et les capacités socio-cognitives complexes chez de nombreuses espèces rend de plus en plus difficile de justifier leur exclusion de la communauté morale sur la base d'une prétendue absence de vie mentale significative.
2. '''Les technologies biomédicales émergentes''' (édition génomique, chimères homme-animal, intelligence artificielle avancée, amélioration cognitive) brouillent les frontières traditionnelles entre espèces et entre naturel et artificiel. Ces développements obligent à repenser les critères mêmes de la personnalité et de l'appartenance à la communauté morale.
3. '''Les mobilisations sociales et juridiques''' pour la reconnaissance des droits des animaux, bien qu'elles aient connu des échecs répétés devant les tribunaux, ont néanmoins placé ces questions au centre du débat public et stimulé une réflexion philosophique approfondie sur les fondements de notre système juridique.
Malgré ces avancées, plusieurs défis théoriques et pratiques majeurs subsistent. Sur le plan théorique, le désaccord persiste sur les critères fondamentaux du statut moral : est-ce la sentience, la conscience de soi, l'autonomie, les capacités cognitives complexes, la vulnérabilité, les relations de soin, ou une combinaison de ces facteurs ? Comment articuler le principe d'égale considération avec la reconnaissance de différences réelles entre êtres ? Comment penser les obligations envers les générations futures d'humains et d'animaux dans le contexte des technologies d'amélioration ?
Sur le plan pratique, les institutions juridiques et politiques peinent à traduire les avancées philosophiques en réformes concrètes. Les tentatives de reconnaissance de la personnalité juridique animale se heurtent à des obstacles structurels : la séparation des pouvoirs (les tribunaux renvoient la question aux législateurs), la complexité des conséquences pratiques, les intérêts économiques considérables liés à l'exploitation animale, et les résistances culturelles ancrées dans des millénaires de domination humaine.
Le concept de personne non-humaine demeure donc à la fois prometteur et problématique : prometteur parce qu'il offre un cadre conceptuel pour reconnaître la dignité et les droits d'êtres trop longtemps traités comme de simples instruments, problématique parce qu'il étend une catégorie juridique et morale historiquement liée à l'humanité à des contextes où son applicabilité reste contestée.
La tâche de la bioéthique contemporaine face à ces questions est triple : approfondir l'analyse conceptuelle des notions de personne, de dignité et de statut moral ; développer une phénoménologie plus riche de l'expérience animale s'appuyant sur les méthodes empiriques des sciences cognitives ; et contribuer à l'élaboration de cadres juridiques et politiques qui, même sans accorder immédiatement la personnalité juridique pleine aux animaux, renforcent progressivement leurs protections et reconnaissent leur valeur intrinsèque.
C'est dans cet esprit de recherche rigoureuse, d'ouverture aux perspectives multiples et d'engagement pour la justice que doit se poursuivre la réflexion bioéthique sur les personnes non-humaines au XXIe siècle.
== Références ==
{{references}}
==Bibliographie==
===Ouvrages===
* '''DeGrazia, David''', ''Taking Animals Seriously: Mental Life and Moral Status'', Cambridge, Cambridge University Press, 1996, 316 p.
* '''Donaldson, Sue''' et '''Kymlicka, Will''', ''Zoopolis: A Political Theory of Animal Rights'', Oxford, Oxford University Press, 2011 ; trad. fr. Pierre Madelin, ''Zoopolis. Une théorie politique des droits des animaux'', préf. Corine Pelluchon, Paris, Alma, 2016, 368 p.
* '''Korsgaard, Christine M.''', ''Fellow Creatures: Our Obligations to the Other Animals'', Oxford, Oxford University Press, 2018, 260 p.
* '''Nussbaum, Martha C.''', ''Frontiers of Justice: Disability, Nationality, Species Membership'', Cambridge (MA), Harvard University Press, 2006, 487 p.
* '''Regan, Tom''', ''The Case for Animal Rights'', Berkeley, University of California Press, 1983 ; 2e éd. 2004, 493 p.
* '''Singer, Peter''', ''Animal Liberation'', New York, Random House, 1975 ; trad. fr. Louise Rousselle, ''La Libération animale'', Paris, Payot, 2012, 480 p. ; nouvelle édition définitive : ''Animal Liberation Now'', Londres, Bodley Head, 2023 ; trad. fr. ''La Libération animale (édition définitive)'', préf. Yuval Noah Harari, Paris, Payot, 2024, 500 p.
* '''Singer, Peter''', ''Practical Ethics'', Cambridge, Cambridge University Press, 1979 ; 2e éd. 1993 ; 3e éd. 2011.
* '''Wise, Steven M.''', ''Rattling the Cage: Toward Legal Rights for Animals'', Cambridge (MA), Perseus Books, 2000.
* '''Wise, Steven M.''', ''Drawing the Line: Science and the Case for Animal Rights'', Cambridge (MA), Perseus Books, 2002.
===Articles et chapitres===
* '''Dardenne, Émilie''', '''Giroux, Valéry''' et '''Utria, Enrique''' (dir.), ''Peter Singer et la libération animale. Quarante ans plus tard'', Rennes, Presses universitaires de Rennes, 2017, 164 p.
* '''DeGrazia, David''', « Moral Status as a Matter of Degree ? », ''Southern Journal of Philosophy'', vol. 46, n° 2, 2008, p. 181-198.
* '''Favre, David''' et '''Abate, Randall S.''', « Legal Personhood for Animals: Has Science Made Its Case ? », ''Animals'', vol. 13, n° 14, 2023, article 2339.
* '''Kittay, Eva Feder''', « The Ethics of Care, Dependence, and Disability », ''Ratio Juris'', vol. 24, n° 1, 2011, p. 49-58.
* '''Kurki, Visa A. J.''', « Towards a Theory of Legal Animal Rights: Simple and Fundamental Rights », ''Oxford Journal of Legal Studies'', vol. 40, n° 3, 2020, p. 533-561.
* '''Laing, Jacqueline''', « Animal Rights Theory and Utilitarianism: Relative Normative Guidance », ''Animal Law Review'', vol. 3, 1996, p. 107-153.
* '''Müller, Frédéric''', « Korsgaard's Duties towards Animals: Two Difficulties », ''Philosophical Quarterly'', vol. 72, n° 4, 2022, p. 911-931.
* '''Nussbaum, Martha C.''', « Should We Ascribe Capabilities to Sentient Animals ? », ''De Ethica'', vol. 3, n° 1, 2016, p. 61-76.
* '''Passos-Ferreira, Claudia''', « Empathizing with the Intellectually Disabled », dans Henk ten Have (dir.), ''Handbook of Bioethical Decisions, vol. I'', Cham, Springer, 2023, p. 287-302.
* '''Pezzetta, Simone''', « Animals as Subjects or Citizens: Can Animals have rights without duties ? », ''Derecho Animal'', vol. 15, n° 1, 2025, p. 9-28.
* '''Riot, Sofia''', « Legal Personhood of Animals (I): The case for the legal personhood of companion animals », ''Derecho Animal'', vol. 9, n° 2, 2018, p. 30-55.
* '''Veit, Walter''' et '''Browning, Heather''', « Phenomenology Applied to Animal Health and Suffering », dans James Giordano et Evan Micah Thompson (dir.), ''Phenomenology of Bioethics: Technoethics and Lived-Experience'', Cham, Springer, 2021, p. 93-108.
* '''Wrenn, Corey Lee''', « Abolitionist Animal Rights: Critical Comparisons and Challenges within the Animal Rights Movement », ''Interface: A Journal for and about Social Movements'', vol. 4, n° 2, 2012, p. 438-458.
===Ressources juridiques et institutionnelles===
* '''Deckha, Maneesha''', « Transition rather than Revolution: The Gradual Road towards Animal Legal Personhood through the Legislature », ''Transnational Environmental Law'', vol. 11, n° 2, 2022, p. 313-337.
* '''Loi française n° 2015-177''' du 16 février 2015 relative à la modernisation et à la simplification du droit et des procédures dans les domaines de la justice et des affaires intérieures, article 2 modifiant l'article 515-14 du Code civil.
* '''Nonhuman Rights Project''', site web officiel : https://www.nonhumanrights.org/
* '''U.S. Transhumanist Party''', ''Transhumanist Bill of Rights Version 3.0'', 2018, disponible en ligne.
* '''UNESCO''', ''Universal Declaration on Bioethics and Human Rights'', Paris, UNESCO, 2005.
* '''WHO Expert Advisory Committee on Developing Global Standards for Governance and Oversight of Human Genome Editing''', ''Human genome editing: a framework for governance'', Genève, WHO, 2021.
===Ressources électroniques===
* '''Francione, Gary L.''' et '''Charlton, Anna''', ''Animal Rights: The Abolitionist Approach'', site web : https://www.abolitionistapproach.com/
* '''Stanford Encyclopedia of Philosophy''', article « Animalism », consulté en 2025 : https://plato.stanford.edu/entries/animalism/
* '''Stanford Encyclopedia of Philosophy''', article « The Moral Status of Animals », consulté en 2025 : https://plato.stanford.edu/entries/moral-animal/
{{autocat}}
46mqbin8rdm6ahxu4r3oiguqb1xc2yl
Dictionnaire de philosophie/Philosophie africaine
0
83094
768692
762102
2026-06-26T04:32:41Z
PandaMystique
119061
Bot : ajout du paramètre lecture=oui au modèle {{DicoPhilo}}
768692
wikitext
text/x-wiki
{{DicoPhilo|Philosophie africaine|lecture=oui}}
La '''pensée africaine''' désigne l'ensemble des traditions philosophiques, des systèmes de réflexion et des productions intellectuelles développés sur le continent africain et par les Africains, aussi bien dans les contextes précoloniaux, coloniaux que contemporains. Loin de constituer un ensemble monolithique, la pensée africaine se caractérise par sa pluralité, ses débats internes et ses multiples ancrages culturels, linguistiques et historiques.
Le présent article se concentre sur la philosophie africaine au sens large : les systèmes de pensée traditionnels, les débats académiques contemporains, les courants intellectuels et les figures majeures qui ont contribué à faire de la pensée africaine un champ philosophique à part entière. Il convient de distinguer, d'une part, les traditions intellectuelles anciennes et orales du continent, d'autre part, la « philosophie africaine » en tant que discipline universitaire constituée à partir des années 1960, et enfin, les productions philosophiques contemporaines des penseurs africains qui s'inscrivent dans les débats mondiaux sans se limiter aux questions identitaires.
==Origines et fondements==
===La pensée pharaonique===
L'Égypte ancienne a développé des corpus textuels et conceptuels d'une grande richesse, dont le statut proprement « philosophique » fait l'objet de discussions savantes. Plusieurs chercheurs, notamment Théophile Obenga et Mubabinge Bilolo, soutiennent que ces textes constituent des systèmes philosophiques structurés antérieurs à la tradition grecque. D'autres historiens de la philosophie estiment que le terme « philosophie » appliqué à ces productions relève d'une extension discutable de la notion. Les Textes des pyramides, les enseignements de Ptahhotep et les cosmologies témoignent, en tout état de cause, d'une élaboration théorique structurée, constituant l'un des premiers corpus de réflexion systématique documentés<ref>Obenga, Theophile (2004). « Egypt: Ancient History of African Philosophy », in Wiredu, Kwasi (dir.), ''A Companion to African Philosophy''. Oxford : Blackwell, p. 31-49.</ref>.
====Systèmes philosophiques égyptiens====
Mubabinge Bilolo a identifié plusieurs traditions de pensée en Égypte ancienne : la pensée de l'Un (Wa), le passage de l'Un aux multiples (Hh), la théologie négative, l'éthique écologique et l'épistémologie<ref>Bilolo, Mubabinge (1986). ''Les cosmo-théologies philosophiques de l'Égypte Antique : problématiques, prémisses herméneutiques et problèmes majeurs''. Kinshasa : Publications Universitaires Africaines, p. 45-89.</ref>. Ses travaux proposent une lecture de la métaphysique égyptienne portant sur l'origine du cosmos, la nature de l'être et les principes régissant l'univers<ref>Bilolo, Mubabinge (2004). ''Les cosmo-théologies philosophiques de l'Egypte Antique''. Munich : Menaibuc, 278 p.</ref>. Cette lecture, si elle a rencontré un écho favorable chez plusieurs africanistes, reste discutée par les égyptologues qui préfèrent parler de « sagesse » ou de « pensée spéculative » plutôt que de « philosophie » au sens strict.
====Textes de sagesse : Ptahhotep et Aménémopé====
Les enseignements de sagesse (''sebayt'') constituent une source textuelle majeure. L'Enseignement de Ptahhotep (vers 2400 av. J.-C., Ancien Empire) compte parmi les plus anciens textes de réflexion morale et politique connus<ref>Vernus, Pascal (2010). « Les sagesses dans la littérature pharaonique », in ''Sagesses de l'Égypte pharaonique''. Paris : Imprimerie Nationale, p. 45-78.</ref>. Ptahhotep, vizir de la Vᵉ dynastie, expose une pensée morale et politique centrée sur l'harmonie, la mesure, l'écoute et la justice, véhiculant une conception structurée du monde et de la société.
Parmi les préceptes de Ptahhotep : « Ne sois pas vain de ce que tu as appris, mais converse avec l'ignorant comme avec le sage. Car aucune limite ne peut être fixée au savoir, pas plus qu'il n'est d'homme habile qui puisse tout connaître »<ref>Ptahhotep, ''Les Maximes de Ptahhotep'', trad. Christian Jacq (2023). Versailles : MdV Éditeur, maxime 1.</ref>. Cette formulation témoigne d'une réflexion épistémologique reconnaissant les limites de la connaissance humaine.
L'Enseignement d'Aménémopé (vers 1000 av. J.-C., Nouvel Empire) développe la piété personnelle, la modération et la relation directe avec la divinité. Des spécialistes de la littérature biblique ont relevé des parentés entre ces textes et la littérature sapientiale du Proche-Orient ancien, notamment le Livre des Proverbes<ref>Lichtheim, Miriam (1976). ''Ancient Egyptian Literature, Volume II: The New Kingdom''. Berkeley : University of California Press, p. 146-163.</ref>.
====Maât : principe structurant====
Maât désigne à la fois une déesse et un concept abstrait incarnant la vérité (''maa''), la justice (''djed''), l'ordre cosmique (''hep''), l'équilibre et l'harmonie universelle<ref>Menu, Bernadette (2015). « Maât, ordre social et inégalités dans l'Égypte ancienne », in ''Droit et Cultures'', n° 69, p. 23-45.</ref>. Ce principe structure la cosmologie, l'éthique et l'organisation politique égyptiennes.
Maât représente l'ordre instauré par le dieu créateur Rê lors de la création, en opposition au chaos primordial (''Isfet''). Cet ordre doit être maintenu par le pharaon, garant de Maât, et par la conduite individuelle<ref>Assmann, Jan (1990). ''Ma'at: Gerechtigkeit und Unsterblichkeit im Alten Ägypten''. Munich : C.H. Beck, p. 62-89.</ref>. Maât constitue un principe cosmique dont dépend la stabilité universelle.
Plan cosmologique : L'univers fonctionne selon des lois rationnelles. Le cycle solaire, les saisons et les crues du Nil obéissent à Maât.
Plan éthique : Les actions humaines doivent s'aligner sur Maât. L'éthique égyptienne repose sur la conformité à l'ordre naturel. Les vertus cardinales — justice (''maa''), vérité (''maa''), rectitude (''udjeb''), générosité (''aw ib''), maîtrise de soi (''htp'') — manifestent Maât<ref>Lichtheim, Miriam (1973). ''Ancient Egyptian Literature, Volume I: The Old and Middle Kingdoms''. Berkeley : University of California Press, p. 5-12.</ref>.
Plan politique : Le pharaon règne en incarnation de Maât. Sa légitimité dépend de sa capacité à maintenir l'ordre, rendre la justice et assurer la prospérité. Les juges prêtent serment au nom de Maât et portent son effigie<ref>Théodoridès, Aristide (1971). « The Concept of Law in Ancient Egypt », in Harris, J.R. (dir.), ''The Legacy of Egypt''. Oxford : Clarendon Press, p. 291-322.</ref>.
Plan eschatologique : Après la mort, le cœur du défunt est pesé contre une plume de Maât lors du jugement dans la Douât. Si le cœur est plus léger — si la personne a vécu conformément à Maât — elle accède à la vie éternelle. Sinon, le cœur est dévoré par Ammit et l'individu subit l'anéantissement définitif<ref>Hornung, Erik (1999). ''The Ancient Egyptian Books of the Afterlife'', trad. David Lorton. Ithaca : Cornell University Press, p. 73-89.</ref>.
====Cosmogonies et métaphysique====
Plusieurs systèmes cosmogoniques ont été développés à Héliopolis, Memphis et Hermopolis, proposant des réflexions sur l'origine du monde<ref>Allen, James P. (1988). ''Genesis in Egypt: The Philosophy of Ancient Egyptian Creation Accounts''. New Haven : Yale Egyptological Seminar, p. 1-28.</ref>.
La cosmogonie héliopolitaine décrit l'émergence d'Atoum à partir du Noun, l'océan primordial. Atoum, par autoengendrement, crée Shou (l'air) et Tefnout (l'humidité), qui engendrent Geb (la terre) et Nout (le ciel). Cette généalogie divine (l'Ennéade) structure le cosmos<ref>Obenga, Theophile (2004). « Egypt: Ancient History of African Philosophy », in Wiredu, Kwasi (dir.), ''A Companion to African Philosophy''. Oxford : Blackwell, p. 40-42.</ref>.
La Théologie memphite, inscrite sur la Pierre de Shabaka (VIIIᵉ siècle av. J.-C., reprenant un texte antérieur), présente une cosmogonie intellectualiste. Le dieu Ptah crée par la pensée (''sia'') conçue dans son cœur (''ib'') et par la parole (''hou'') prononcée. Des auteurs comme James Henry Breasted y ont vu un précédent de la doctrine du Logos créateur, rapprochant cette cosmogonie du prologue johannique<ref>Breasted, James Henry (1912). ''Development of Religion and Thought in Ancient Egypt''. New York : Charles Scribner's Sons, p. 43-46.</ref>. Ce rapprochement, suggestif, demeure discuté parmi les spécialistes, qui divergent sur la portée exacte d'une telle comparaison et sur les voies de transmission possibles.
La cosmogonie hermopolitaine propose une réflexion sur le néant pré-créationnel à travers l'Ogdoade, quatre paires de divinités représentant les aspects du chaos primordial : Noun et Naunet (les eaux primordiales), Heh et Hehet (l'infini spatial), Kek et Keket (les ténèbres), Amon et Amonet (le caché, l'indifférencié). De ces principes négatifs émerge la création positive<ref>Hornung, Erik (1982). ''Conceptions of God in Ancient Egypt: The One and the Many'', trad. John Baines. Ithaca : Cornell University Press, p. 219-231.</ref>.
====Épistémologie et figure du philosophe====
Les Égyptiens utilisaient plusieurs termes pour désigner le sage : ''rekh'' (le savant), ''sai'' ou ''sia'' (le sage), ''ib-akh'' (celui dont le cœur est lumineux). L'Inscription d'Antef (XIIᵉ dynastie, vers 1900 av. J.-C.) fournit un portrait du sage :
« Il est celui dont le cœur est informé sur ces choses qui seraient autrement ignorées, celui qui voit clair lorsqu'il s'enfonce dans un problème, celui qui est modéré dans ses actions, qui pénètre les écrits anciens, dont on recherche les conseils pour démêler les complications, qui est vraiment sage, qui a instruit son propre cœur, qui reste éveillé la nuit à chercher les voies justes, qui surpasse ce qu'il a accompli hier, qui est plus sage qu'un sage, qui s'est lui-même conduit à la sagesse, qui demande conseil et veille à ce qu'on lui demande conseil »<ref>Brunner, Hellmut (1988). ''Altägyptische Weisheit: Lehren für das Leben''. Zürich : Artemis Verlag, p. 117-118, cité dans Obenga, Theophile (2004). « Egypt: Ancient History of African Philosophy », in Wiredu, Kwasi (dir.), ''A Companion to African Philosophy''. Oxford : Blackwell, p. 35.</ref>.
Ce portrait met l'accent sur la pénétration intellectuelle, la modération éthique, la connaissance des traditions, la capacité au conseil, l'auto-formation, le progrès continu et le dialogue.
====Rapports avec la philosophie grecque====
Les sources antiques mentionnent que plusieurs penseurs grecs se sont rendus en Égypte. Hérodote rapporte que Pythagore, Thalès, Platon et Démocrite y ont séjourné pour s'instruire auprès des prêtres<ref>Hérodote, ''Histoires'', Livre II, § 81, 109, 123.</ref>. Diogène Laërce mentionne également ces séjours<ref>Diogène Laërce, ''Vies et doctrines des philosophes illustres'', Livre I, VIII, IX.</ref>. La fiabilité de ces témoignages et la nature exacte de ce que les Grecs auraient emprunté aux Égyptiens font l'objet de vifs débats parmi les historiens de la philosophie.
Plusieurs chercheurs ont relevé des analogies entre certaines doctrines grecques et des conceptions égyptiennes : la doctrine pythagoricienne de l'immortalité et de la métempsychose<ref>Hornung, Erik (2001). ''The Secret Lore of Egypt: Its Impact on the West'', trad. David Lorton. Ithaca : Cornell University Press, p. 17-32.</ref>, un rapprochement proposé par certains auteurs entre la théorie platonicienne des Idées et le concept égyptien de ''ka'' (double spirituel)<ref>Allen, Thomas George (1960). « Egyptian Concept of the World », in Kramer, Samuel Noah (dir.), ''Mythologies of the Ancient World''. New York : Doubleday, p. 79-94.</ref>, le parallèle entre le Logos créateur dans la philosophie stoïcienne et la cosmogonie memphite<ref>Breasted, James Henry (1912). ''Development of Religion and Thought in Ancient Egypt''. New York : Charles Scribner's Sons, p. 44.</ref>, ainsi que la proximité entre l'éthique de la mesure (''métron'') grecque et le principe égyptien de modération lié à Maât.
Ces analogies sont interprétées de manières très diverses. Pour des auteurs comme Théophile Obenga, elles témoignent d'un dialogue intellectuel substantiel et d'une dette de la pensée grecque envers l'Égypte : « La philosophie n'a pas commencé en Grèce au VIᵉ siècle avant notre ère. Les Grecs eux-mêmes reconnaissaient que leurs maîtres en philosophie et en sciences étaient les Égyptiens »<ref>Obenga, Theophile (1990). ''African Philosophy: The Pharaonic Period 2780-330 BC''. Popenguine : Per Ankh, p. 11.</ref>. Pour d'autres historiens, ces parallèles restent superficiels et ne permettent pas de conclure à une influence directe, les analogies pouvant s'expliquer par des développements intellectuels indépendants ou par un fonds culturel commun au monde méditerranéen ancien.
====Pensée pharaonique et contexte africain====
La question des liens entre l'Égypte ancienne et les autres régions d'Afrique constitue un autre champ de recherche disputé. Cheikh Anta Diop a défendu l'existence d'une parenté culturelle profonde entre l'Égypte pharaonique et l'Afrique subsaharienne, à travers des études linguistiques comparées et anthropologiques<ref>Diop, Cheikh Anta (1974). ''The African Origin of Civilization: Myth or Reality'', trad. Mercer Cook. Chicago : Lawrence Hill Books, p. 123-156.</ref>. Sa thèse a suscité un débat considérable ; si elle est soutenue par une partie des chercheurs africanistes, elle est contestée par des linguistes et des archéologues qui jugent les preuves insuffisantes pour établir une continuité aussi directe.
Parmi les parallèles évoqués par certains chercheurs figurent le rapprochement entre le concept de force vitale (''sekhem'') en Égypte et le concept bantu de force vitale décrit par Placide Tempels<ref>Tempels, Placide (1945). ''La Philosophie bantoue''. Paris : Présence Africaine, p. 31-52.</ref>, l'importance de la parole créatrice chez les Dogon du Mali telle que la rapporte Marcel Griaule<ref>Griaule, Marcel et Dieterlen, Germaine (1965). ''Le Renard pâle''. Paris : Institut d'ethnologie, p. 123-145.</ref>, et un rapprochement entre le principe de Maât et le concept akan d'équilibre cosmique et social<ref>Wiredu, Kwasi (1996). ''Cultural Universals and Particulars: An African Perspective''. Bloomington : Indiana University Press, p. 45-67.</ref>. Ces parallèles sont instructifs mais doivent être maniés avec prudence : ils peuvent relever de convergences indépendantes aussi bien que de liens historiques avérés.
====Conclusion====
La pensée pharaonique constitue un corpus métaphysique et éthique documenté, dont l'étude permet d'élargir l'histoire de la pensée au-delà du seul horizon gréco-romain. Son statut exact — sagesse, pensée spéculative, ou philosophie au sens plein — reste discuté. Mubabinge Bilolo soutient que « la philosophie égyptienne n'est pas une simple préhistoire de la philosophie grecque. Elle possède sa propre cohérence, ses propres questionnements et ses propres réponses. Elle mérite d'être étudiée pour elle-même, dans sa spécificité africaine, et non comme un simple prologue à la philosophie occidentale »<ref>Bilolo, Mubabinge (1986). ''Les cosmo-théologies philosophiques de l'Égypte Antique : problématiques, prémisses herméneutiques et problèmes majeurs''. Kinshasa : Publications Universitaires Africaines, p. 12.</ref>. Quelle que soit la position adoptée sur la qualification « philosophique » de ces textes, leur richesse intellectuelle et leur contribution à l'histoire de la pensée sont aujourd'hui largement reconnues.
===Les traditions orales===
Les sociétés africaines ont développé des systèmes de pensée sophistiqués transmis par l'oralité. Les proverbes, mythes, récits initiatiques et pratiques rituelles véhiculent des conceptions sur l'être, la connaissance, l'éthique et la communauté. Ces savoirs, longtemps négligés ou méprisés par l'académie occidentale, constituent, pour de nombreux philosophes africains contemporains, des formes authentiques de philosophisation<ref>Kagame, Alexis (1956). ''La Philosophie bantu-rwandaise de l'être''. Bruxelles : Académie Royale des Sciences Coloniales, p. 17-38.</ref>.
====L'oralité comme mode de civilisation====
Contrairement aux cultures où la transmission du savoir se fait principalement par écrit, la tradition orale occupe une place privilégiée dans de nombreuses sociétés africaines. Les contes, légendes et proverbes sont racontés et retransmis pendant les cérémonies, les veillées et les rassemblements communautaires<ref>Baumgardt, Ursula et Derive, Jean (2013). ''Littératures orales africaines : perspectives théoriques et méthodologiques''. Paris : Karthala, p. 12-34.</ref>. Cette tradition crée un lien fort entre les générations, renforçant le sens de la communauté et du partage.
Amadou Hampâté Bâ, écrivain et ethnologue malien, a condensé cette réalité dans sa formule devenue proverbiale : « En Afrique, quand un vieillard meurt, c'est une bibliothèque qui brûle »<ref>Hampâté Bâ, Amadou (1985). « Récolte des traditions orales », in ''Nouvelles du Sud'', numéro spécial « Islam et littératures africaines », p. 215.</ref>. Cette métaphore souligne l'importance de la transmission orale et le rôle des anciens comme dépositaires de la sagesse collective.
La tradition orale n'est pas simplement un moyen de communication, mais un processus culturel à travers lequel l'éducation des enfants et la transmission des valeurs, des savoirs et des traditions se réalisent<ref>Hampâté Bâ, Amadou (1987). « De la tradition orale », in ''Nouvelles du Sud'', p. 215-218.</ref>. C'est dans cette oralité que se forment les éléments de la conscience et de l'intelligence collectives.
====Les proverbes : palme-oil de la parole====
Les proverbes occupent une place centrale dans la pensée traditionnelle de nombreuses sociétés africaines. Comme l'a exprimé Chinua Achebe dans ''Le monde s'effondre'', « les proverbes sont l'huile de palme avec laquelle on mange les mots »<ref>Achebe, Chinua (1958). ''Things Fall Apart''. London : Heinemann, p. 5 (trad. française : ''Le monde s'effondre''. Paris : Présence Africaine, 1966).</ref>. Cette métaphore igbo illustre la fonction des proverbes : ils rendent la communication plus digeste, plus mémorable.
Les proverbes africains ne sont pas de simples dictons folkloriques. Ils constituent de véritables condensés de philosophie pratique qui encapsulent des vérités morales, des observations sur la nature humaine, des conseils de vie<ref>Pacéré, Titinga Frédéric (2005). ''Pensées africaines. Proverbes, dictons et sagesse des Anciens''. Paris : L'Harmattan, p. 23-45.</ref>. Chez les Akan du Ghana, les Yoruba et les Igbo du Nigeria, les Peul d'Afrique de l'Ouest, les Tharaka du Kenya, les proverbes servent de référence morale et de guide pour la résolution des conflits.
Les proverbes remplissent plusieurs fonctions essentielles :
Fonction pédagogique : Les proverbes sont des instruments d'éducation morale. Par exemple, le proverbe peul « Celui qui est patient voit le soleil se lever » enseigne la valeur de la patience. Le proverbe akan « Un arbre ne peut faire une forêt » enseigne l'importance de la solidarité communautaire<ref>Sarpong, Peter (1972). « Aspects of Akan Ethics », in ''Ghana Bulletin of Theology'', vol. 4, n°3, p. 42.</ref>.
Fonction sociale : Les proverbes régulent les interactions sociales en rappelant les normes et valeurs communautaires. Le proverbe igbo « Un vieillard assis voit plus loin qu'un jeune homme debout » rappelle le respect dû aux anciens et reconnaît leur expérience<ref>Neequaye, George Kotei (2020). « Ethical Thought of Kwasi Wiredu and Kwame Gyekye », in Wariboko, N. et Falola, T. (dir.), ''The Palgrave Handbook of African Social Ethics''. Cham : Palgrave Macmillan, p. 418.</ref>.
Fonction juridique : Dans de nombreuses sociétés traditionnelles africaines, les proverbes sont invoqués lors des palabres (assemblées de règlement des conflits) pour appuyer un argument, clarifier une position morale, ou suggérer une résolution juste<ref>Kudadjie, Joshua N. (1973). « Does Religion Determine Morality in African Society? », in ''Ghana Bulletin of Theology'', vol. 4, p. 47.</ref>.
Fonction herméneutique : Les proverbes permettent une communication indirecte qui laisse place à l'interprétation et à la réflexion. Comme le note Nimrod Kahn, cette indirection protège contre le dogmatisme éthique en créant un espace pour la liberté individuelle au sein de l'éthique communautaire<ref>Kahn, Nimrod (2024). « This Thing Called Communitarianism », in Bateye et al. (dir.), ''Well-Being in African Philosophy''. Lanham : Lexington Books, p. 49-65.</ref>.
====Les griots et autres gardiens de la tradition====
Les griots (appelés également djeli, jali, ou gewel selon les régions) sont les maîtres de la parole en Afrique de l'Ouest. Ces conteurs traditionnels ont la responsabilité de mémoriser et de transmettre la sagesse ancestrale. Ils sont respectés pour leur art oratoire et pour leur rôle éducatif<ref>Baumgardt, Ursula (2008). « Les enjeux d'une pédagogie de la littérature orale », in ''Cahiers de littérature orale'', n°63-64, p. 175-196.</ref>.
Les griots sont les gardiens de l'histoire, de la généalogie des familles nobles, des épopées héroïques comme celle de Soundjata Keïta (fondateur de l'empire du Mali). Par leur récitation, ils maintiennent vivante la mémoire collective et assurent la continuité culturelle. Leur statut est à la fois admiré et ambivalent : admiré pour leur connaissance et leur talent artistique, mais parfois tenu à distance en raison du pouvoir de leur parole, qui peut louer ou critiquer.
Au-delà des griots, d'autres figures jouent un rôle dans la transmission orale de la sagesse : les devins et prêtres traditionnels, qui interprètent les messages des ancêtres et des divinités ; les anciens et sages du village, consultés pour leurs conseils lors de décisions importantes ; les grands-parents, qui transmettent les contes et les valeurs morales aux petits-enfants ; les maîtres initiateurs, responsables de l'éducation rituelle lors des cérémonies d'initiation.
====La « Sage Philosophy » d'Henry Odera Oruka====
Le philosophe kényan Henry Odera Oruka (1944-1995) a développé le concept de « sage philosophy » (philosophie des sages) pour montrer l'existence d'une pensée philosophique critique au sein des traditions orales africaines<ref>Oruka, Henry Odera (1990). ''Sage Philosophy: Indigenous Thinkers and Modern Debate on African Philosophy''. Leiden : E.J. Brill, p. 28-47.</ref>. Son projet visait à réfuter le stéréotype selon lequel les cultures africaines manqueraient de traditions philosophiques rigoureuses.
Oruka distingue deux types de sagesse traditionnelle :
La sagesse populaire (folk wisdom) : Il s'agit des croyances communément partagées, des maximes et proverbes répétés de génération en génération sans être nécessairement questionnés. Cette sagesse reflète le consensus culturel et constitue ce qu'Oruka appelle la « philosophie culturelle » (''mythos'') d'un peuple.
La sagesse philosophique (philosophic sagacity) : Certains sages, tout en étant profondément enracinés dans leur culture, développent une pensée critique et personnelle. Ils ne se contentent pas de répéter les dictons traditionnels mais les examinent, les questionnent, les adaptent aux circonstances nouvelles. Ces « philosophes sages » utilisent la raison pour évaluer les croyances héritées et proposer de nouvelles interprétations<ref>Oruka, Henry Odera (1990). ''Sage Philosophy: Indigenous Thinkers and Modern Debate on African Philosophy''. Leiden : E.J. Brill, p. 33.</ref>.
Oruka a mené de nombreux entretiens avec des sages ruraux kényans, notamment des communautés luo et luhya. Il a transcrit et publié leurs réflexions sur des questions métaphysiques, éthiques et épistémologiques. Un exemple célèbre est le sage Paul Mbuya Akoko, un paysan luo qui, sans éducation formelle occidentale, avait développé des positions philosophiques originales sur la nature de Dieu, la moralité, la justice sociale et la politique<ref>Graness, Anke et Kresse, Kai (dir.) (1997). ''Sagacious Reasoning: Henry Odera Oruka in Memoriam''. Frankfurt : Peter Lang, p. 45-67.</ref>.
L'approche d'Oruka s'inspire de la méthode socratique. Socrate lui-même, qui n'a laissé aucun écrit, philosophait oralement dans les rues d'Athènes, questionnant ses interlocuteurs pour faire émerger leur pensée. De même, les sages africains, interrogés avec respect et méthode, révèlent une profondeur de réflexion qui mérite reconnaissance<ref>Masolo, Dismas A. (1994). ''African Philosophy in Search of Identity''. Bloomington : Indiana University Press, p. 227-236.</ref>.
====Oralité, écriture et philosophie====
La question du rapport entre oralité et écriture en philosophie africaine a été l'objet de débats intenses. Le philosophe béninois Paulin Hountondji (1942-2024) a soutenu que la philosophie, pour être véritablement critique et cumulative, nécessite l'écriture. Selon lui, tant que les idées restent uniquement orales, elles ne peuvent pas être soumises à un examen critique rigoureux et ne peuvent pas s'inscrire dans une tradition intellectuelle durable<ref>Hountondji, Paulin J. (1977). ''Sur la philosophie africaine. Critique de l'ethnophilosophie''. Paris : Maspero, p. 67-89.</ref>.
Hountondji soutient que « l'absence de transcription ne dévalorise certainement pas intrinsèquement un discours philosophique, mais elle l'empêche de s'intégrer dans une tradition théorique collective et de prendre place dans l'histoire comme point de référence capable d'orienter les discussions futures »<ref>Hountondji, Paulin J. (1983). ''African Philosophy: Myth and Reality''. Bloomington : Indiana University Press, p. 106.</ref>. L'oralité tend, selon lui, à favoriser la répétition plutôt que l'innovation critique.
Cette position a été contestée. Kwame Gyekye souligne que les proverbes akan sont le produit de « spéculations intellectuelles aiguës » et constituent une forme de philosophisation même s'ils sont transmis oralement<ref>Gyekye, Kwame (1995). ''An Essay on African Philosophical Thought: The Akan Conceptual Scheme''. Cambridge : Cambridge University Press, p. 21.</ref>. Pour Gyekye, refuser le statut philosophique aux traditions orales revient à « jeter le bébé avec l'eau du bain ».
Kwasi Wiredu (1931-2022) adopte une position nuancée. Il reconnaît la valeur philosophique de certaines conceptions transmises oralement (ce qu'il appelle « folk philosophy »), mais insiste sur la nécessité d'une analyse critique et systématique de ces matériaux. L'écriture permet cette analyse, mais n'est pas en soi la condition de la pensée philosophique<ref>Wiredu, Kwasi (1980). ''Philosophy and an African Culture''. Cambridge : Cambridge University Press, p. 23-36.</ref>.
====Les genres de littérature orale à dimension philosophique====
Au-delà des proverbes, plusieurs genres de littérature orale africaine véhiculent des contenus philosophiques :
Les mythes cosmogoniques : Les récits sur l'origine du monde, des dieux, des humains contiennent des conceptions métaphysiques. Par exemple, le mythe dogon de la création, recueilli par Marcel Griaule auprès du sage Ogotemmêli, présente une cosmologie élaborée avec le dieu créateur Amma, la gémellité primordiale, et l'organisation de l'univers<ref>Griaule, Marcel (1948). ''Dieu d'eau : Entretiens avec Ogotemmêli''. Paris : Éditions du Chêne, p. 15-89.</ref>. Il convient de noter que les travaux de Griaule font eux-mêmes l'objet de discussions critiques quant à la part de reconstruction savante dans les récits recueillis.
Les contes moraux : Les histoires d'Anansi l'araignée (Akan), de Leuk le lièvre (Wolof), du caméléon et du lézard contiennent des leçons éthiques sur la ruse, la sagesse, la justice, les conséquences de l'orgueil ou de la cupidité.
Les épopées : Les grandes épopées comme celle de Soundjata, de l'Ozidi (Ijo du Nigeria), de Mwindo (Congo) véhiculent des valeurs héroïques mais aussi des réflexions sur le pouvoir, la justice, le destin<ref>Okpewho, Isidore (1979). ''The Epic in Africa: Toward a Poetics of the Oral Performance''. New York : Columbia University Press, p. 134-167.</ref>.
Les devinettes et énigmes : Elles stimulent la réflexion logique et la pensée abstraite. Les séances de devinettes le soir au clair de lune constituent une forme d'éducation intellectuelle pour les jeunes.
Les chants rituels et liturgiques : Les invocations aux divinités, les chants funéraires, les hymnes royaux expriment des conceptions religieuses et métaphysiques. Chez les Yoruba, les poèmes oraculaires d'Ifá constituent un corpus philosophico-religieux immense<ref>Abimbola, Wande (1976). ''Ifá: An Exposition of Ifá Literary Corpus''. Ibadan : Oxford University Press Nigeria, p. 23-56.</ref>.
====Enjeux contemporains de l'oralité====
À l'ère de la mondialisation et de la numérisation, les traditions orales africaines font face à plusieurs défis.
Érosion de la transmission intergénérationnelle : L'urbanisation, l'éducation occidentalisée, l'influence des médias de masse affaiblissent les mécanismes traditionnels de transmission. Les jeunes générations sont souvent plus familières avec la culture populaire globale qu'avec les proverbes et contes de leurs propres cultures.
Nécessité de documentation : Il devient urgent de collecter, transcrire et archiver les traditions orales avant la disparition des derniers dépositaires. Les travaux d'Oruka au Kenya et ceux d'Amadou Hampâté Bâ au Mali montrent la voie.
Valorisation pédagogique : Comment intégrer les traditions orales dans les systèmes éducatifs formels en Afrique ? Des initiatives visent à introduire les contes, proverbes et épopées dans les programmes scolaires pour renforcer l'identité culturelle et la pensée critique<ref>Baumgardt, Ursula (2008). « Les enjeux d'une pédagogie de la littérature orale », in ''Cahiers de littérature orale'', n°63-64, p. 189.</ref>.
Innovation et néo-oralité : Les traditions orales ne sont pas figées. Elles évoluent et se renouvellent. On assiste aujourd'hui à des formes de « néo-oralité » : contes radiophoniques, spectacles théâtralisés, slam et spoken word qui s'inspirent des techniques traditionnelles, épopées « intermédialisées » utilisant les nouvelles technologies<ref>Derive, Jean (2012). « L'oralité dans la modernité », in ''Cahiers de littérature orale'', n°72, p. 45-67.</ref>.
====Conclusion====
Les traditions orales africaines ne sont pas de simples curiosités ethnographiques. Comme l'écrit Fabien Eboussi Boulaga, « la tradition orale n'est pas un musée de formes mortes, mais un champ vivant de créativité et de réflexion critique »<ref>Eboussi Boulaga, Fabien (1977). ''La crise du Muntu : Authenticité africaine et philosophie''. Paris : Présence Africaine, p. 78.</ref>.
==Les grands débats==
La philosophie africaine, en tant que discipline académique contemporaine, s'est constituée à travers une série de débats majeurs qui ont marqué son histoire et continuent d'alimenter la réflexion philosophique sur le continent. Ces débats soulèvent des questions fondamentales sur la nature même de la philosophie, son rapport à la tradition, à la modernité, et à l'identité culturelle. Ils structurent le champ de la pensée africaine et définissent ses enjeux contemporains.
===Le débat sur l'ethnophilosophie : Tempels, Hountondji et la critique===
Le débat le plus célèbre et le plus structurant de la philosophie africaine contemporaine porte sur l'ethnophilosophie. Ce débat trouve son origine dans l'ouvrage du missionnaire belge Placide Tempels, ''La Philosophie bantoue'' (1945)<ref>Tempels, Placide (1949). ''La Philosophie bantoue''. Paris : Présence Africaine.</ref>. Tempels affirme que les Bantous du Congo possèdent une philosophie authentique, structurée autour du concept central de force vitale. Selon lui, toute l'ontologie bantoue repose sur l'idée que « l'être est force » et que les êtres se définissent par leur degré de force vitale dans une hiérarchie cosmique.
L'apport de Tempels a été double. D'une part, il a contesté les thèses hégéliennes et les préjugés anthropologiques du XIXᵉ siècle qui déniaient toute capacité philosophique aux Africains, considérés comme relevant d'une « mentalité primitive » (Lucien Lévy-Bruhl). D'autre part, il a proposé une reconstruction systématique de la vision du monde bantoue, montrant sa cohérence interne et sa sophistication.
Toutefois, l'ouvrage de Tempels a rapidement suscité des critiques acerbes, notamment de la part de philosophes africains qui y ont vu une entreprise ambiguë. Le philosophe camerounais Marcien Towa (1931-2014) a été l'un des premiers à dénoncer le caractère colonial de l'entreprise de Tempels. Dans son analyse, Towa soutient que le but de Tempels n'était pas proprement philosophique mais missionnaire et colonial : il s'agissait de mieux comprendre la « mentalité bantoue » pour améliorer les méthodes de christianisation et de domination<ref>Towa, Marcien (1971). ''Léopold Sédar Senghor : Négritude ou servitude ?''. Yaoundé : CLE.</ref>.
Pour Towa, Tempels « qui n'est pas philosophe, se montre soucieux d'une recherche philosophique pour mieux enseigner le message du Christ et il se dit que ce message serait mieux saisi s'il prenait en compte la logique bantoue »<ref>Towa, Marcien (1979). ''Essai sur la problématique philosophique dans l'Afrique actuelle''. Yaoundé : CLE, p. 23.</ref>. De surcroît, Tempels attribue aux Bantous une philosophie collective et inconsciente, ce qui pose problème : si les Bantous philosophent sans le savoir, s'ils n'ont pas conscience de leur propre philosophie, peut-on parler de philosophie au sens strict ?
C'est le philosophe béninois Paulin Hountondji (1942-2024) qui a mené la critique la plus systématique de ce qu'il a nommé l'« ethnophilosophie ». Dans ''Sur la « philosophie africaine ». Critique de l'ethnophilosophie'' (1977), Hountondji définit l'ethnophilosophie comme « une recherche ethnologique prétendant être philosophique »<ref>Hountondji, Paulin J. (1977). ''Sur la « philosophie africaine ». Critique de l'ethnophilosophie''. Paris : Maspero.</ref>.
Selon Hountondji, l'ethnophilosophie commet plusieurs erreurs fondamentales. Elle confond philosophie et vision du monde : la philosophie n'est pas une Weltanschauung collective, mais une activité critique individuelle. Elle est collective et anonyme, alors que la philosophie requiert des auteurs identifiables, des textes signés, une tradition de débats entre penseurs individuels. Elle manque de dimension critique, se contentant de décrire les croyances traditionnelles sans les examiner. Enfin, elle perpétue une forme de paternalisme colonial en modifiant les critères de ce qui compte comme philosophie uniquement pour l'Afrique.
Hountondji propose sa propre définition de la philosophie africaine : « la littérature philosophique africaine, c'est-à-dire l'ensemble des textes écrits par des Africains et qualifiés par leurs auteurs eux-mêmes de philosophiques »<ref>Hountondji, Paulin J. (1977). ''Sur la « philosophie africaine ». Critique de l'ethnophilosophie''. Paris : Maspero, p. 33.</ref>. Cette définition exigeante fait de l'écriture, de l'individualité et de l'intentionnalité philosophique les critères de la philosophie africaine.
Le débat sur l'ethnophilosophie a structuré plusieurs décennies de philosophie africaine et a eu des conséquences pédagogiques, politiques et épistémologiques majeures. Il a aussi conduit à une réflexion approfondie sur la nature de la philosophie elle-même et sur les conditions de possibilité d'une philosophie authentiquement africaine.
===La critique de la Négritude : Towa contre Senghor===
Un deuxième grand débat porte sur le mouvement de la Négritude, et plus particulièrement sur l'interprétation qu'en a donnée Léopold Sédar Senghor (1906-2001). La Négritude, mouvement littéraire et idéologique né dans les années 1930, visait à affirmer la dignité et la spécificité de l'identité noire face au racisme colonial et à l'assimilation culturelle.
Senghor a développé une philosophie de la Négritude articulée autour de l'idée d'une spécificité ontologique et épistémologique du Noir. Il affirmait notamment que « l'émotion est nègre comme la raison est hellène », suggérant que les Noirs auraient un mode de connaissance particulier, plus intuitif, plus émotionnel, plus participatif que le mode rationnel et analytique des Européens. Senghor parlait de « raison intuitive » et de « connaissance par participation »<ref>Senghor, Léopold Sédar (1964). ''Liberté I : Négritude et humanisme''. Paris : Seuil.</ref>.
Marcien Towa a mené une critique sévère de cette conception. Dans ''Léopold Sédar Senghor : Négritude ou servitude ?'' (1971), Towa accuse Senghor de trois erreurs capitales<ref>Towa, Marcien (1971). ''Léopold Sédar Senghor : Négritude ou servitude ?''. Yaoundé : CLE.</ref> :
1. La biologisation du culturel (essentialisme racial)
Towa reproche à Senghor de confondre le biologique et le culturel, c'est-à-dire de naturaliser des traits culturels en les présentant comme innés et raciaux. Attribuer l'émotivité au Noir en tant que caractéristique raciale revient à un racisme, même si celui-ci se veut positif. Comme l'écrit Towa : « Ce qui est remarquable dans la thèse de l'émotivité du nègre, c'est la confusion qu'elle implique entre le culturel et le biologique »<ref>Towa, Marcien (1971). ''Léopold Sédar Senghor : Négritude ou servitude ?''. Yaoundé : CLE, p. 12.</ref>. Cette « biologisation du culturel » condamne le Noir à une essence immuable : si l'émotivité est inscrite dans sa race, il ne peut s'en défaire. C'est une forme de déterminisme qui nie la liberté et la transcendance de l'être humain.
2. L'immobilisme
La Négritude senghorienne, en célébrant les valeurs traditionnelles africaines (émotivité, communion avec la nature, sens du rythme) et en les opposant aux valeurs occidentales (raison, analyse, technique), enferme l'Africain dans son passé et le détourne de la transformation nécessaire. Plutôt que de favoriser la libération et le développement de l'Afrique, la Négritude senghorienne aboutit à une résignation et à une acceptation de la domination coloniale. Towa affirme que la Négritude de Senghor « s'oppose aux fondements même de ce mouvement, car son but est d'aboutir à neutraliser le Nègre pour lui faire accepter la réalité telle qu'elle est ». Au lieu d'appeler à la transformation, Senghor prône l'acceptation d'une « complémentarité » entre l'Afrique émotionnelle et l'Occident rationnel.
3. L'essentialisme culturel
Senghor propose une vision de l'humanité divisée en races ou cultures ayant chacune leur essence propre. Cette vision aboutit à une « segmentation indéfinie de l'humanité » et empêche la construction d'un humanisme universel. Towa plaide au contraire pour une conception existentialiste de l'identité, où l'être humain se définit par ses actes et ses projets, non par son appartenance raciale ou ethnique.
Towa oppose à la Négritude senghorienne la Négritude de l'Antillais Aimé Césaire, qu'il juge plus authentique car plus orientée vers la lutte contre l'oppression. Alors que Senghor célèbre et fige l'identité noire, Césaire appelle à la révolte. La Négritude césairienne est dynamique, tournée vers l'action transformatrice ; la Négritude senghorienne est statique, tournée vers la contemplation du passé.
Ce débat pose des questions essentielles : Peut-on affirmer une identité culturelle sans tomber dans l'essentialisme ? Comment concilier la valorisation du patrimoine africain et l'ouverture à l'universel ? L'identité est-elle donnée (essence) ou construite (existence) ?
===Le débat tradition-modernité : quel avenir pour la pensée africaine ?===
Un troisième grand débat, transversal aux précédents, concerne le rapport entre tradition et modernité. Ce débat oppose grossièrement deux camps :
Les traditionalistes (ou néo-traditionalistes) estiment que la philosophie africaine doit puiser dans les ressources de la pensée traditionnelle — proverbes, mythes, institutions, langues, sagesses ancestrales. Ils soulignent que ces traditions contiennent une richesse conceptuelle immense méprisée ou occultée par la colonisation. La tâche de la philosophie africaine serait de retrouver, reconstituer, réactualiser cette sagesse ancestrale.
Les modernistes (ou universalistes) considèrent que la philosophie africaine ne doit pas se limiter à l'exhumation du passé traditionnel. Ils insistent sur le fait que la philosophie est une activité critique et créative qui doit s'attaquer aux problèmes contemporains de l'Afrique — développement économique, démocratie, droits humains, justice sociale, éducation, santé, technologie. Se réfugier dans les traditions serait une forme d'archaïsme qui empêche l'Afrique de relever les défis de la modernité.
Paulin Hountondji, après sa phase critique de l'ethnophilosophie, a évolué vers une position nuancée. Dans les années 1990-2000, il a développé le concept de « savoirs endogènes »<ref>Hountondji, Paulin J. (dir.) (1994). ''Les Savoirs endogènes : pistes pour une recherche''. Dakar : CODESRIA.</ref>. Contrairement aux « savoirs traditionnels » (patrimoine figé du passé) ou aux « savoirs indigènes » (construits sans influences extérieures), les savoirs endogènes sont élaborés par les membres d'une société en intégrant de manière critique des éléments externes.
Pour Hountondji, la philosophie africaine doit partir des savoirs endogènes et non des savoirs traditionnels figés. L'objectif n'est pas de revenir en arrière pour exhumer des savoirs traditionnels, mais de développer une production endogène de connaissances qui permette à l'Afrique de rompre avec sa dépendance épistémique et de devenir un producteur de théories à portée universelle. Cette position cherche à dépasser la fausse alternative tradition/modernité.
===Le débat sur l'universalisme et le relativisme culturel===
Un quatrième débat majeur oppose universalistes et particularistes (ou relativistes culturels).
Les particularistes soutiennent que chaque culture a sa propre rationalité, ses propres normes, ses propres valeurs qui ne peuvent être jugées de l'extérieur. La philosophie africaine serait irréductiblement différente de la philosophie occidentale, et il serait illégitime d'imposer à l'Afrique des critères philosophiques élaborés en Occident. Cette position valorise la diversité culturelle et la pluralité des rationalités.
Les universalistes affirment au contraire qu'il existe des normes rationnelles et éthiques universelles, valables pour tous les êtres humains indépendamment de leur culture. Certes, les cultures diffèrent dans leurs expressions particulières, mais la raison humaine est fondamentalement une. La philosophie, en tant qu'exercice de la raison critique, vise l'universel.
Le philosophe ghanéen Kwasi Wiredu a défendu une position d'universalisme modéré. Dans ''Cultural Universals and Particulars: An African Perspective'' (1996), Wiredu soutient qu'il existe des problèmes humains universels (la mort, la souffrance, la justice, la vérité) auxquels toutes les cultures tentent de répondre, mais que les réponses sont culturellement variables<ref>Wiredu, Kwasi (1996). ''Cultural Universals and Particulars: An African Perspective''. Bloomington : Indiana University Press.</ref>. La tâche de la philosophie est de distinguer ce qui est universel de ce qui est particulier, et de dialoguer entre les différentes traditions pour enrichir mutuellement les perspectives.
Wiredu critique à la fois l'ethnocentrisme occidental qui impose ses concepts comme universels et le relativisme culturel qui empêcherait toute critique interculturelle et tout progrès moral. Il plaide pour un dialogue interculturel où chaque tradition philosophique peut contribuer à l'élaboration de vérités à portée universelle.
===Le débat sur la sage-philosophy===
Le débat sur la « sage philosophy » d'Oruka, déjà mentionné dans la section sur les traditions orales, mérite d'être replacé dans le contexte des grands débats. La sage philosophy cherche à montrer que la pensée philosophique critique n'est pas l'apanage de l'Occident ni de l'élite lettrée, mais qu'elle existe aussi chez des penseurs africains traditionnels qui philosophent de manière individuelle et rigoureuse. Cela répond à l'objection de Hountondji selon laquelle la philosophie africaine traditionnelle serait nécessairement collective et anonyme.
Toutefois, la sage philosophy a aussi fait l'objet de critiques. Certains lui reprochent de manquer de rigueur méthodologique (comment distinguer la sagesse populaire de la sagesse philosophique ?), d'idéaliser les sages traditionnels, et de ne pas suffisamment contribuer à la résolution des problèmes contemporains de l'Afrique.
===Conclusion===
Ces débats ne sont pas clos. Ils continuent d'animer la philosophie africaine au XXIᵉ siècle et se renouvellent à la lumière de nouveaux enjeux : la globalisation, les technologies numériques, les crises environnementales, les migrations. Comme le souligne Kwame Gyekye : « La philosophie africaine ne sera vivante que si elle demeure un espace de débats, de controverses, de désaccords féconds. Une philosophie consensuelle et irénique serait une philosophie morte »<ref>Gyekye, Kwame (1997). ''Tradition and Modernity: Philosophical Reflections on the African Experience''. New York : Oxford University Press, p. 218.</ref>.
==Les courants principaux==
La philosophie africaine contemporaine, telle qu'elle s'est développée depuis les années 1960, se caractérise par une diversité de courants de pensée. Ces courants ne sont pas des écoles fermées et exclusives, mais plutôt des tendances intellectuelles qui se croisent, s'influencent mutuellement, se critiquent et se répondent.
===L'Ubuntu et le courant communautarien===
Le courant communautarien s'articule autour du concept d'Ubuntu, un terme issu des langues bantoues d'Afrique australe qui désigne une philosophie de l'humanité partagée et de l'interdépendance. Le principe fondamental de l'Ubuntu est condensé dans l'adage : « Umuntu ngumuntu ngabantu » — « Une personne est une personne par les autres personnes » ou « Je suis parce que nous sommes »<ref>Ramose, Mogobe B. (1999). ''African Philosophy through Ubuntu''. Harare : Mond Books.</ref>.
Cette philosophie repose sur plusieurs piliers. Premièrement, la primauté de la communauté : pour les penseurs communautariens, la communauté n'est pas une simple agrégation d'individus, mais une réalité première. L'individu ne peut se définir et acquérir sa pleine humanité qu'au sein de la communauté et par ses relations avec les autres<ref>Mbiti, John S. (1969). ''African Religions and Philosophy''. London : Heinemann, p. 108-109.</ref><ref>Menkiti, Ifeanyi (1984). « Person and Community in African Traditional Thought », in Wright, R.A. (dir.), ''African Philosophy: An Introduction''. New York : University Press of America, p. 171-181.</ref>. Deuxièmement, l'éthique relationnelle : l'Ubuntu propose une éthique de la relation où la valeur morale d'une action se mesure à sa capacité à promouvoir l'harmonie, la solidarité et le bien-être communautaires. Thaddeus Metz a développé une interprétation sophistiquée de l'Ubuntu comme théorie morale fondée sur la communion et la solidarité<ref>Metz, Thaddeus (2007). « Toward an African Moral Theory », ''Journal of Political Philosophy'', vol. 15, n°3, p. 321-341.</ref>. Troisièmement, la conception de la personne : dans la tradition Ubuntu, la personnalité (personhood) n'est pas donnée à la naissance mais s'acquiert progressivement à travers l'intégration sociale et le développement de vertus relationnelles. Menkiti distingue ainsi le statut d'« être humain » (human being), qui est biologique, du statut de « personne » (person), qui est social et moral<ref>Menkiti, Ifeanyi (1984). « Person and Community in African Traditional Thought », in Wright, R.A. (dir.), ''African Philosophy: An Introduction''. New York : University Press of America, p. 172-173.</ref>.
L'Ubuntu a été mobilisé dans de nombreux contextes pratiques et politiques : la Commission Vérité et Réconciliation en Afrique du Sud post-apartheid<ref>Tutu, Desmond (1999). ''No Future Without Forgiveness''. New York : Doubleday.</ref>, la bioéthique africaine<ref>Jecker, Nancy S. (2020). « Ubuntu and Bioethics », in Imafidon, E. et al. (dir.), ''Handbook of African Philosophy''. Cham : Springer, p. 161-179.</ref>, et la philosophie politique<ref>Ramose, Mogobe B. (2003). « The Philosophy of Ubuntu and Ubuntu as a Philosophy », in Coetzee, P.H. et Roux, A.P.J. (dir.), ''Philosophy from Africa''. Cape Town : Oxford University Press, p. 230-238.</ref>.
Le courant communautarien a suscité des critiques. Kwame Gyekye accuse le « communautarianisme radical » de Menkiti de nier l'autonomie individuelle et propose un « communautarianisme modéré » qui préserve l'individualité<ref>Gyekye, Kwame (1997). ''Tradition and Modernity: Philosophical Reflections on the African Experience''. New York : Oxford University Press, p. 35-76.</ref>. D'autres critiques soulignent que l'emphase sur la conformité communautaire peut justifier l'oppression des minorités<ref>Matolino, Bernard (2014). ''Personhood in African Philosophy''. Pietermaritzburg : Cluster Publications.</ref>. Bernard Matolino estime qu'il est temps d'« exorciser le fantôme communautarien » car cette philosophie idéalise un passé précolonial qui n'a jamais existé sous cette forme homogène.
===Le courant existentialiste et de la libération : Fanon et Mbembe===
Le courant existentialiste et de la libération se concentre sur les questions de liberté, d'aliénation, de conscience et de lutte contre l'oppression.
Frantz Fanon (1925-1961), psychiatre et philosophe martiniquais, est une figure majeure de ce courant. Dans ''Peau noire, masques blancs'' (1952), il analyse les effets psychologiques du colonialisme et du racisme : le système colonial crée une double conscience chez le Noir, qui intériorise le regard méprisant du colonisateur<ref>Fanon, Frantz (1952). ''Peau noire, masques blancs''. Paris : Seuil, p. 5-25.</ref>. Dans ''Les Damnés de la terre'' (1961), Fanon développe sa théorie controversée de la violence comme moyen de libération<ref>Fanon, Frantz (1961). ''Les Damnés de la terre''. Paris : Maspero, p. 41-50.</ref>. Malgré cet accent sur la spécificité de l'expérience noire, Fanon défend dans la conclusion des ''Damnés'' un humanisme universel, appelant à « inventer l'homme total »<ref>Fanon, Frantz (1961). ''Les Damnés de la terre''. Paris : Maspero, p. 303-307.</ref>.
Achille Mbembe (1957-) poursuit et renouvelle la réflexion fanonienne. Dans ''De la postcolonie'' (2000), il analyse les formes de pouvoir et de subjectivité dans l'Afrique postcoloniale<ref>Mbembe, Achille (2000). ''De la postcolonie : Essai sur l'imagination politique dans l'Afrique contemporaine''. Paris : Karthala.</ref>. Dans ''Critique de la raison nègre'' (2013), il analyse la construction historique de la catégorie de « Nègre » et affirme que nous assistons à un « devenir-nègre du monde » : les logiques d'exclusion et de déshumanisation qui ont d'abord visé les Noirs s'étendent à l'ensemble de l'humanité sous le capitalisme néolibéral<ref>Mbembe, Achille (2013). ''Critique de la raison nègre''. Paris : La Découverte.</ref>. Il a développé le concept de « nécropolitique » pour désigner les formes contemporaines de pouvoir qui exercent le droit de décider qui peut vivre et qui doit mourir<ref>Mbembe, Achille (2003). « Necropolitics », ''Public Culture'', vol. 15, n°1, p. 11-40.</ref>. Plus récemment, Mbembe promeut un « afropolitanisme » — une identité africaine cosmopolite, ouverte, hybride<ref>Mbembe, Achille (2007). « Afropolitanisme », ''Africultures'', n°66, p. 9-15.</ref>.
===Le courant herméneutique et de la philosophie professionnelle===
Ce courant insiste sur la nécessité d'une philosophie africaine rigoureuse, écrite, critique et individuelle.
Paulin Hountondji en est le principal représentant (voir la section sur le débat sur l'ethnophilosophie). Au-delà de sa critique, il propose une vision constructive : la philosophie africaine doit être comprise comme l'ensemble des textes écrits par des Africains et qualifiés par leurs auteurs de philosophiques. Il défend un pluralisme philosophique interne à l'Afrique et développe, dans les années 1990-2000, la réflexion sur les « savoirs endogènes »<ref>Hountondji, Paulin J. (dir.) (1994). ''Les Savoirs endogènes : pistes pour une recherche''. Dakar : CODESRIA.</ref>.
Kwasi Wiredu partage le souci de rigueur philosophique mais adopte une approche plus ouverte aux ressources culturelles africaines. Il appelle à une « décolonisation conceptuelle » : examiner de manière critique les concepts philosophiques hérités de la colonisation pour distinguer ceux qui sont véritablement universels de ceux qui sont culturellement spécifiques à l'Occident<ref>Wiredu, Kwasi (1996). ''Cultural Universals and Particulars: An African Perspective''. Bloomington : Indiana University Press, p. 1-19.</ref>. Dans le domaine politique, Wiredu propose de s'inspirer de la pratique akan du consensus pour développer des modèles de démocratie plus inclusifs<ref>Wiredu, Kwasi (1995). « Democracy and Consensus in African Traditional Politics: A Plea for a Non-Party Polity », ''The Centennial Review'', vol. 39, n°1, p. 53-64.</ref>.
===Le courant afrocentrique===
Le courant afrocentrique insiste sur la revalorisation de l'héritage culturel et intellectuel africain. Molefi Kete Asante le définit comme « la perspective qui place les intérêts, les valeurs et les perspectives africaines au centre de toute analyse des phénomènes africains »<ref>Asante, Molefi Kete (1980). ''Afrocentricity: The Theory of Social Change''. Buffalo : Amulefi Publishing Company.</ref>. L'afrocentrisme repose sur le recentrage épistémologique (penser l'Afrique depuis l'Afrique), la continuité culturelle entre l'Afrique ancienne et contemporaine, et la valorisation de l'agencéité africaine.
L'afrocentrisme a été critiqué pour son essentialisme, pour un historicisme jugé fragile (la thèse de la continuité Égypte ancienne-Afrique noire contemporaine), et pour sa tendance à reproduire symétriquement les défauts de l'eurocentrisme. Kwame Anthony Appiah, dans ''In My Father's House'' (1992), lui reproche de perpétuer les logiques raciales qu'il prétend combattre<ref>Appiah, Kwame Anthony (1992). ''In My Father's House: Africa in the Philosophy of Culture''. New York : Oxford University Press.</ref>.
===Le courant de la philosophie interculturelle===
Le courant interculturel cherche à dépasser les oppositions binaires (Afrique/Occident, tradition/modernité) pour promouvoir un dialogue philosophique entre les différentes traditions.
Kwame Anthony Appiah développe le concept de « cosmopolitisme enraciné » (''rooted cosmopolitanism'') : nous pouvons être à la fois enracinés dans nos cultures locales particulières et cosmopolites, ouverts aux autres cultures<ref>Appiah, Kwame Anthony (2006). ''Cosmopolitanism: Ethics in a World of Strangers''. New York : W.W. Norton & Company.</ref>. Appiah critique à la fois le nationalisme culturel étroit et le cosmopolitisme déraciné.
===Conclusion===
Ces courants se répondent et se critiquent mutuellement. Comme le souligne Dismas Masolo : « La philosophie africaine sera vivante tant qu'elle restera un espace de controverses fécondes »<ref>Masolo, Dismas A. (2010). ''Self and Community in a Changing World''. Indianapolis : Indiana University Press, p. 241.</ref>.
==La pensée africaine contemporaine==
Depuis les années 2000, la philosophie africaine a élargi son champ au-delà des débats fondateurs sur l'ethnophilosophie et l'identité. Plusieurs de ses représentants s'engagent dans les questions de globalisation, de technologie, d'écologie et de justice épistémique, formulant des concepts originaux pour penser ces enjeux à partir de l'expérience africaine.
===La décolonisation épistémique : du politique à l'épistémologique===
Si les indépendances politiques des années 1960 ont libéré l'Afrique du joug colonial direct, la dépendance épistémique — la subordination intellectuelle aux savoirs, méthodes et catégories de pensée occidentaux — demeure l'un des principaux défis.
====Valentin-Yves Mudimbe et l'invention de l'Afrique====
Valentin-Yves Mudimbe (1941-2024) est la figure pionnière de cette réflexion. Dans ''The Invention of Africa'' (1988), il démontre comment l'Afrique en tant qu'objet de connaissance a été « inventée » par le discours colonial<ref>Mudimbe, Valentin-Yves (1988). ''The Invention of Africa: Gnosis, Philosophy, and the Order of Knowledge''. Bloomington : Indiana University Press.</ref>.
Mudimbe analyse ce qu'il nomme la « bibliothèque coloniale » — cet ensemble de textes religieux, anthropologiques et administratifs qui ont construit l'Afrique comme un objet à connaître, à dominer, à « sauver »<ref>Mudimbe, Valentin-Yves (1988). ''The Invention of Africa''. Bloomington : Indiana University Press, p. 1-23.</ref>. Contrairement à Edward Said qui avait montré comment l'Occident avait créé un « Orient » fascinant mais menaçant, Mudimbe révèle que l'Afrique a souvent été conçue comme une absence à remplir, un vide culturel justifiant l'intervention civilisatrice coloniale.
Sa contribution majeure réside dans la démonstration que la libération de l'Afrique ne peut se limiter à l'indépendance politique : elle doit passer par une refonte des savoirs. Il faut rompre avec l'idée que seuls les outils de pensée occidentaux peuvent appréhender l'Afrique, tout en résistant aux tentations essentialistes qui, au nom de l'authenticité, créeraient d'autres prisons conceptuelles.
====Les savoirs endogènes====
Le concept de « savoirs endogènes », développé par Hountondji, structure ce débat. Les savoirs endogènes ne sont ni les « savoirs traditionnels » figés dans un passé précolonial idéalisé, ni les « savoirs indigènes » prétendument purs. Ce sont des connaissances produites localement en Afrique, en dialogue critique avec les savoirs venus d'ailleurs<ref>Hountondji, Paulin J. (dir.) (1994). ''Les Savoirs endogènes : pistes pour une recherche''. Dakar : CODESRIA.</ref>. L'objectif est de rompre avec la dépendance épistémique : l'Afrique doit devenir un producteur de théories à portée universelle, non plus seulement un consommateur de théories produites ailleurs.
===La justice cognitive et l'université africaine au XXIᵉ siècle===
La décolonisation épistémique se traduit concrètement dans les débats sur la justice cognitive. Ce concept, développé notamment par Florence Piron en dialogue avec des chercheurs africains et haïtiens, désigne la lutte contre les injustices propres au domaine du savoir<ref>Piron, Florence (dir.) (2016). ''Justice cognitive, libre accès et savoirs locaux : Pour une science ouverte juste, au service du développement local durable''. Québec : Éditions science et bien commun.</ref>.
Les injustices cognitives en Afrique sont multiples : fracture dans l'accès aux publications scientifiques enfermées derrière des murs payants<ref>Piron, Florence (dir.) (2016). ''Justice cognitive, libre accès et savoirs locaux''. Québec : Éditions science et bien commun, p. 75-90.</ref> ; manque de ressources pour la recherche dans les universités africaines<ref>Piron (2016), p. 91-110.</ref> ; invisibilité des connaissances produites en Afrique, rarement numérisées ou indexées<ref>Piron (2016), p. 111-130.</ref> ; domination des bases de données occidentales (Web of Science, SCOPUS) dans la validation des savoirs<ref>Piron (2016), p. 131-150.</ref>.
Face à ces injustices, la science ouverte est proposée comme voie vers la justice cognitive. Des initiatives comme le projet SOHA (Sciences Ouvertes en Haïti et Afrique francophone) et l'archive institutionnelle DICAMES tentent de rendre les savoirs africains accessibles gratuitement en ligne.
===L'Afrique-monde et l'afropolitanisme===
Un autre axe de la pensée contemporaine concerne la refondation de l'identité africaine. Comment penser l'identité africaine dans un monde globalisé ?
Achille Mbembe a le plus profondément renouvelé cette réflexion. Sa ''Critique de la raison nègre'' (2013) analyse la construction du « Nègre » comme figure de l'altérité et de la déshumanisation, puis affirme un « devenir-nègre du monde » où les logiques d'exclusion s'étendent à l'humanité entière sous le capitalisme néolibéral<ref>Mbembe, Achille (2013). ''Critique de la raison nègre''. Paris : La Découverte, p. 5-20.</ref>.
Son concept d'afropolitanisme propose une identité africaine cosmopolite, ouverte, hybride, qui assume les multiples influences culturelles<ref>Mbembe, Achille (2007). « Afropolitanisme », ''Africultures'', n°66, p. 9-15.</ref>. Cette position a suscité des débats : certains y voient une soumission aux valeurs occidentales déguisée en cosmopolitisme ; d'autres saluent une libération des identités essentialistes.
===L'écologie et l'Anthropocène===
La pensée africaine contemporaine s'engage de plus en plus dans les débats écologiques mondiaux. L'Afrique est le continent le moins responsable du changement climatique mais en subit déjà les conséquences les plus graves.
Le philosophe camerounais Jean Godefroy Bidima a proposé une réflexion critique sur les discours de l'Anthropocène. Il met en garde contre les discours apocalyptiques qui, au nom de l'urgence climatique, peuvent étouffer la pensée critique et imposer de nouvelles formes de domination<ref>Bidima, Jean Godefroy (2021). « Anthropocènes et nouveaux discours africains : Habiter le monde avec poésie et critique », in Hengehold, Laura (dir.), ''African Philosophy for the Twenty-First Century''. Rowman & Littlefield, p. 105-130.</ref>.
Bidima souligne que certaines traditions africaines précoloniales portaient une conception intégrée du rapport entre humains et nature — les humains, les animaux, les plantes, les ancêtres, les esprits formant une seule chaîne de vie. Cette vision, longtemps disqualifiée comme « animisme », pourrait offrir des ressources pour repenser notre rapport à l'environnement<ref>Bidima (2021), p. 108-115.</ref>. Il appelle à « habiter le monde avec poésie et critique » : avec poésie, c'est-à-dire en développant une sensibilité et un soin vis-à-vis de tous les êtres ; avec critique, en évitant les pièges du « greenwashing » ou des solutions technocratiques imposées du Nord<ref>Bidima (2021), p. 126-130.</ref>.
===Les archives rituelles et les épistémologies indigènes===
L'historien nigérian Toyin Falola a développé le concept d'« archives rituelles » pour désigner le corpus de connaissances contenues dans les rituels, les mythes, les proverbes, les objets sacrés, les pratiques divinatoires<ref>Falola, Toyin (2018). « Ritual Archives and Indigenous Epistemologies », in ''The Palgrave Handbook of African Social Ethics''. Palgrave Macmillan, p. 461-493.</ref>.
Le système de divination yoruba Ifá, par exemple, constitue un système épistémologique complexe comprenant une ontologie, une épistémologie, une éthique, une dimension mathématique (256 ''odù'' = 2⁸), une dimension historique et une pharmacopée<ref>Falola (2018), p. 464-475.</ref>. Les « archives rituelles » incluent aussi des objets — noix de cola, cauris, sculptures, textiles — qui ne sont pas de simples œuvres d'art mais des textes matériels encodant des savoirs<ref>Falola (2018), p. 478-483.</ref>.
Le défi pour la pensée africaine contemporaine est de réintégrer ces épistémologies dans les systèmes éducatifs et de recherche, non pas de manière nostalgique, mais de manière critique et créative<ref>Falola (2018), p. 484-490.</ref>.
===Le genre, le féminisme et la pensée africaine===
La pensée africaine contemporaine est traversée par les critiques féministes. Des philosophes africaines dénoncent le fait que la philosophie africaine « classique » a été dominée par des hommes et a souvent reproduit les structures patriarcales.
La notion d'Ubuntu, souvent présentée comme valorisant la communauté harmonieuse, est relue par des féministes africaines qui soulignent que cette communauté était souvent inégalitaire : les femmes y étaient subordonnées aux hommes, les cadettes aux aînés<ref>Imafidon, Elvis (2019). ''African Philosophy and the Otherness of Albinism: White Skin, Black Race''. Routledge, p. 85-100.</ref>. La colonisation a souvent aggravé ces inégalités de genre<ref>Ipadeola, Abosede Priscilla (2023). « African Feminist Interrogation of Existential Epistemology: Women as the Other of the Other in PostColonial Africa », in ''The Palgrave Handbook of African Social Ethics''. Palgrave Macmillan, p. 413-429.</ref>.
Dans l'Afrique postcoloniale, les femmes subissent ce que certaines auteures appellent une « double othérisation » : marginalisées à la fois par les structures héritées de la colonisation et par les structures patriarcales locales<ref>Ipadeola (2023), p. 414-420.</ref>. Des philosophes comme Nkiru Nzegwu, Oyèrónké Oyěwùmí, Sophie Oluwole et Abosede Ipadeola développent des approches féministes articulant critique du patriarcat et critique du colonialisme.
===Le numérique et l'intelligence artificielle===
Le continent africain connaît une transformation numérique accélérée, même si elle reste très inégale. Selon le rapport GSMA ''The Mobile Economy Africa 2025'', 416 millions de personnes utilisent l'internet mobile en Afrique, mais près de 75 % de la population reste non connectée<ref>GSMA (2025). ''The Mobile Economy Africa 2025''. London : GSMA, p. 1-5.</ref>. Des innovations comme le paiement mobile (M-Pesa au Kenya, lancé en 2007) ont toutefois montré que le continent pouvait produire des solutions technologiques originales.
Cette transformation pose des questions philosophiques que plusieurs auteurs commencent à formuler : la question de la souveraineté sur les infrastructures numériques et les données ; celle de l'adéquation des algorithmes utilisés en Afrique aux réalités locales ; et le risque de ce qu'Achille Mbembe appelle une nouvelle forme de « colonialisme numérique »<ref>Mbembe, Achille (2021). « Out of the Dark Night: Towards a Planetary Ethics », in ''Cambridge University Press Blog'', 28 février 2021.</ref>. Laura Schelenz souligne que la numérisation en Afrique reproduit souvent des asymétries de pouvoir préexistantes<ref>Schelenz, Laura (2018). « Digitalization in Africa: Interdisciplinary Perspectives on Technology, Development, and Justice », ''International Journal of Digital Society'', p. 1-12.</ref>.
Certains philosophes explorent la possibilité d'inspirer, à partir des principes d'Ubuntu (interconnexion, solidarité, responsabilité collective), des architectures de réseaux sociaux moins extractives ou des formes de gouvernance algorithmique plus participatives.
==Thématiques philosophiques==
La philosophie africaine aborde un ensemble de questions — ontologie, épistémologie, éthique, philosophie de la religion, conceptions du temps et de la personne — qui, selon les auteurs, tantôt prolongent des préoccupations universelles, tantôt proposent des voies distinctes. Les reconstructions qui suivent, souvent disputées entre partisans de l'ethnophilosophie et tenants de la philosophie professionnelle, doivent être lues comme des interprétations savantes, non comme des descriptions exhaustives de « la » pensée d'un continent.
===Ontologie et métaphysique===
Plusieurs auteurs ont tenté de dégager, à partir de traditions diverses, les traits d'une ontologie proprement africaine. Le point de départ le plus discuté reste le concept de force vitale, proposé par Placide Tempels dans sa ''Philosophie bantoue'' (1945).
====La philosophie de la force vitale selon Tempels====
Pour Tempels, l'être dans la pensée bantoue ne se définit pas comme « ce qui est » mais comme « ce qui est force »<ref>Tempels, Placide (1945). ''La Philosophie bantoue''. Paris : Présence Africaine, p. 35.</ref>. Cette conception implique que l'être est intrinsèquement dynamique : le Dieu suprême est la Force suprême, et tous les êtres participent à cette dynamique universelle des forces<ref>Tempels (1945), p. 40-45.</ref>.
L'univers, dans la reconstruction qu'en propose Tempels, s'explique comme une interrelation de forces au sein de tout le domaine de l'existence, organisées dans une hiérarchie : Dieu (origine de la force vitale), les ancêtres et les morts-vivants, la communauté humaine vivante, puis le monde animé et inanimé.
====L'ontologie relationnelle====
La reconstruction de Tempels met en avant une ontologie relationnelle. Il souligne que « le concept d'êtres séparés, de substances qui se trouvent côte à côte, entièrement indépendantes les unes des autres, est étranger à la pensée bantoue »<ref>Tempels (1945), p. 58.</ref>. Un ''muntu'' (personne) vivant est « en relation d'être à être avec Dieu, avec les membres de son clan, avec sa famille et avec ses descendants »<ref>Tempels (1945), p. 66.</ref>. Chaque personne forme un maillon dans la chaîne des forces vitales.
====Critique et développements contemporains====
La théorie de Tempels a été influente mais aussi critiquée. Certains philosophes africains lui reprochent d'avoir essentialisé et homogénéisé la pensée africaine, en présentant comme « la » philosophie bantoue ce qui est en réalité sa propre reconstruction à partir d'observations partielles. Kwame Gyekye propose une interprétation plus nuancée de l'ontologie akan qui, tout en reconnaissant la dimension communautaire, insiste sur l'individualité irréductible de la personne<ref>Gyekye, Kwame (1997). ''Tradition and Modernity: Philosophical Reflections on the African Experience''. Oxford : Oxford University Press.</ref>. Il convient de souligner que la diversité des sociétés africaines rend toute généralisation sur « l'ontologie africaine » nécessairement schématique : les conceptions métaphysiques varient considérablement d'une culture à l'autre.
===La personne humaine : ontologie et normativité===
La question de la personnalité (''personhood'') constitue l'un des débats les plus riches de la philosophie africaine contemporaine.
Au niveau ontologique, la personne dans plusieurs traditions africaines est conçue comme un être composé de plusieurs éléments physiques et non-physiques. Chez les Akan du Ghana, Kwasi Wiredu identifie cinq éléments constitutifs : le ''nipadua'' (corps physique), l{{'}}''okra'' (principe de vie), le ''sunsum'' (principe de personnalité), le ''mogya'' (sang, principe de parenté) et le ''ntoro'' (hérité du père)<ref>Wiredu, Kwasi (1995). « La personne et la communauté dans la pensée akan », in Wiredu et Gyekye, ''Person and Community''. Washington : Council for Research in Values and Philosophy, p. 132.</ref>. Chez les Yoruba, Segun Gbadegesin postule que l{{'}}''eniyan'' possède quatre composantes : l{{'}}''ara'' (corps), l{{'}}''okan'' (cœur), l{{'}}''emi'' (élément vital) et l{{'}}''ori'' (tête spirituelle)<ref>Gbadegesin, Segun (1991). ''African Philosophy: Traditional Yoruba Philosophy and Contemporary African Realities''. New York : Peter Lang, p. 53-58.</ref>.
Au-delà de la dimension ontologique, la philosophie africaine accorde une importance centrale à la dimension normative de la personnalité. Selon Ifeanyi Menkiti, la personnalité n'est pas donnée à la naissance mais s'acquiert progressivement à travers la socialisation et l'accomplissement moral<ref>Menkiti, Ifeanyi (1984). « Person and Community in African Traditional Thought », in Wright, R.A. (dir.), ''African Philosophy: An Introduction''. Lanham : University Press of America, p. 176.</ref>.
Le débat entre Menkiti et Gyekye a occupé la philosophie africaine pendant plus de trente ans. Gyekye qualifie la thèse de Menkiti de « communautarianisme radical » et la juge confuse : il propose un « communautarianisme modéré » reconnaissant que l'individu possède des attributs comme la rationalité et la capacité au choix moral qui ne dérivent pas de la communauté<ref>Gyekye, Kwame (1997). ''Tradition and Modernity''. Oxford : Oxford University Press, p. 47-49.</ref>.
===Épistémologie===
L'épistémologie dans le contexte africain présente plusieurs traits caractéristiques, tels que reconstruits par différents auteurs.
Léopold Sédar Senghor a proposé que le point de départ de ce qu'il appelle l'« épistémologie négro-africaine » serait le postulat « Je sens, donc je suis », par contraste avec le ''Cogito'' cartésien<ref>Senghor, Léopold Sédar (1964). ''Liberté I : Négritude et humanisme''. Paris : Seuil, p. 24.</ref>. Cette formulation, fortement influencée par le romantisme français, a été contestée (voir la critique de Towa ci-dessus). Zubairi bin Nasseem a proposé un autre point de départ : « Nous sommes, donc je suis », soulignant la priorité du collectif dans certaines épistémologies africaines.
Selon la reconstruction de Tempels, reprise par plusieurs commentateurs, la connaissance dans certaines perspectives africaines traditionnelles consiste à comprendre la nature des forces et leur interaction cosmique. La vraie sagesse réside dans la connaissance ontologique : l'intelligence des forces, de leur hiérarchie, de leur cohésion et de leur interaction.
L'autorité des anciens joue un rôle central dans certaines épistémologies traditionnelles africaines. Le présupposé est que plus une personne vieillit, plus elle approche de la sagesse. Kwasi Wiredu note que « la société traditionnelle était fondée sur une communauté de croyances partagées dans la sagesse de l'âge, la sainteté de la chefferie et la force contraignante des coutumes et usages de nos ancêtres »<ref>Wiredu, Kwasi (1980). ''Philosophy and an African Culture''. Cambridge : Cambridge University Press, p. 45.</ref>.
K.C. Anyanwu soutient que, dans certaines traditions africaines, la connaissance vient « de la coopération de toutes les facultés et expériences humaines. Il voit, sent, imagine, raisonne ou pense et intuitionne tout à la fois »<ref>Anyanwu, K.C. (1984). « The African Worldview and Theory of Knowledge », in Wright, Richard A. (dir.), ''African Philosophy: An Introduction''. Lanham : University Press of America, p. 80.</ref>. Cette caractérisation, partagée par plusieurs auteurs, doit être maniée avec prudence : elle décrit des tendances observées dans certaines traditions, non une propriété universelle de « l'Africain » en tant que tel. La diversité des sociétés africaines rend illusoire toute généralisation uniforme en la matière.
===Éthique et philosophie morale===
L'éthique telle qu'elle a été reconstruite par plusieurs philosophes africains contemporains se caractérise par une orientation communautaire et relationnelle, ainsi que par un accent mis sur les vertus plutôt que sur des principes abstraits.
L'Ubuntu incarne cette éthique. Desmond Tutu l'explicite ainsi : « Ubuntu parle de l'essence même de l'être humain. C'est dire : "Mon humanité est liée, inextricablement liée à la vôtre" »<ref>Tutu, Desmond (1999). ''No Future Without Forgiveness''. New York : Doubleday, p. 31.</ref>. Les vertus associées à l'Ubuntu incluent la politesse, la compassion, la bienveillance, l'altruisme, le pardon, la tolérance<ref>Gyekye, Kwame (1992). « Person and Community in African Thought », in Wiredu et Gyekye (dir.), ''Person and Community''. Washington : Council for Research in Values and Philosophy, p. 109-110.</ref>.
Segun Gbadegesin souligne que dans la tradition yoruba, le « Je » n'est qu'un « Nous » d'un autre point de vue. Le couronnement de la vie personnelle est d'être utile à sa communauté<ref>Gbadegesin (1991), p. 58.</ref>.
===Philosophie de la religion et spiritualité===
Selon les travaux d'ethnologues et de philosophes de la religion comme John S. Mbiti et Bolaji Idowu, la religion occupe une place centrale dans la vie et la pensée de nombreuses sociétés africaines traditionnelles. La croyance en un Être Suprême y est, selon ces auteurs, fondamentale. Idowu le définit comme « l'Être vivant éternel qui est le créateur et la source de toute vie »<ref>Idowu, Bolaji (1962). ''Olodumare: God in Yoruba Belief''. London : Longmans.</ref>.
Dans de nombreuses traditions, des êtres spirituels intermédiaires (divinités, esprits) servent de médiateurs entre les humains et l'Être Suprême. Le culte des ancêtres reconnaît le rôle continu des défunts dans la vie de la communauté. Selon John S. Mbiti, les ancêtres sont les « morts-vivants » (''living-dead'') dont les personnalités vivent encore avec le peuple<ref>Mbiti, John S. (1971). ''New Testament Eschatology in an African Background''. Oxford : Oxford University Press, p. 133.</ref>.
===Philosophie du temps, de la mort et de l'au-delà===
John S. Mbiti a proposé une théorie influente selon laquelle, dans certaines conceptions africaines traditionnelles, le temps serait bidimensionnel : passé (''Zamani'') et présent (''Sasa''), la dimension future étant largement absente<ref>Mbiti, John S. (1989). ''African Religions and Philosophy'', 2ᵉ édition. Oxford : Heinemann, p. 17.</ref>. Cette thèse, stimulante, a été nuancée par d'autres chercheurs.
La mort, dans de nombreuses traditions africaines, n'est pas conçue comme une fin absolue mais comme une transition vers un autre mode d'existence<ref>Ekore, R.I. et Lanre-Abass, B. (2016). « African Cultural Concept of Death and the Idea of Advance Care Directives », ''Indian Journal of Palliative Care'', vol. 22, n°4, p. 369.</ref>. De nombreuses cultures africaines intègrent également une croyance en la réincarnation : les défunts vertueux peuvent se réincarner dans leur lignée familiale<ref>Ekore et Lanre-Abass (2016), p. 370.</ref>.
Le philosophe Aribiah David Attoe a soulevé des questions sur le sens de la vie à la lumière de la « seconde mort » — l'idée que même les ancêtres finissent par être oubliés, ce qui interroge la portée réelle de l'immortalité ancestrale<ref>Attoe, Aribiah David (2020). « Death, Meaning and African Philosophy », ''Philarchive''.</ref>.
===Philosophie politique et sociale===
L'une des questions centrales de la philosophie politique africaine concerne le rapport entre communauté et individu. Le communautarisme met l'accent sur la primauté de la communauté, comme l'affirme Claude Ake : certaines traditions africaines « ne permettent pas que l'individu ait des revendications qui peuvent l'emporter sur celles de la société »<ref>Ake, Claude (1987). « The African Context of Human Rights », ''Africa Today'', vol. 34, n°1-2, p. 5.</ref>.
Thaddeus Metz soutient que le communautarisme africain, correctement compris, peut respecter la différence individuelle : l'éthique africaine prescrit de valoriser les gens en vertu de leur capacité à communier, c'est-à-dire à être partie prenante de relations de partage<ref>Metz, Thaddeus (2020). « African Communitarianism and Difference », in Imafidon, Elvis (dir.), ''Handbook of African Philosophy of Difference''. Cham : Springer, p. 31-49.</ref>.
Les valeurs d'Ubuntu ont inspiré des innovations institutionnelles, notamment les tribunaux ''Gacaca'' au Rwanda après le génocide de 1994, fondés sur les principes de réconciliation et de restauration de l'équilibre communautaire<ref>Clark, Phil (2010). ''The Gacaca Courts, Post-Genocide Justice and Reconciliation in Rwanda''. Cambridge : Cambridge University Press, p. 50-76.</ref>.
===Conclusion===
Ces thématiques, loin d'être cloisonnées, se recoupent et s'alimentent mutuellement. Elles font l'objet de reconstructions savantes et de débats vifs, reflétant la diversité interne de la pensée africaine plutôt qu'un système unifié.
==Défis et perspectives==
La philosophie africaine, après plus de six décennies d'existence académique, se trouve aujourd'hui à un tournant de son évolution, marqué par des défis multiples et des perspectives prometteuses.
===Le défi de la décolonisation épistémique===
Achille Mbembe a souligné que les universités africaines ont été décolonisées au niveau du personnel mais que les curricula et les méthodologies héritées de l'époque coloniale sont souvent restées intactes<ref>Mbembe, Achille (2016). « Decolonizing the University: New Directions », ''Arts and Humanities in Higher Education'', vol. 15, n°1, p. 29-45.</ref>. Dans de nombreux départements de philosophie en Afrique, l'enseignement porte principalement sur la philosophie occidentale, consacrant une place marginale à la philosophie africaine<ref>Bolarinwa, A.H. (2022). « Decolonising Knowledge Production in Africa: Implications for Educational Development », ''African Journal of Contemporary Research'', p. 1-15.</ref>.
La question de la langue est centrale. Ngũgĩ wa Thiong'o a montré comment l'imposition des langues coloniales comme langues d'enseignement a creusé un fossé entre les intellectuels et les masses populaires<ref>Ngũgĩ wa Thiong'o (1986). ''Decolonising the Mind: The Politics of Language in African Literature''. London : James Currey.</ref>. La philosophie africaine est aujourd'hui écrite majoritairement en anglais et en français<ref>Hountondji, Paulin J. (1997). ''Combats pour le sens : Un itinéraire africain''. Cotonou : Les Flamboyants, p. 217-230.</ref>, ce qui entraîne souvent des pertes de sens dans la traduction de concepts comme ''ubuntu'', ''ujamaa'' ou ''maat''.
Wiredu a proposé la décolonisation conceptuelle comme remède : les philosophes africains doivent examiner de manière critique les concepts qu'ils utilisent et éviter les conceptualisations étrangères restées dans leur pensée par inertie<ref>Wiredu, Kwasi (1998). « Toward Decolonizing African Philosophy and Religion », ''African Studies Quarterly'', vol. 1, n°4, p. 17-46.</ref>.
===Le défi de la production et de la circulation des savoirs===
Les conditions matérielles et institutionnelles de la production philosophique en Afrique restent très inégales. Florence Piron a mis en évidence les « injustices cognitives » dont souffrent les chercheurs africains : accès limité aux publications scientifiques mondiales enfermées derrière des murs payants, manque de ressources pour la recherche, invisibilité des travaux produits en Afrique<ref>Piron, Florence (dir.) (2016). ''Justice cognitive, libre accès et savoirs locaux''. Québec : Éditions science et bien commun, p. 75-150.</ref>.
Edwin Etieyibo et Jonathan Chimakonam ont montré comment le système mondial de publication scientifique, dominé par des bases de données occidentales (Web of Science, SCOPUS), crée une dépendance structurelle : publier dans une revue africaine ne « compte » pas pour la carrière d'un chercheur africain, qui doit publier dans des revues du Nord pour être reconnu<ref>Etieyibo, Edwin et Chimakonam, Jonathan (2018). « African Philosophy and the Epistemic Marginalization of Women », ''Philosophical Papers'', vol. 47, n°1, p. 33-59.</ref>.
===Le défi de la pertinence pratique===
La philosophie africaine académique est parfois accusée d'être trop abstraite, trop déconnectée des réalités vécues par la majorité des Africains. Ada Agada a critiqué le fait que la philosophie africaine tourne en rond sur les mêmes débats méthodologiques sans produire de théories capables de résoudre les problèmes africains<ref>Agada, Ada (2013). « Is African Philosophy Progressing? A Critical Analysis », ''Filosofia Theoretica'', vol. 2, n°2, p. 239-255.</ref>.
Face à ce défi, plusieurs philosophes plaident pour une philosophie engagée, qui intervienne dans les débats publics et conseille les décideurs politiques<ref>Lajul, Wilfred (2020). « The Task of African Political Philosophy », ''African Journal of Political Science'', p. 179-195.</ref>.
===Les défis de la mondialisation et de la technologie===
La mondialisation est à la fois une menace pour la diversité culturelle et une opportunité pour la diffusion des idées africaines. Getye Abneh a analysé comment l'hégémonie culturelle occidentale véhiculée par la mondialisation entre en tension avec les valeurs communautaires de nombreuses sociétés africaines<ref>Abneh, Getye (2015). « The Contribution of African Philosophy in Challenging Western Hegemony and Globalization », ''Journal of Philosophy and Culture'', p. 1-18.</ref>. La transformation numérique du continent (voir la section « Le numérique et l'intelligence artificielle » ci-dessus) prolonge ces tensions sur le terrain technologique.
===Perspectives===
Parmi les perspectives prometteuses : la construction de systèmes philosophiques africains à portée universelle, comme le ''Consolationism'' d'Ada Agada<ref>Agada, Ada (2021). ''Consolationism and Comparative African Philosophy''. London : Routledge.</ref>, l{{'}}''Ezumezu'' de Jonathan Chimakonam<ref>Chimakonam, Jonathan (2019). ''Ezumezu: A System of Logic for African Philosophy''. Cham : Springer.</ref>, ou la théorie morale relationnelle de Thaddeus Metz<ref>Metz, Thaddeus (2022). ''A Relational Moral Theory: African Ethics in and beyond the Continent''. Oxford : Oxford University Press.</ref> ; le développement de la science ouverte et de la justice cognitive ; l'essor d'une philosophie publique africaine qui sort de l'université ; le dialogue interculturel avec d'autres traditions philosophiques (chinoise, indienne, islamique, latino-américaine) ; et l'éducation philosophique des jeunes générations.
La philosophie africaine du XXIᵉ siècle est un effort continu d'invention de nouvelles manières de penser pour un monde plus juste et plus humain.
==Principaux philosophes==
===Kwasi Wiredu (1931-2022)===
Né à Kumasi (Ghana), formé à l'Université du Ghana puis à Oxford, Wiredu a enseigné dans plusieurs universités ghanéennes et américaines. Son projet central — la décolonisation conceptuelle — vise à identifier les conceptualisations étrangères restées dans la pensée africaine par inertie et à exploiter les ressources des cadres conceptuels africains<ref>Wiredu, Kwasi (2002). « Conceptual Decolonization as an Imperative in Contemporary African Philosophy: Some Personal Reflections », ''Rue Descartes'', n°36, p. 53-64.</ref>. Il a notamment contribué à l'analyse de la conception akan de la personne et proposé un modèle de démocratie consensuelle<ref>Wiredu, Kwasi (1996). ''Cultural Universals and Particulars: An African Perspective''. Bloomington : Indiana University Press.</ref>.
Œuvres principales : ''Philosophy and an African Culture'' (1980) ; ''Cultural Universals and Particulars'' (1996) ; ''A Companion to African Philosophy'' (2004, codirigé).
===Paulin Hountondji (1942-2024)===
Né à Abidjan (Côte d'Ivoire), décédé à Cotonou (Bénin), Hountondji a été formé au lycée Henri IV puis à l'École normale supérieure de Paris, où il a obtenu l'agrégation de philosophie avant de soutenir une thèse sur Husserl sous la direction de Paul Ricœur<ref>Hountondji, Paulin J. (1997). ''Combats pour le sens : Un itinéraire africain''. Cotonou : Les Flamboyants.</ref>. Il a enseigné à l'Université d'Abomey-Calavi et exercé des fonctions ministérielles au Bénin (1990-1993). Sa critique de l'ethnophilosophie et sa réflexion sur les « savoirs endogènes » sont analysées dans les sections précédentes.
Œuvres principales : ''Sur la « philosophie africaine »'' (1977) ; ''African Philosophy: Myth and Reality'' (1983) ; ''Les Savoirs endogènes'' (1994, dir.) ; ''Combats pour le sens'' (1997).
===Achille Mbembe (1957-)===
Né au Cameroun, formé en France (Sciences Po Paris, Paris I), Mbembe est directeur de recherche au WISER (Université du Witwatersrand, Johannesburg). Ses contributions majeures — postcolonie, ''Critique de la raison nègre'', nécropolitique, afropolitanisme — sont analysées dans les sections précédentes.
Œuvres principales : ''De la postcolonie'' (2000) ; ''Sortir de la grande nuit'' (2010) ; ''Critique de la raison nègre'' (2013) ; ''Politiques de l'inimitié'' (2016) ; ''Brutalisme'' (2020).
===Kwame Gyekye (1939-2019)===
Philosophe ghanéen, Gyekye est célèbre pour sa critique du « communautarianisme radical » de Menkiti et sa proposition d'un « communautarianisme modéré » (voir les sections précédentes). Son ouvrage ''Tradition and Modernity'' (1997) explore la navigation entre valeurs traditionnelles et exigences de la modernité<ref>Gyekye, Kwame (1997). ''Tradition and Modernity: Philosophical Reflections on the African Experience''. New York : Oxford University Press.</ref>.
Œuvres principales : ''An Essay on African Philosophical Thought'' (1987) ; ''Tradition and Modernity'' (1997) ; ''African Cultural Values'' (2003).
===Valentin-Yves Mudimbe (1941-2024)===
Philosophe, romancier et poète congolais, Mudimbe a profondément influencé les études africaines et postcoloniales par son analyse de la « bibliothèque coloniale » (voir la section sur la décolonisation épistémique)<ref>Mudimbe, Valentin-Yves (1988). ''The Invention of Africa''. Bloomington : Indiana University Press.</ref>.
Œuvres principales : ''The Invention of Africa'' (1988) ; ''Parables and Fables'' (1991) ; ''The Idea of Africa'' (1994).
===Frantz Fanon (1925-1961)===
Né en Martinique, Fanon est une figure incontournable de la philosophie africaine en raison de son engagement dans la lutte anticoloniale en Algérie et de son influence massive sur la pensée de la libération. Dans ''Peau noire, masques blancs'' (1952), il analyse les effets psychologiques du colonialisme<ref>Fanon, Frantz (1952). ''Peau noire, masques blancs''. Paris : Seuil.</ref>. Dans ''Les Damnés de la terre'' (1961), il développe sa théorie controversée de la violence libératrice, tout en appelant in fine à un humanisme universel<ref>Fanon, Frantz (1961). ''Les Damnés de la terre''. Paris : Maspero.</ref>.
Œuvres principales : ''Peau noire, masques blancs'' (1952) ; ''L'An V de la révolution algérienne'' (1959) ; ''Les Damnés de la terre'' (1961).
===Autres figures importantes===
Marcien Towa (1931-2014), philosophe camerounais, auteur de ''Léopold Sédar Senghor : Négritude ou servitude ?'' (1971) et de ''Essai sur la problématique philosophique dans l'Afrique actuelle'' (1979).
Léopold Sédar Senghor (1906-2001), poète, homme d'État sénégalais et théoricien de la Négritude.
Henry Odera Oruka (1944-1995), philosophe kenyan, créateur du concept de « sage philosophy ».
Thaddeus Metz (1969-), philosophe sud-africain d'origine américaine, auteur d'une théorie morale relationnelle basée sur les principes africains.
Sophie Oluwole (1936-2018), philosophe nigériane, première femme à obtenir un doctorat en philosophie au Nigeria, spécialiste de la philosophie yoruba.
==Références==
{{references}}
==Bibliographie sélective==
===Ouvrages généraux et introductions===
====Manuels et encyclopédies====
* Wiredu, Kwasi (dir.) (2004). ''A Companion to African Philosophy''. Oxford : Wiley-Blackwell, 568 p.
* Imafidon, Elvis, Tshivhase, Mpho et Freter, Björn (dir.) (2023). ''Handbook of African Philosophy''. Cham : Springer, 850 p.
* Eze, Emmanuel Chukwudi (dir.) (1998). ''African Philosophy: An Anthology''. Oxford : Blackwell Publishers, 496 p.
* Coetzee, P.H. et Roux, A.P.J. (dir.) (2002). ''Philosophy from Africa: A Text with Readings''. 2ᵉ édition. Cape Town : Oxford University Press, 672 p.
* Brown, Lee M. (dir.) (2004). ''African Philosophy: New and Traditional Perspectives''. Oxford : Oxford University Press, 256 p.
====Histoires de la philosophie africaine====
* Hallen, Barry (2002). ''A Short History of African Philosophy''. Bloomington : Indiana University Press, 144 p.
* Masolo, Dismas A. (1994). ''African Philosophy in Search of Identity''. Bloomington : Indiana University Press, 311 p.
* Smet, A.J. (1980). ''Histoire de la philosophie africaine contemporaine : Courants et problèmes''. Kinshasa : Faculté de Théologie Catholique, 450 p.
===Textes fondateurs et classiques===
====Philosophie pharaonique====
* Obenga, Théophile (1990). ''La philosophie africaine de la période pharaonique : 2780-330 avant notre ère''. Paris : L'Harmattan, 526 p.
* Bilolo, Mubabinge (2003). ''Les cosmo-théologies philosophiques de l'Égypte Antique''. 2 volumes. Munich : Menaibuc, 650 p.
* Griaule, Marcel (1948). ''Dieu d'eau : Entretiens avec Ogotemmêli''. Paris : Fayard, 224 p.
* Diop, Cheikh Anta (1954). ''Nations nègres et culture''. Paris : Présence Africaine, 564 p.
====Philosophie traditionnelle et ethnophilosophie====
* Tempels, Placide (1945/1949). ''La Philosophie bantoue''. Paris : Présence Africaine, 126 p.
* Kagame, Alexis (1956). ''La Philosophie bantu-rwandaise de l'être''. Bruxelles : Académie Royale des Sciences Coloniales, 447 p.
* Kagame, Alexis (1976). ''La Philosophie bantu comparée''. Paris : Présence Africaine, 352 p.
* Mbiti, John S. (1969). ''African Religions and Philosophy''. London : Heinemann, 290 p.
===Critique de l'ethnophilosophie et philosophie professionnelle===
* Hountondji, Paulin J. (1977). ''Sur la « philosophie africaine ». Critique de l'ethnophilosophie''. Paris : Maspero, 284 p.
* Hountondji, Paulin J. (1983). ''African Philosophy: Myth and Reality''. Bloomington : Indiana University Press, 231 p.
* Towa, Marcien (1971). ''Léopold Sédar Senghor : Négritude ou servitude ?''. Yaoundé : Éditions CLE, 120 p.
* Towa, Marcien (1979). ''Essai sur la problématique philosophique dans l'Afrique actuelle''. Yaoundé : Éditions CLE, 180 p.
* Bodunrin, Peter (1981). ''Philosophy in Africa: Trends and Perspectives''. Ile-Ife : University of Ife Press, 256 p.
===Philosophie culturelle et herméneutique===
* Wiredu, Kwasi (1980). ''Philosophy and an African Culture''. Cambridge : Cambridge University Press, 200 p.
* Wiredu, Kwasi (1996). ''Cultural Universals and Particulars: An African Perspective''. Bloomington : Indiana University Press, 236 p.
* Gyekye, Kwame (1987). ''An Essay on African Philosophical Thought: The Akan Conceptual Scheme''. Cambridge : Cambridge University Press, 244 p.
* Gyekye, Kwame (1997). ''Tradition and Modernity: Philosophical Reflections on the African Experience''. New York : Oxford University Press, 296 p.
* Gbadegesin, Segun (1991). ''African Philosophy: Traditional Yoruba Philosophy and Contemporary African Realities''. New York : Peter Lang, 228 p.
* Oruka, Henry Odera (1990). ''Sage Philosophy: Indigenous Thinkers and Modern Debate on African Philosophy''. Leiden : E.J. Brill, 336 p.
===Philosophie politique et sociale===
====Panafricanisme et Négritude====
* Senghor, Léopold Sédar (1964). ''Liberté I : Négritude et humanisme''. Paris : Seuil, 444 p.
* Césaire, Aimé (1955). ''Discours sur le colonialisme''. Paris : Présence Africaine, 92 p.
* Nkrumah, Kwame (1964). ''Consciencism: Philosophy and Ideology for Decolonization''. London : Heinemann, 122 p.
* Nyerere, Julius K. (1968). ''Ujamaa: Essays on Socialism''. Dar es Salaam : Oxford University Press, 186 p.
* Cabral, Amilcar (1975). ''Unité et lutte''. Paris : Maspero, 2 volumes.
====Philosophie de la libération====
* Fanon, Frantz (1952). ''Peau noire, masques blancs''. Paris : Seuil, 188 p.
* Fanon, Frantz (1961). ''Les Damnés de la terre''. Paris : Maspero, 241 p.
* Biko, Steve (1978). ''I Write What I Like''. London : Bowerdean Press, 216 p.
====Ubuntu et communautarisme====
* Ramose, Mogobe B. (1999). ''African Philosophy through Ubuntu''. Harare : Mond Books, 196 p.
* Tutu, Desmond (1999). ''No Future Without Forgiveness''. New York : Doubleday, 304 p.
* Shutte, Augustine (2001). ''Ubuntu: An Ethic for a New South Africa''. Pietermaritzburg : Cluster Publications, 180 p.
* Clark, Phil (2010). ''The Gacaca Courts, Post-Genocide Justice and Reconciliation in Rwanda''. Cambridge : Cambridge University Press, 328 p.
* Menkiti, Ifeanyi (1984). « Person and Community in African Traditional Thought », in Wright, Richard A. (dir.), ''African Philosophy: An Introduction''. Lanham : University Press of America, p. 171-181.
===Philosophie contemporaine et théorie postcoloniale===
* Mudimbe, Valentin-Yves (1988). ''The Invention of Africa: Gnosis, Philosophy, and the Order of Knowledge''. Bloomington : Indiana University Press, 241 p.
* Mudimbe, Valentin-Yves (1994). ''The Idea of Africa''. Bloomington : Indiana University Press, 234 p.
* Mbembe, Achille (2000). ''De la postcolonie''. Paris : Karthala, 293 p.
* Mbembe, Achille (2013). ''Critique de la raison nègre''. Paris : La Découverte, 267 p.
* Mbembe, Achille (2016). ''Politiques de l'inimitié''. Paris : La Découverte, 184 p.
* Mbembe, Achille (2020). ''Brutalisme''. Paris : La Découverte, 256 p.
* Appiah, Kwame Anthony (1992). ''In My Father's House: Africa in the Philosophy of Culture''. New York : Oxford University Press, 256 p.
* Appiah, Kwame Anthony (2006). ''Cosmopolitanism: Ethics in a World of Strangers''. New York : W.W. Norton & Company, 196 p.
===Éthique et philosophie morale===
* Metz, Thaddeus (2013). ''Meaning in Life: An Analytic Study''. Oxford : Oxford University Press, 272 p.
* Metz, Thaddeus (2022). ''A Relational Moral Theory: African Ethics in and beyond the Continent''. Oxford : Oxford University Press, 352 p.
* Molefe, Motsamai (2019). ''An African Philosophy of Personhood, Morality, and Politics''. Cham : Palgrave Macmillan, 246 p.
* Matolino, Bernard (2014). ''Personhood in African Philosophy''. Pietermaritzburg : Cluster Publications, 168 p.
* Imafidon, Elvis et al. (dir.) (2019). ''The Palgrave Handbook of African Social Ethics''. Cham : Palgrave Macmillan, 650 p.
===Épistémologie et savoirs endogènes===
* Hountondji, Paulin J. (dir.) (1994). ''Les Savoirs endogènes : pistes pour une recherche''. Dakar : CODESRIA, 348 p.
* Hountondji, Paulin J. (1997). ''Combats pour le sens : Un itinéraire africain''. Cotonou : Les Flamboyants, 452 p.
* Hallen, Barry (2000). ''The Good, the Bad, and the Beautiful: Discourse about Values in Yoruba Culture''. Bloomington : Indiana University Press, 168 p.
* Hallen, Barry et Sodipo, J. Olubi (1997). ''Knowledge, Belief, and Witchcraft: Analytic Experiments in African Philosophy''. 2ᵉ édition. Stanford : Stanford University Press, 196 p.
===Philosophie de la religion===
* Idowu, Bolaji (1962). ''Olodumare: God in Yoruba Belief''. London : Longmans, 222 p.
* Mbiti, John S. (1970). ''Concepts of God in Africa''. London : SPCK, 348 p.
* Paris, Peter J. (1995). ''The Spirituality of African Peoples''. Minneapolis : Fortress Press, 208 p.
===Logique et métaphysique===
* Chimakonam, Jonathan O. (2019). ''Ezumezu: A System of Logic for African Philosophy and Studies''. Cham : Springer, 332 p.
* Agada, Ada (2021). ''Consolationism and Comparative African Philosophy''. London : Routledge, 226 p.
* Asouzu, Innocent I. (2004). ''The Method and Principles of Complementary Reflection in and beyond African Philosophy''. Münster : LIT Verlag, 544 p.
===Philosophie du XXIᵉ siècle===
* Bidima, Jean Godefroy et Hengehold, Laura (dir.) (2021). ''African Philosophy for the Twenty-First Century: Acts of Transition''. Lanham : Rowman & Littlefield, 240 p.
* Imafidon, Elvis (dir.) (2020). ''Handbook of African Philosophy of Difference''. Cham : Springer, 468 p.
* Goredema, Regis (dir.) (2021). ''Well-Being in African Philosophy''. Lanham : Rowman & Littlefield, 284 p.
===Justice cognitive et décolonisation des savoirs===
* Piron, Florence (dir.) (2016). ''Justice cognitive, libre accès et savoirs locaux''. Québec : Éditions science et bien commun, 252 p.
* Ngũgĩ wa Thiong'o (1986). ''Decolonising the Mind: The Politics of Language in African Literature''. London : James Currey, 114 p.
* Santos, Boaventura de Sousa (2014). ''Epistemologies of the South: Justice against Epistemicide''. Boulder : Paradigm Publishers, 312 p.
===Philosophie féministe africaine===
* Nzegwu, Nkiru (2006). ''Family Matters: Feminist Concepts in African Philosophy of Culture''. Albany : State University of New York Press, 292 p.
* Oyěwùmí, Oyèrónkẹ́ (1997). ''The Invention of Women: Making an African Sense of Western Gender Discourses''. Minneapolis : University of Minnesota Press, 216 p.
* Steady, Filomina Chioma (dir.) (1981). ''The Black Woman Cross-Culturally''. Cambridge : Schenkman Publishing Company, 645 p.
===Revues spécialisées===
* ''Filosofia Theoretica: Journal of African Philosophy, Culture and Religions'' (Nigeria, depuis 2011)
* ''South African Journal of Philosophy'' (depuis 1982)
* ''Philosophia Africana: Analysis of Philosophy and Issues in Africa and the Black Diaspora'' (depuis 1997)
* ''Quest: An African Journal of Philosophy'' (Kenya, depuis 1987)
===Ressources en ligne===
* Stanford Encyclopedia of Philosophy — Section African Philosophy : https://plato.stanford.edu/
* African Journals Online (AJOL) : https://www.ajol.info/
* PhilPapers — Philosophy in Africa and the African Diaspora : https://philpapers.org/browse/african-philosophy
* Internet Encyclopedia of Philosophy — African Philosophy : https://iep.utm.edu/
{{Autocat}}
aup8660ovaqrb056qba7l3txg3k4ghe
Manuel de terminale de philosophie/Raison
0
83103
768681
753502
2026-06-26T04:30:01Z
PandaMystique
119061
768681
wikitext
text/x-wiki
{{ManuelPhilo}}
La raison passe d'ordinaire pour ce qui élève l'être humain : la faculté de juger, de démontrer, de distinguer le vrai du faux. Mais cette même raison a servi à justifier des dominations, à traiter la nature comme un simple matériau et, parfois, à exclure ceux qu'elle déclarait déraisonnables. La notion porte ainsi un double visage : lumière qui éclaire, instrument qui maîtrise. Le travail de la philosophie consiste à tenir ensemble ces deux aspects, sans se contenter d'un éloge naïf ni d'une dénonciation facile.
D'où une question directrice pour ce chapitre : la raison nous permet-elle seulement de connaître la vérité, ou doit-elle aussi reconnaître ses propres limites ? Plusieurs interrogations s'y rattachent. La raison nous libère-t-elle de l'erreur, ou produit-elle aussi ses illusions ? Suffit-elle à guider l'action et à fonder la morale ? La critique des préjugés peut-elle, à son tour, devenir dogmatique ? Pour avancer, on distinguera dès maintenant deux registres souvent confondus : le « rationnel », qui relève de la logique et du calcul, et le « raisonnable », qui suppose la mesure, le sens des circonstances et le souci d'autrui. Une conduite peut être rationnelle, parfaitement cohérente dans ses moyens, sans être raisonnable dans ses fins.
Le mot « raison » vient du latin ''ratio'', qui désigne le calcul et le compte, puis la faculté de penser et de rendre compte<ref>Alain Rey (dir.), ''Dictionnaire historique de la langue française'', Paris, Le Robert, 1992, article « raison ».</ref>. Le grec disait ''logos'', terme plus large encore, qui réunit la parole, le discours, le rapport et la pensée. Cette parenté de sens oriente déjà l'enquête : raisonner, c'est aussi pouvoir dire pourquoi.
== Qu'est-ce que la raison ? ==
=== Le ''logos'' : raison, parole et argument ===
On présente souvent l'être humain comme un « animal doué de raison », traduction commode du grec ''zoon logon ekhon''. La formule est utile, mais elle risque d'écraser la richesse du mot ''logos'', qui signifie à la fois la raison, la parole, le discours et l'argument, donc la capacité de « rendre raison » de ce que l'on avance. Aristote ne réduit pas l'homme à une faculté de calcul. Dans la ''Politique'', il définit l'être humain comme un « animal politique » (''zoon politikon'') et lie cette nature au langage : seul l'homme possède la parole qui permet de manifester le juste et l'injuste, l'utile et le nuisible, là où les autres animaux n'ont que la voix pour exprimer la douleur ou le plaisir<ref>Aristote, ''Politique'', I, 2, 1253a, trad. Pierre Pellegrin, Paris, GF-Flammarion, 1990.</ref>. La raison, pour Aristote, n'est donc pas séparable de la vie en commun ni de la discussion sur les valeurs. On pourra consulter la [[Dictionnaire de philosophie/Aristote|notice consacrée à Aristote]].
Cette parenté entre raison et parole vient de loin. Héraclite, au {{-s|VI|e}}, nomme ''logos'' la loi commune qui ordonne le cosmos : une mesure cachée que le discours humain peut tenter de ressaisir<ref>Héraclite, fragments B1 et B50, dans Jean-Paul Dumont (dir.), ''Les Présocratiques'', Paris, Gallimard, « Bibliothèque de la Pléiade », 1988.</ref>. L'idée que le réel est ordonné selon des rapports intelligibles traverse toute l'histoire des sciences. Galilée, bien plus tard, soutiendra que le grand livre de la nature est écrit en langage mathématique : comprendre, ce serait retrouver l'ordre rationnel inscrit dans les choses.
=== Le bon sens et l'art de bien juger ===
Au {{s|XVII}}, René Descartes ouvre le ''Discours de la méthode'' par une remarque qui paraît d'abord ironique : « le bon sens est la chose du monde la mieux partagée »<ref>René Descartes, ''Discours de la méthode'', première partie, 1637, AT VI, p. 1-2.</ref>. Par « bon sens », il entend la raison, soit « la puissance de bien juger et distinguer le vrai d'avec le faux », qu'il dit « naturellement égale en tous les hommes ». Tous possèdent donc cette faculté, et nul n'en demande davantage.
Pourquoi, alors, tant de désaccords et d'erreurs ? Parce que posséder la raison ne suffit pas. « Ce n'est pas assez d'avoir l'esprit bon, écrit-il, mais le principal est de l'appliquer bien. » La diversité de nos opinions ne tient pas à une inégalité des esprits, mais à la diversité des chemins que nous empruntons. La raison se présente ainsi moins comme un trésor donné une fois pour toutes que comme une exigence : une faculté qu'il faut conduire avec méthode.
=== Raison ou calcul ? ===
Faut-il identifier la raison au calcul ? Thomas Hobbes le pensait : raisonner, écrit-il, n'est rien d'autre que calculer, additionner et soustraire des idées comme on opère sur des nombres<ref>Thomas Hobbes, ''Léviathan'', I, 5, 1651, trad. Gérard Mairet, Paris, Gallimard, « Folio », 2000.</ref>. Cette thèse a une longue postérité : elle annonce l'idée d'une pensée mécanisable, et donc l'informatique.
Mais calculer suffit-il à comprendre ? L'expérience de pensée dite de la « chambre chinoise », proposée par John Searle, invite à en douter. Imaginons une personne enfermée dans une pièce, qui reçoit des questions écrites en chinois et renvoie des réponses correctes en suivant un manuel de règles, sans comprendre un seul mot de chinois<ref>John Searle, « Minds, Brains, and Programs », ''Behavioral and Brain Sciences'', vol. 3, 1980, p. 417-424.</ref>. Elle manipule des signes d'après leur forme, non d'après leur sens. Searle en conclut qu'exécuter un calcul, aussi parfait soit-il, n'équivaut pas à saisir une signification. L'expérience vise un point simple : une machine, ou un système qui applique seulement des règles formelles, peut manipuler correctement des signes sans rien comprendre à leur sens. On peut ainsi distinguer « expliquer », rapporter un phénomène à ses causes ou à des règles, et « comprendre », saisir un sens de l'intérieur. La raison humaine ne se réduit pas au calcul : elle vise aussi le sens.
== Que peut connaître la raison ? ==
Une question organise l'âge classique : la raison peut-elle, par ses seules forces, atteindre des vérités sur le réel, ou doit-elle tout recevoir de l'expérience ? Deux familles de réponses s'opposent, avant que Kant ne tente de les concilier.
=== Le rationalisme : la primauté de la raison ===
Les philosophes que l'on dit rationalistes accordent à la raison le pouvoir de connaître des principes et des vérités nécessaires indépendamment de l'expérience sensible. Descartes en donne le modèle. Par le doute méthodique, il écarte toute croyance susceptible d'être trompeuse, jusqu'à une certitude qui résiste à tout : « je pense, donc je suis » (''cogito ergo sum'')<ref>René Descartes, ''Discours de la méthode'', quatrième partie, 1637, AT VI, p. 32 ; voir aussi les ''Méditations métaphysiques'', AT VII, p. 25.</ref>. Cette vérité première n'est pas tirée des sens : elle s'impose à la pensée qui s'examine elle-même. Descartes admet en outre des idées « innées », non au sens d'images présentes dès la naissance, mais de dispositions de l'esprit à former certains concepts, comme celui d'infini ou de perfection<ref>René Descartes, ''Méditations métaphysiques'', Troisième Méditation, 1641, AT VII, p. 37-52.</ref>.
Il serait toutefois inexact de réunir tous les rationalistes sous une même thèse. Descartes fonde la connaissance sur l'évidence du sujet pensant ; Spinoza construit, dans l{{'}}''Éthique'', un ordre déductif où tout découle de la nature de la substance ; Leibniz distingue les vérités de raison, nécessaires, et les vérités de fait, contingentes, et accorde à l'expérience un rôle que Descartes minorait. Aucun d'eux ne nie que nous fassions des expériences ; ce qu'ils soutiennent, c'est que la raison possède des principes qui ne se réduisent pas à elles. On peut introduire ici une distinction utile entre le « certain », ce qui s'impose sans doute possible, le « probable », ce qui est seulement vraisemblable, et le « vrai », ce qui est conforme à ce qui est : le rationalisme recherche avant tout des certitudes rationnelles. La notion de [[Dictionnaire de philosophie/Vérité|vérité]] est ici centrale.
=== L'empirisme : la primauté de l'expérience ===
À l'inverse, les philosophes empiristes soutiennent que toute connaissance vient de l'expérience. John Locke compare l'esprit, avant toute sensation, à une page blanche, une « table rase » sur laquelle l'expérience seule vient inscrire ses caractères<ref>John Locke, ''Essai sur l'entendement humain'', II, 1, 1689, trad. Pierre Coste, Paris, Vrin, 1989.</ref>. Il n'y aurait donc pas d'idées innées : tout ce que nous pensons dériverait, directement ou indirectement, de ce que nous avons perçu. On se reportera à la [[Dictionnaire de philosophie/Empirisme|notice sur l'empirisme]].
David Hume précise cette position d'une manière qui interdit de la caricaturer. Il distingue deux objets de connaissance. D'un côté, les « relations d'idées », comme les vérités des mathématiques : elles sont certaines et se démontrent par la seule raison, sans recourir à l'expérience, car leur négation serait contradictoire. De l'autre, les « faits », qui concernent l'existence et le cours du monde : ceux-là ne peuvent jamais être établis par la raison seule, mais reposent sur l'observation et sur l'habitude<ref>David Hume, ''Enquête sur l'entendement humain'', section IV, 1748, trad. Michelle Beyssade, Paris, GF-Flammarion, 1983.</ref>. La raison n'est donc pas impuissante chez Hume : elle règne sur les rapports entre idées. Mais elle ne saurait, à elle seule, nous apprendre ce qui existe. La question disputée se reformule alors avec plus de justesse : la raison peut-elle produire des connaissances portant sur le réel sans le secours de l'expérience ?
=== La synthèse kantienne : sensibilité, entendement, raison ===
Emmanuel Kant cherche à dépasser cette opposition. Sa formule est connue : « toute notre connaissance commence avec l'expérience », mais il ne s'ensuit pas « qu'elle dérive toute de l'expérience »<ref>Emmanuel Kant, ''Critique de la raison pure'', Introduction, B1, 1787, trad. André Tremesaygues et Bernard Pacaud, Paris, PUF, 1944.</ref>. Connaître, ce n'est pas recevoir passivement des impressions, ni filer des idées hors du monde : c'est mettre en forme une matière sensible à l'aide de structures que l'esprit apporte lui-même.
Encore faut-il distinguer soigneusement trois pouvoirs que le langage courant confond. La sensibilité reçoit les impressions et les ordonne au moyen de l'espace et du temps, qui ne sont pas des choses perçues mais les formes ''a priori'' de toute intuition. L'entendement pense ensuite ces données à l'aide de concepts purs, les « catégories » comme la cause ou la substance, et c'est lui qui connaît les phénomènes, c'est-à-dire les choses telles qu'elles nous apparaissent. La raison, enfin, prise au sens strict, ne connaît pas d'objets : elle cherche l'inconditionné, l'achèvement de la série des conditions, et forme à cette fin des idées comme l'âme, le monde pris dans sa totalité et Dieu<ref>Emmanuel Kant, ''Critique de la raison pure'', Esthétique transcendantale et Dialectique transcendantale, 1781-1787, trad. André Tremesaygues et Bernard Pacaud.</ref>.
Cette distinction commande une mise en garde. Parler des structures ''a priori'' comme de « formes innées » serait maladroit. L{{'}}''a priori'' kantien ne désigne pas une donnée psychologique déposée dans le cerveau à la naissance ; il désigne une condition de possibilité de l'expérience, ce sans quoi aucun objet ne pourrait être connu. La question n'est pas « depuis quand avons-nous ces formes ? » mais « que faut-il supposer pour que la connaissance soit possible ? ». Chez Kant, les formes de la sensibilité et les catégories de l'entendement rendent l'expérience connaissable ; la raison, elle, cherche l'unité du savoir, mais ne peut connaître aucun objet au-delà de l'expérience possible.
=== Les illusions de la raison ===
Que se passe-t-il lorsque la raison veut franchir les bornes de l'expérience pour atteindre l'inconditionné ? Elle s'égare dans des contradictions que Kant nomme les « antinomies » : on peut démontrer, avec une apparente rigueur, que le monde a un commencement dans le temps et qu'il n'en a pas, qu'il est limité dans l'espace et qu'il est infini<ref>Emmanuel Kant, ''Critique de la raison pure'', Dialectique transcendantale, « Les antinomies de la raison pure », 1781-1787, trad. André Tremesaygues et Bernard Pacaud.</ref>. Ces conflits ne tiennent pas à une faiblesse passagère : ils sont l'effet d'une tendance naturelle de la raison à tout vouloir achever. Reconnaître ces illusions, c'est se tenir à égale distance de deux excès : le dogmatisme, qui affirme sans preuve des thèses sur Dieu ou le monde, et le scepticisme, qui désespère de toute connaissance. La critique kantienne ouvre une troisième voie, celle d'un savoir qui mesure sa propre portée. On comparera avec la [[Dictionnaire de philosophie/Scepticisme|notice sur le scepticisme]].
<div style="clear:both; margin:0.6em 0 1em 0; padding:0.5em 0.9em; border:1px solid #aaa; background-color:#f8f8f8; font-size:95%;">
''À retenir.'' Chez Kant, la connaissance naît de la rencontre entre la sensibilité, qui reçoit les impressions, et l'entendement, qui les pense au moyen des catégories ; la raison, elle, cherche l'unité du savoir. Elle est puissante parce qu'elle vise cette unité ; elle est limitée parce qu'elle ne peut pas connaître ce qui dépasse l'expérience possible, sous peine de tomber dans les antinomies.
</div>
== La raison suffit-elle à guider l'action ? ==
=== Raison théorique et raison pratique ===
Jusqu'ici, il a été question de connaître. Mais la raison ne sert pas seulement à savoir ce qui est : elle prétend aussi orienter ce que nous devons faire. On distingue ainsi la « théorie », qui vise la connaissance du réel pour lui-même, et la « pratique », qui concerne l'action et ses fins. La raison théorique cherche le vrai, la raison pratique cherche le bien. Reste à savoir si la seconde existe vraiment, c'est-à-dire si la raison peut, à elle seule, commander l'action.
=== Kant : l'impératif catégorique ===
Kant répond par l'affirmative. La raison pratique découvre en nous une loi morale qui ne dépend ni de nos désirs ni des circonstances : l'impératif catégorique. Sa formule la plus simple est la suivante : « Agis seulement d'après la maxime grâce à laquelle tu peux vouloir en même temps qu'elle devienne une loi universelle »<ref>Emmanuel Kant, ''Fondements de la métaphysique des mœurs'', deuxième section, 1785, trad. Victor Delbos, Paris, Delagrave, 1907.</ref>. Le critère n'est pas le résultat de l'acte, mais la possibilité d'en universaliser la règle. Prenons l'exemple de la fausse promesse : si je promets de rembourser sans intention de le faire, je voudrais à la fois que ma promesse soit crue et que chacun puisse mentir ; or, si tout le monde agissait ainsi, la promesse n'aurait plus aucun sens. La maxime se détruit elle-même dès qu'on veut l'ériger en loi universelle. Kant donne de la même loi une autre formule, qui en éclaire l'enjeu humain : « Agis de telle sorte que tu traites l'humanité, aussi bien dans ta personne que dans celle de tout autre, toujours en même temps comme une fin, et jamais simplement comme un moyen »<ref>Emmanuel Kant, ''Fondements de la métaphysique des mœurs'', deuxième section, 1785, trad. Victor Delbos.</ref>. Respecter la raison présente en chacun, c'est ne jamais réduire une personne à un simple instrument au service de nos buts.
Cette obéissance à une loi que la raison se donne à elle-même porte un nom : l'autonomie, du grec ''autos'', soi-même, et ''nomos'', loi. Être libre, pour Kant, ce n'est pas suivre ses penchants, c'est se déterminer par une règle rationnelle valable pour tout être raisonnable. La raison pratique est ainsi le lieu où se rejoignent moralité et liberté. Voir les chapitres [[Manuel de terminale de philosophie/Liberté|Liberté]] et [[Manuel de terminale de philosophie/Devoir|Devoir]].
=== Hume : la raison « esclave des passions » ===
À cette confiance dans la raison pratique, Hume oppose une thèse abrupte : « la raison est, et ne doit qu'être, l'esclave des passions »<ref>David Hume, ''Traité de la nature humaine'', livre II, partie III, section 3, 1739-1740, trad. André Leroy, Paris, Aubier, 1946.</ref>. Il ne veut pas dire que la raison soit méprisable, mais qu'elle ne fournit jamais, à elle seule, le mobile d'une action. La raison calcule les moyens ; ce sont les désirs et les sentiments qui fixent les fins. De même, nos jugements moraux ne viennent pas d'une démonstration : approuver un acte généreux ou blâmer une cruauté, c'est d'abord éprouver un sentiment d'approbation ou de désapprobation<ref>David Hume, ''Traité de la nature humaine'', livre III, partie I, section 1, 1739-1740, trad. André Leroy.</ref>. La morale, pour Hume, repose sur une sensibilité partagée plus que sur la seule raison.
Deux conceptions de l'action se font donc face. Pour Kant, la raison commande la morale et fonde la liberté ; pour Hume, elle sert des passions qu'elle ne crée pas. Cette tension n'a pas à être tranchée trop vite : elle invite à se demander ce que peut, et ce que ne peut pas, la raison lorsqu'il s'agit d'agir.
=== Du rationnel au raisonnable ===
Ce débat éclaire la distinction posée au départ entre le « rationnel » et le « raisonnable ». Une décision peut être parfaitement rationnelle, cohérente dans le choix de ses moyens, et pourtant déraisonnable dans ses fins ou ses conséquences. Calculer avec efficacité la manière d'atteindre un but ne dit rien de la valeur de ce but. Une entreprise peut, de façon parfaitement rationnelle, réduire ses coûts en licenciant massivement ; la décision reste discutable si elle ruine des existences et un territoire. Un algorithme peut optimiser sans erreur un résultat, mais la question demeure : au service de quelles fins ? Être raisonnable, c'est précisément subordonner l'efficacité des moyens à un examen des fins : tenir compte d'autrui, des circonstances, de ce qui mérite d'être voulu. Cette différence prépare une question plus inquiétante : que devient la raison lorsqu'elle ne s'occupe plus que des moyens ?
<div style="clear:both; margin:0.6em 0 1em 0; padding:0.5em 0.9em; border:1px solid #aaa; background-color:#f8f8f8; font-size:95%;">
''À retenir.'' Pour Kant, la raison pratique commande la morale : elle énonce une loi universelle et fait de la liberté l'obéissance à la règle qu'on se donne soi-même. Pour Hume, la raison ne fixe pas les fins de l'action : elle sert des passions et des sentiments moraux qu'elle ne crée pas. Une même conduite peut être rationnelle dans ses moyens sans être raisonnable dans ses fins.
</div>
== La raison et ses autres ==
=== Pascal : le cœur et les premiers principes ===
On cite souvent Pascal pour opposer le sentiment à la raison : « le cœur a ses raisons que la raison ne connaît point »<ref>Blaise Pascal, ''Pensées'', fragment 277 (édition Brunschvicg), 423 (édition Lafuma), dans l'édition de Philippe Sellier, Paris, Bordas, 1991.</ref>. La formule est juste, mais on la réduit à tort au seul sentiment religieux. Chez Pascal, le « cœur » ne désigne pas d'abord l'émotion : il nomme la connaissance immédiate des premiers principes, ceux que la démonstration suppose sans pouvoir les établir. Nous savons qu'il y a l'espace, le temps, le mouvement, les nombres, et la raison s'appuie sur ces évidences premières qu'elle n'a pourtant pas démontrées<ref>Blaise Pascal, ''Pensées'', fragment 282 (édition Brunschvicg), 110 (édition Lafuma), éd. Philippe Sellier.</ref>. Le cœur n'est donc pas l'ennemi de la raison : il en est le fondement silencieux. La raison démontre à partir de principes qu'elle reçoit d'ailleurs.
Cette analyse aide à reconsidérer le rapport entre raison et émotions. On a longtemps tenu les émotions pour de simples troubles du jugement, qu'il faudrait maîtriser. Mais une émotion ajustée peut éclairer une situation, signaler ce qui compte, orienter l'attention : l'indignation devant une injustice flagrante, la crainte devant un danger réel ne troublent pas toujours le jugement, il leur arrive de le renseigner. Raison et affectivité ne s'opposent pas terme à terme : une délibération sans aucune sensibilité aux valeurs serait aveugle à ce qui mérite d'être pesé.
=== Foucault : la folie comme « autre » de la raison ===
La folie a longtemps été définie comme l'autre de la raison, son envers et sa limite. Michel Foucault refuse de prendre cette opposition pour un fait de nature. Dans l{{'}}''Histoire de la folie à l'âge classique'', il montre que le partage entre raison et déraison a une histoire : il s'est construit, à l'âge classique, par des pratiques d'exclusion, dont le « grand renfermement » qui, au {{s|XVII}}, place dans les mêmes lieux les pauvres, les oisifs et les insensés<ref>Michel Foucault, ''Histoire de la folie à l'âge classique'', Paris, Gallimard, 1972 (première édition 1961 sous le titre ''Folie et déraison'').</ref>. Ce n'est donc pas une raison immuable qui reconnaîtrait une folie immuable : c'est une certaine culture qui trace, à un moment donné, la frontière entre ce qu'elle admet et ce qu'elle rejette. Foucault n'invite pas à faire l'éloge de la folie, mais à interroger l'opposition elle-même et le pouvoir qui l'institue.
=== Adorno et Horkheimer : la raison instrumentale ===
Au {{s|XX}}, Max Horkheimer et Theodor W. Adorno adressent à la raison une critique qui ne vise pas la raison en général, mais une de ses formes : la raison « instrumentale ». Dans ''La Dialectique de la raison'', ils analysent une rationalité tout entière tournée vers la maîtrise, qui ne s'interroge plus sur les fins et ne retient des choses que ce qui peut être mesuré, prévu, exploité<ref>Max Horkheimer et Theodor W. Adorno, ''La Dialectique de la raison'', 1947, trad. Éliane Kaufholz, Paris, Gallimard, 1974.</ref>. Une telle raison transforme la nature, mais aussi les êtres humains, en objets à dominer. Le critère se réduit alors à la seule efficacité : une telle raison ne demande plus « cela est-il juste ? », mais seulement « cela fonctionne-t-il ? ». Le rêve d'une intelligence qui calculerait tout, illustré par le « démon » imaginé par Laplace, capable de prédire l'avenir s'il connaissait toutes les forces présentes, montre l'idéal et le risque de cette rationalité : un savoir de contrôle qui peut se retourner contre l'humanité qu'il prétendait servir. La critique vise donc moins la raison que son rétrécissement à la seule efficacité technique. On rapprochera ce point du chapitre [[Manuel de terminale de philosophie/Technique|Technique]].
<div style="clear:both; margin:0.6em 0 1em 0; padding:0.5em 0.9em; border:1px solid #aaa; background-color:#f8f8f8; font-size:95%;">
''À retenir.'' La raison a aussi ses « autres ». Le cœur, chez Pascal, lui fournit les premiers principes qu'elle ne démontre pas. La folie, selon Foucault, n'est pas un simple manque de raison, mais le produit d'un partage historique. La raison instrumentale, dénoncée par Adorno et Horkheimer, n'est pas la raison tout entière, mais sa réduction à la seule efficacité. Critiquer la raison, ce n'est donc pas y renoncer : c'est en surveiller les usages.
</div>
== La raison des Lumières et ses limites ==
=== « Ose savoir » ===
Le {{s|XVIII}} fait de la raison un instrument d'émancipation. Kant résume le programme des Lumières par une devise empruntée à Horace : ''sapere aude'', « ose savoir », aie le courage de te servir de ton propre entendement<ref>Emmanuel Kant, ''Qu'est-ce que les Lumières ?'', 1784, trad. Jean-François Poirier et Françoise Proust, Paris, GF-Flammarion, 1991.</ref>. Sortir de l'ignorance ne va pas de soi : cela demande de renoncer au confort des opinions reçues et de l'autorité qui dispense de penser. Voltaire mène ce combat contre les préjugés et les superstitions, qu'il tient pour des jugements adoptés sans examen<ref>Voltaire, ''Dictionnaire philosophique'', article « Préjugés », 1764.</ref>. La raison apparaît alors comme une force critique : elle n'accorde son crédit à une croyance qu'après l'avoir éprouvée. Voir les chapitres [[Manuel de terminale de philosophie/Vérité|Vérité]] et [[Manuel de terminale de philosophie/Science|Science]].
=== La critique peut-elle devenir dogmatique ? ===
Reste une difficulté que les Lumières elles-mêmes ont rencontrée. Une raison qui dénonce tous les préjugés peut, à son tour, ériger ses propres conclusions en certitudes indiscutables et mépriser ce qu'elle ne comprend pas. La critique des dogmes risque de devenir un nouveau dogmatisme. Pour l'éviter, il faut distinguer trois attitudes : le dogmatique affirme sans examiner ; le sceptique doute de tout, jusqu'à se priver de toute conclusion ; l'esprit critique, lui, examine les raisons, pèse les objections et conclut sans se croire à l'abri de l'erreur.
Cette vigilance passe aussi par la manière dont on défend une idée. On peut « persuader », emporter l'adhésion en jouant sur les sentiments, ou « convaincre », obtenir l'accord par des raisons que chacun peut examiner. De même, une « preuve » établit une vérité de façon contraignante, tandis qu'un « exemple » illustre sans démontrer : un cas particulier ne suffit pas à fonder une règle générale. Une raison fidèle à elle-même cherche à convaincre par des preuves, non à persuader par des effets.
== Conclusion ==
La raison est à la fois un pouvoir et une responsabilité. Elle participe à l'organisation de notre connaissance et nous aide à distinguer le vrai du faux, elle fonde des choix et arme la critique des préjugés ; mais elle rencontre ses limites lorsqu'elle prétend tout connaître, et elle se dégrade lorsqu'elle se réduit au calcul des moyens. Tout au long de ce parcours, une même exigence est revenue : passer du rationnel au raisonnable, c'est-à-dire mettre la puissance de la raison au service de fins que l'on a examinées. Une émotion ajustée, l'attention aux premiers principes, le souci d'autrui ne sont pas les ennemis de la raison : ils en accompagnent le bon usage.
C'est peut-être le sens le plus durable de la leçon cartésienne. Avoir l'esprit bon ne suffit pas : « le principal est de l'appliquer bien »<ref>René Descartes, ''Discours de la méthode'', première partie, 1637, AT VI, p. 2.</ref>. La raison ne vaut que par l'usage que nous en faisons. Exercée avec méthode, mesure et lucidité sur ses propres limites, elle reste l'instrument de notre liberté.
== Méthode ==
=== Repères à maîtriser ===
Le programme invite à se servir de quelques distinctions conceptuelles. Les principales, pour la notion de raison, sont les suivantes :
* « rationnel » et « raisonnable » : la cohérence logique des moyens ne garantit pas la justesse des fins.
* « raison » et « calcul » : calculer, c'est opérer sur des signes ; raisonner suppose aussi de comprendre un sens.
* « expliquer » et « comprendre » : rapporter à des causes ou à des lois n'est pas saisir une signification de l'intérieur.
* « théorie » et « pratique » : connaître ce qui est, ou orienter ce qui doit être fait.
* « vrai », « probable » et « certain » : ce qui est conforme au réel, ce qui est seulement vraisemblable, ce qui s'impose sans doute possible.
* « convaincre » et « persuader » : obtenir l'accord par des raisons, ou emporter l'adhésion par les sentiments.
* « preuve » et « exemple » : établir de façon contraignante, ou illustrer sans démontrer.
* « objectif », « subjectif » et « intersubjectif » : ce qui vaut indépendamment de chacun, ce qui dépend d'un sujet, ce qui peut être partagé et reconnu par plusieurs.
Deux de ces repères méritent un développement, car ils sont parmi les plus difficiles. « Expliquer » et « comprendre » désignent deux manières de connaître. On explique un phénomène quand on le rattache à ses causes ou à des lois générales : la chute d'un corps s'explique par la gravitation. On comprend une conduite, un geste ou un texte quand on en saisit le sens de l'intérieur, en se rapportant aux intentions et aux raisons d'agir. Les sciences de la nature expliquent ; les sciences humaines, sans renoncer à expliquer, cherchent aussi à comprendre.
« Objectif », « subjectif » et « intersubjectif » distinguent trois rapports à la vérité. Est objectif ce qui vaut indépendamment des préférences de chacun, comme le résultat d'une mesure. Est subjectif ce qui dépend du sujet particulier, comme un goût ou une impression personnelle. Est intersubjectif ce qui peut être partagé, discuté et reconnu par plusieurs : une démonstration vaut parce que tout esprit rationnel peut la refaire et l'admettre. La raison vise l'objectif, mais elle l'établit toujours par l'intersubjectif, c'est-à-dire par un accord que d'autres peuvent vérifier.
=== Sujets de dissertation ===
Trois sujets permettent de mettre ces analyses à l'épreuve.
# La raison nous libère-t-elle de nos illusions ? On pourra examiner si la raison dissipe l'erreur, mais aussi si elle engendre ses propres illusions, comme le suggèrent les antinomies kantiennes.
# Suffit-il d'être rationnel pour être raisonnable ? Le sujet engage la distinction entre la cohérence des moyens et la valeur des fins, ainsi que la critique de la raison instrumentale.
# La raison peut-elle fonder la morale ? On confrontera la position de Kant, pour qui la raison commande la loi morale, et celle de Hume, pour qui la morale repose sur le sentiment.
Voir la [[Manuel de terminale de philosophie/Dissertation|méthode de la dissertation]] et le chapitre [[Manuel de terminale de philosophie/Morale|Morale]].
=== Texte à expliquer ===
Le texte suivant ouvre le ''Discours de la méthode''. On l'expliquera en repérant la thèse soutenue, les étapes de l'argument et la fonction de l'exemple.
{{citation bloc|Le bon sens est la chose du monde la mieux partagée ; car chacun pense en être si bien pourvu, que ceux même qui sont les plus difficiles à contenter en toute autre chose n'ont point coutume d'en désirer plus qu'ils en ont. En quoi il n'est pas vraisemblable que tous se trompent : mais plutôt cela témoigne que la puissance de bien juger et distinguer le vrai d'avec le faux, qui est proprement ce qu'on nomme le bon sens ou la raison, est naturellement égale en tous les hommes ; et ainsi que la diversité de nos opinions ne vient pas de ce que les uns sont plus raisonnables que les autres, mais seulement de ce que nous conduisons nos pensées par diverses voies, et ne considérons pas les mêmes choses.}}
Source : René Descartes, ''Discours de la méthode'', première partie, 1637 ; texte intégral sur [[s:Discours de la méthode (éd. Cousin)/Première partie|Wikisource]].
Questions :
# Quelle thèse Descartes soutient-il sur la répartition du bon sens entre les hommes ?
# Comment définit-il le « bon sens » ? En quoi cette définition l'identifie-t-elle à la raison ?
# Si la raison est également partagée, comment l'auteur explique-t-il la diversité de nos opinions ?
# Quelle conséquence cette thèse a-t-elle pour la suite : que faut-il, selon Descartes, pour bien user de sa raison ?
== Notes et références ==
{{references|colonnes=2}}
== Pour aller plus loin ==
=== Textes essentiels ===
* Aristote, ''Politique'' (livre I, chapitre 2), ''Organon'' et ''Éthique à Nicomaque'', {{-s|IV|e}}.
* René Descartes, ''Discours de la méthode'', 1637.
* René Descartes, ''Méditations métaphysiques'', 1641.
* Blaise Pascal, ''Pensées'', 1670 (édition posthume).
* John Locke, ''Essai sur l'entendement humain'', 1689.
* David Hume, ''Enquête sur l'entendement humain'', 1748.
* Emmanuel Kant, ''Critique de la raison pure'', 1781-1787 (à lire par extraits).
* Emmanuel Kant, ''Fondements de la métaphysique des mœurs'', 1785.
* Max Horkheimer et Theodor W. Adorno, ''La Dialectique de la raison'', 1947 (à lire par extraits).
=== Lectures accessibles ===
* Bertrand Russell, ''Problèmes de philosophie'', trad. française, Paris, Payot.
* Alain, ''Éléments de philosophie'', Paris, Gallimard.
* Maxime Kristanek (dir.), ''L'Encyclopédie philosophique'' en ligne, notices « Raison » et « Logique ».
=== Études ===
* Jean-Louis Gardies, ''Qu'est-ce que et pourquoi la logique ?'', Paris, Vrin, 1991.
* Alain Renaut, ''Kant aujourd'hui'', Paris, Aubier, 1997.
* Jacques Bouveresse, ''Rationalité et cynisme'', Paris, Minuit, 1984.
* Jean-Marie Chevalier, « Raisonnement », dans Maxime Kristanek (dir.), ''L'Encyclopédie philosophique'', 2016.
[[Catégorie:Manuel de terminale de philosophie (livre)]]
{{Autocat}}
804w23wxn60nkiicn5o7bp5zlyg5ldp
Dictionnaire de philosophie/Empirisme
0
83122
768680
768456
2026-06-26T04:29:05Z
PandaMystique
119061
768680
wikitext
text/x-wiki
{{DicoPhilo|Empirisme|lecture=oui}}
L''''empirisme''' désigne un ensemble de doctrines philosophiques qui font de l’expérience sensible l’origine de toute connaissance ou, au minimum, de toute connaissance portant sur le monde. Cette position épistémologique soutient que nos idées, nos concepts et nos savoirs dérivent de ce que nous percevons par nos sens et ne peuvent, en dernière analyse, être justifiés qu’à l’aune de l’expérience. L’empirisme s’oppose ainsi à l’innéisme et au rationalisme, pour lesquels il existe des connaissances ou des principes indépendants de toute expérience sensible.
== Définition et enjeux ==
=== Les trois thèses de l’empirisme ===
On peut distinguer, de manière analytique, trois dimensions dans la doctrine empiriste, qui ne sont pas toujours soutenues ensemble ni avec la même intensité<ref>{{Ouvrage|auteur=Ayers, Michael|titre=Locke: Epistemology and Ontology|lieu=London|éditeur=Routledge|année=1993|pages=52-78}}</ref>.
'''La thèse psychologique''' porte sur le mode d’acquisition de nos pensées et de nos contenus mentaux. L’empirisme psychologique affirme que les éléments constitutifs de toutes nos pensées – croyances, images, souvenirs, raisonnements – sont, directement ou indirectement, acquis par l’intermédiaire de l’expérience sensible. Sans contact avec le réel par nos sens (sens externes et sens interne), l’esprit ne pourrait former aucune idée. La métaphore classique, chez Locke, de l’''esprit-table rase'' exprime cette conviction : il n’y a pas de contenus mentaux innés, mais seulement des dispositions générales à recevoir et à travailler des matériaux fournis par l’expérience.
'''La thèse épistémologique''' concerne la justification de nos croyances et de nos connaissances. L’empirisme épistémologique soutient que c’est l’expérience qui nous permet, en dernière instance, de juger si une croyance est vraie ou fausse, bien fondée ou arbitraire. La connaissance n’est assurée et garantie qu’autant que nos croyances se laissent éprouver par la confrontation à l’expérience sensible, entendue au sens large (perception, observation, expérimentation). On peut être empiriste de manière plus ou moins radicale : certains admettent que l’expérience est une source de connaissance parmi d’autres (aux côtés de la raison, par exemple) ; d’autres soutiennent, plus strictement, que toute connaissance authentique est nécessairement empirique et ''a posteriori''<ref>{{Ouvrage|auteur=Locke, John|titre=Essai philosophique concernant l’entendement humain|lieu=Paris|éditeur=Vrin|année=2001|annéeorigine=1690|pages=Livre II, chap. I, § 2}}</ref>.
'''La thèse sémantique''' porte enfin sur la signification des mots et des expressions linguistiques. Selon l’empirisme sémantique, ce qui confère un sens déterminé aux termes que nous utilisons est, au moins en partie, le lien qu’ils entretiennent avec les données de l’expérience sensible. Un mot qui ne renvoie à aucune expérience possible – actuelle ou possible pour un sujet humain – est dépourvu de contenu cognitif : il ne désigne rien, et relève du pseudo-concept, voire du pur « flatus vocis ». Hume formule ainsi un principe méthodologique : lorsque nous rencontrons un terme ou une proposition, nous devons nous demander de quelle impression sensible dérive l’idée correspondante ; si aucune impression ne peut être indiquée, le terme est suspect de vacuité<ref>{{Ouvrage|auteur=Hume, David|titre=Enquête sur l’entendement humain|lieu=Paris|éditeur=Flammarion|collection=GF|année=1983|annéeorigine=1748|pages=Section II}}</ref>.
Ces trois thèses – psychologique, épistémologique et sémantique – peuvent être dissociées. Il est possible, par exemple, d’admettre que tous nos concepts dérivent de l’expérience (thèse psychologique) tout en soutenant qu’il existe des vérités nécessaires indépendantes de l’expérience (refus de la thèse épistémologique radicale), ou encore de refuser qu’un terme ait un sens s’il ne renvoie à aucune expérience possible (thèse sémantique) sans pour autant nier l’existence de connaissances ''a priori''. L’« empirisme » désigne en pratique une famille de positions articulant ces thèses à des degrés divers.
=== L’opposition empirisme–rationalisme ===
L’histoire de la philosophie moderne a souvent présenté l’empirisme et le rationalisme comme deux écoles antagonistes, particulièrement aux XVIIe et XVIIIe siècles. Cette dichotomie, bien qu’un peu schématique, permet de situer certains enjeux fondamentaux de l’épistémologie moderne.
Pour les rationalistes comme René Descartes, Baruch Spinoza ou Gottfried Wilhelm Leibniz, la raison humaine est la source principale, voire exclusive, de la connaissance véritable. Certaines vérités universelles et nécessaires – mathématiques, métaphysiques, morales – peuvent être atteintes par la seule raison, indépendamment des incertitudes et des illusions de l’expérience sensible. Ces philosophes accordent une importance centrale aux idées innées, présentes dans l’esprit dès la naissance, et aux vérités ''a priori'', c’est-à-dire indépendantes de l’expérience. Descartes, par exemple, soutient que les idées de Dieu, d’infini ou de perfection ne peuvent être acquises par l’expérience du monde corporel, mais sont « nées avec nous » en tant que marques de leur auteur divin<ref>{{Ouvrage|auteur=Descartes, René|titre=Méditations métaphysiques|lieu=Paris|éditeur=Garnier-Flammarion|année=1979|annéeorigine=1641|pages=Méditation III}}</ref>.
L’empirisme britannique, avec John Locke, George Berkeley et David Hume, s’oppose à cette conception. Ces auteurs soutiennent que l’expérience sensible, interne (réflexion sur les opérations de l’esprit) ou externe (perception des objets), est la seule source de nos connaissances factuelles. Toutes nos idées, même les plus générales et les plus abstraites, dérivent ultimement de ce que nous percevons. Il n’existe pas d’idées innées ; l’esprit à la naissance est comparable à une table rase (''tabula rasa'') qui se remplit progressivement par l’expérience<ref>{{Ouvrage|auteur=Locke, John|titre=Essai philosophique concernant l’entendement humain|éditeur=Vrin|lieu=Paris|année=2001|annéeorigine=1690|pages=Livre II, chap. I, § 2}}</ref>.
Les historiens de la philosophie ont cependant nuancé cette opposition tranchée. D’une part, les empiristes ne nient pas que la raison joue un rôle dans le processus de connaissance : ils refusent seulement la possibilité de connaissances ''purement'' rationnelles portant sur des faits concernant le monde, indépendamment de toute expérience. D’autre part, les rationalistes ne rejettent pas entièrement l’importance de l’expérience, notamment dans les sciences de la nature. La dichotomie empirisme/rationalisme fonctionne donc davantage comme un schéma classificatoire rétrospectif que comme la description exacte de deux camps homogènes. Kant lui-même, dans la ''Critique de la raison pure'', exploite cette opposition pour mettre en scène la « querelle » entre empiristes et rationalistes, avant de proposer sa propre solution transcendantale<ref>{{Ouvrage|auteur=Kant, Emmanuel|titre=Critique de la raison pure|lieu=Paris|éditeur=PUF|collection=Quadrige|année=2012|annéeorigine=1781|pages=Préface à la seconde édition}}</ref>.
== Les origines antiques et médiévales ==
Même si l’empirisme au sens moderne se constitue principalement aux XVIIe et XVIIIe siècles, ses prémisses remontent à l’Antiquité. Le terme grec d’''empeirikos'' (« empirique ») désignait, dans la médecine antique, une école qui s’opposait aux médecins « dogmatiques ». Les médecins empiriques refusaient de spéculer sur des causes cachées ou sur des essences inobservables ; ils privilégiaient l’observation directe des symptômes, la comparaison de cas et l’accumulation d’expériences cliniques. La guérison ne devait pas être expliquée par des théories abstraites, mais guidée par l’art de tirer parti d’expériences analogues antérieures.
Sur le plan philosophique, certains aspects de l’empirisme se retrouvent dans le scepticisme, notamment chez Sextus Empiricus, dont le nom signale d’ailleurs la proximité avec la tradition médicale empirique. Les sceptiques pyrrhoniens accordent une grande importance à l’observation des phénomènes, tout en suspendant le jugement sur ce qui serait la réalité ultime ou la cause véritable. Ils défendent ainsi une forme d’« empirisme sans dogmatisme » : il faut se conformer aux apparences et à l’expérience vécue, mais renoncer à affirmer des thèses métaphysiques sur ce qui se trouve derrière elles.
Au Moyen Âge, certains auteurs, sans être empiristes au sens strict, accordent à l’expérience un rôle méthodologique privilégié. Roger Bacon (vers 1214–1294) insiste sur la nécessité de l’expérimentation pour confirmer les raisonnements et dépasser les limites de la spéculation pure. Guillaume d’Ockham (vers 1285–1347), avec son principe de parcimonie (le « rasoir d’Ockham »), recommande de ne pas multiplier les entités sans nécessité et de s’en tenir autant que possible aux données de l’expérience pour construire nos ontologies<ref>{{Ouvrage|auteur=Guillaume d’Ockham|titre=Somme de logique|lieu=Mauvezin|éditeur=Trans-Europ-Repress|année=1988|annéeorigine=vers 1323|pages=Première partie}}</ref>. Cette exigence d’économie théorique anticipe certains réflexes empiristes modernes : ne pas supposer, au-delà de l’expérience, des entités qui n’apportent aucune différence observable.
== L’empirisme moderne : Francis Bacon et la méthode expérimentale ==
=== Francis Bacon : l’induction et le ''Novum Organum'' ===
Francis Bacon (1561–1626) occupe une place singulière dans l’histoire de l’empirisme. Il n’est pas empiriste au sens psychologique strict (il ne développe pas une théorie détaillée de l’origine des idées), mais il est l’un des premiers à formuler de manière systématique une méthode scientifique fondée sur l’observation, l’expérimentation et une induction méthodiquement contrôlée. Son ouvrage majeur, le ''Novum Organum'' (1620), se présente comme un nouvel instrument de connaissance, destiné à remplacer l’''Organon'' aristotélicien.
Bacon critique vigoureusement deux attitudes intellectuelles qu’il juge stériles. D’un côté, les « empiriques » au sens péjoratif, comparés à des fourmis, se contentent d’amasser des observations hétéroclites sans en dégager des principes. De l’autre, les « dogmatiques » ou « rationnels », assimilés à des araignées, tissent à partir d’eux-mêmes des systèmes abstraits peu soucieux de l’expérience. Le bon chercheur doit être comme l’abeille : il recueille la matière première dans la nature et la transforme par l’activité de son entendement en une connaissance véritablement nouvelle<ref>{{Ouvrage|auteur=Bacon, Francis|titre=Novum Organum|lieu=Paris|éditeur=PUF|année=1986|annéeorigine=1620|pages=Livre I, aphorisme 95}}</ref>.
La méthode baconienne repose sur une forme d’induction que Bacon cherche à rendre rigoureuse. Il propose de dresser des « tables » : table de présence (énumération des cas où le phénomène est présent), table d’absence (cas semblables où le phénomène est absent), table des degrés (cas où le phénomène varie d’intensité). En croisant ces séries de cas, le savant peut éliminer progressivement les fausses causes et isoler les conditions réellement pertinentes, ce que Bacon appelle la « forme » du phénomène étudié<ref>{{Ouvrage|auteur=Bacon, Francis|titre=Novum Organum|éditeur=PUF|lieu=Paris|année=1986|annéeorigine=1620|pages=Livre II, aphorismes 11-20}}</ref>. L’induction n’est plus une simple généralisation hâtive, mais une procédure méthodique d’exclusion.
Bacon souligne également la nécessité de purifier l’esprit des « idoles » qui le trompent : idoles de la tribu (erreurs propres à la nature humaine en général), de la caverne (préjugés individuels), du marché (confusions liées au langage), du théâtre (illusions engendrées par les systèmes philosophiques et théologiques traditionnels)<ref>{{Ouvrage|auteur=Bacon, Francis|titre=Novum Organum|éditeur=PUF|lieu=Paris|année=1986|annéeorigine=1620|pages=Livre I, aphorismes 38-68}}</ref>. Cette critique des illusions cognitives sera reprise, sous des formes différentes, par de nombreux empiristes ultérieurs.
== L’empirisme britannique classique ==
=== John Locke : la table rase et l’origine des idées ===
John Locke (1632–1704) est généralement considéré comme le fondateur de l’empirisme moderne au sens strict. Son ''Essai philosophique concernant l’entendement humain'' (1690) propose une théorie systématique de la connaissance fondée sur l’expérience et un rejet argumenté de la doctrine des idées innées.
Le premier livre de l’''Essai'' est consacré à la réfutation de l’innéisme. Locke examine l’argument selon lequel certaines vérités seraient universellement reconnues (par exemple, « tout ce qui est, est ») et que cette universalité d’assentiment prouverait leur caractère inné. Il objecte que les enfants en bas âge et les personnes atteintes de déficiences mentales ne manifestent aucun assentiment à ces prétendus principes universels. Si l’on rétorque que ces principes sont présents dans l’esprit mais non encore perçus explicitement, Locke répond qu’un principe qui ne devient effectif qu’à la faveur de l’expérience et de l’exercice de la pensée ne mérite pas d’être appelé inné : il est alors, en réalité, acquis<ref>{{Ouvrage|auteur=Locke, John|titre=Essai philosophique concernant l’entendement humain|éditeur=Vrin|lieu=Paris|année=2001|annéeorigine=1690|pages=Livre I, chap. II}}</ref>.
La thèse positive de Locke apparaît au début du second livre : « Je réponds d’un seul mot : de l’expérience ; en elle toute notre connaissance est fondée. » L’esprit à la naissance est comparable à une « chambre obscure » ou à une « feuille de papier blanche » qui se remplit progressivement par deux sources : la ''sensation'', qui nous donne les idées des objets extérieurs par nos cinq sens, et la ''réflexion'', qui est la perception des opérations de notre propre esprit (penser, douter, vouloir, raisonner, etc.)<ref>{{Ouvrage|auteur=Locke, John|titre=Essai philosophique concernant l’entendement humain|éditeur=Vrin|lieu=Paris|année=2001|annéeorigine=1690|pages=Livre II, chap. I, § 2-4}}</ref>.
Locke distingue les ''idées simples'', reçues passivement par la sensation ou la réflexion, et les ''idées complexes'', que l’entendement forme activement en combinant, comparant et abstrayant les idées simples. L’empirisme lockien n’est donc pas un simple sensualisme passif : si tout vient de l’expérience, l’esprit n’en est pas moins une puissance de synthèse, de comparaison et de généralisation<ref>{{Ouvrage|auteur=Locke, John|titre=Essai philosophique concernant l’entendement humain|éditeur=Vrin|lieu=Paris|année=2001|annéeorigine=1690|pages=Livre II, chap. XII}}</ref>.
Une autre distinction célèbre chez Locke oppose les ''qualités premières'' (ou primaires) aux ''qualités secondes''. Les premières – solidité, étendue, figure, mouvement ou repos, nombre – appartiennent réellement aux corps et sont inséparables de leur existence. Les secondes – couleurs, sons, saveurs, odeurs – ne sont que des puissances qu’ont les objets de produire en nous certaines sensations. Ainsi, la « rougeur » d’une tomate n’est pas une propriété qui serait dans la tomate au même sens que son étendue ; elle résulte de l’interaction entre la structure corpusculaire de l’objet et notre système perceptif<ref>{{Ouvrage|auteur=Locke, John|titre=Essai philosophique concernant l’entendement humain|éditeur=Vrin|lieu=Paris|année=2001|annéeorigine=1690|pages=Livre II, chap. VIII, § 9-26}}</ref>.
Cette distinction ouvre une difficulté pour l’empirisme : si nous n’avons un accès direct qu’à nos idées, et non aux choses elles-mêmes, comment justifier l’affirmation selon laquelle les qualités premières « ressemblent » aux objets alors que les qualités secondes n’en sont que des effets subjectifs ? Locke maintient une forme de réalisme représentatif, mais la tension entre empirisme psychologique et réalisme métaphysique sera au cœur des critiques adressées à sa philosophie.
=== George Berkeley : l’idéalisme immatérialiste ===
George Berkeley (1685–1753), évêque anglican et philosophe, radicalise l’empirisme lockien en tirant de ses prémisses des conséquences inattendues. Dans les ''Principes de la connaissance humaine'' (1710) et les ''Trois dialogues entre Hylas et Philonous'' (1713), il développe un idéalisme immatérialiste : la « matière » entendue comme substance étendue existant indépendamment de tout esprit n’existe pas ; seuls existent des esprits et leurs idées.
Le principe fondamental de Berkeley s’exprime dans la formule : ''esse est percipi aut percipere'' – « être, c’est être perçu ou percevoir ». Ce que nous appelons « choses matérielles » n’est rien d’autre qu’un ensemble stable et ordonné de perceptions : couleurs, formes, sons, résistances tactiles. Affirmer qu’un objet existe indépendamment de toute perception revient à le penser tout en prétendant qu’il peut exister sans être pensé : c’est, selon Berkeley, une contradiction performative<ref>{{Ouvrage|auteur=Berkeley, George|titre=Principes de la connaissance humaine|lieu=Paris|éditeur=Flammarion|collection=GF|année=1991|annéeorigine=1710|pages=§ 3-7}}</ref>.
Berkeley critique la distinction lockienne entre qualités premières et secondes. Si l’on admet que les qualités secondes n’existent que dans l’esprit du percevant, pourquoi les qualités premières feraient-elles exception ? Nous ne percevons jamais une « étendue » ou un « mouvement » en soi, mais toujours une étendue colorée, un mouvement visible, une figure ressentie. Toutes nos perceptions ont la même nature mentale ; il n’y a aucune raison de réserver à certaines un statut objectif privilégié<ref>{{Ouvrage|auteur=Berkeley, George|titre=Principes de la connaissance humaine|lieu=Paris|éditeur=Flammarion|collection=GF|année=1991|annéeorigine=1710|pages=§ 9-15}}</ref>.
On pourrait croire que cette position conduit au solipsisme : si les choses n’existent que comme perçues, cessent-elles d’exister lorsque personne ne les regarde ? Berkeley répond que les objets continuent d’exister parce qu’ils sont constamment perçus par Dieu, esprit infini. Le monde sensible est un système d’idées que Dieu produit de manière régulière dans les esprits finis. L’immatérialisme berkeleyien n’est donc pas un scepticisme, mais une métaphysique théiste : la constance de l’expérience est garantie par la fidélité de Dieu<ref>{{Ouvrage|auteur=Berkeley, George|titre=Trois dialogues entre Hylas et Philonous|lieu=Paris|éditeur=Flammarion|collection=GF|année=1998|annéeorigine=1713|pages=Troisième dialogue}}</ref>.
L’empirisme de Berkeley se prolonge par une réflexion sur le langage et les idées générales. Il récuse la théorie lockienne des idées abstraites : nous ne pouvons pas nous représenter un « triangle en général » qui ne serait ni équilatéral, ni isocèle, ni scalène. Les mots généraux ne renvoient pas à des idées abstraites, mais à des idées particulières qui fonctionnent comme signes pour une multiplicité d’objets. La généralité appartient au langage, non aux représentations mentales<ref>{{Ouvrage|auteur=Berkeley, George|titre=Principes de la connaissance humaine|lieu=Paris|éditeur=Flammarion|collection=GF|année=1991|annéeorigine=1710|pages=Introduction, § 6-20}}</ref>.
=== David Hume : l’empirisme sceptique ===
David Hume (1711–1776) porte l’empirisme à un point de radicalité qui frôle le scepticisme. Son ''Traité de la nature humaine'' (1739–1740) et l’''Enquête sur l’entendement humain'' (1748) constituent l’aboutissement de l’empirisme britannique et mettent en lumière des difficultés profondes pour toute prétention à une connaissance nécessaire des faits.
Hume commence par distinguer deux types de perceptions : les ''impressions'', qui sont les sensations, passions et émotions telles qu’elles se présentent avec vivacité à l’esprit, et les ''idées'', qui sont des copies affaiblies de ces impressions dans la pensée. Toutes les idées simples dérivent d’impressions correspondantes ; nous ne pouvons pas former l’idée d’une couleur que nous n’avons jamais vue ni celle d’une saveur que nous n’avons jamais goûtée. C’est le « principe de copie », qui fonde l’empirisme humien sur un critère précis de légitimité des idées<ref>{{Ouvrage|auteur=Hume, David|titre=Traité de la nature humaine|lieu=Paris|éditeur=Flammarion|collection=GF|année=1995|annéeorigine=1739|pages=Livre I, partie I, section I}}</ref>.
Hume distingue ensuite deux sortes d’objets de la connaissance : les ''relations d’idées'' (vérités logiques et mathématiques), qui sont nécessaires mais purement analytiques, et les ''questions de fait'', qui concernent le monde et ne peuvent être établies que par l’expérience ''a posteriori''<ref>{{Ouvrage|auteur=Hume, David|titre=Enquête sur l’entendement humain|lieu=Paris|éditeur=Flammarion|collection=GF|année=1983|annéeorigine=1748|pages=Section IV, partie I}}</ref>.
Sa contribution la plus célèbre est la critique de la causalité et du raisonnement inductif. Nous croyons spontanément que certains événements ''causent'' nécessairement d’autres (le feu cause la chaleur, le choc d’une bille entraîne le mouvement de l’autre). Or, selon Hume, nous ne percevons jamais cette prétendue « connexion nécessaire ». Nous observons seulement qu’un type de phénomène est constamment suivi d’un autre ; de cette conjonction constante naît dans notre esprit une ''habitude'' qui nous porte à attendre la répétition de cette succession. La nécessité causale n’est pas une relation objective entre les choses, mais un produit de notre esprit accoutumé<ref>{{Ouvrage|auteur=Hume, David|titre=Enquête sur l’entendement humain|lieu=Paris|éditeur=Flammarion|collection=GF|année=1983|annéeorigine=1748|pages=Section VII}}</ref>.
D’où le problème de l’induction : comment justifier rationnellement le passage du passé au futur, du particulier au général ? Nous supposons que « le futur ressemblera au passé », mais ce principe ne peut être démontré ni par la raison (il n’est pas analytique) ni par l’expérience (il faudrait déjà l’utiliser pour fonder la valeur de l’induction). Hume conclut que nos croyances causales et nos anticipations ne reposent sur aucun fondement rationnel nécessaire, mais sur une propension naturelle de l’esprit. La raison n’en est pas moins condamnée à les avaliser, car nous ne pouvons vivre sans projeter l’avenir à partir du passé<ref>{{Ouvrage|auteur=Hume, David|titre=Enquête sur l’entendement humain|lieu=Paris|éditeur=Flammarion|collection=GF|année=1983|annéeorigine=1748|pages=Section IV, partie II}}</ref>.
L’empirisme de Hume conduit encore à remettre en question l’idée d’un moi substantiel : lorsque nous nous livrons à l’introspection, nous ne rencontrons jamais un « moi » simple et identique, mais seulement un faisceau de perceptions changeantes. De même, les grands thèmes de la métaphysique traditionnelle (substance, âme, Dieu) excèdent les limites de ce qui peut être rattaché à une impression ; ils relèvent, selon Hume, de la fiction ou de la projection<ref>{{Ouvrage|auteur=Hume, David|titre=Traité de la nature humaine|lieu=Paris|éditeur=Flammarion|collection=GF|année=1995|annéeorigine=1739|pages=Livre I, partie IV, section VI}}</ref>.
== Le sensualisme continental : Condillac ==
=== Étienne Bonnot de Condillac : la statue sensible ===
Étienne Bonnot de Condillac (1715–1780) développe en France une variante originale de l’empirisme, souvent qualifiée de ''sensualisme''. Il soutient que toutes nos facultés mentales, y compris les plus élaborées, dérivent de la simple sensation. Son ''Traité des sensations'' (1754) propose une expérience de pensée célèbre : l’allégorie de la statue.
Condillac invite à imaginer une statue organisée intérieurement comme un être humain, mais privée de toute idée. On réveille en elle, l’un après l’autre, ses sens, en commençant par l’odorat. Lorsque la statue sent pour la première fois une odeur de rose, elle n’est rien d’autre que cette odeur ; elle ne distingue pas encore un sujet qui sentirait un objet senti. Par la répétition et la comparaison des sensations, surgissent progressivement l’attention, la mémoire, le désir, puis le jugement, la réflexion et même la volonté. Toutes ces facultés, que Locke traitait comme distinctes de la sensation, ne sont pour Condillac que de la « sensation transformée »<ref>{{Ouvrage|auteur=Condillac, Étienne Bonnot de|titre=Traité des sensations|lieu=Paris|éditeur=Fayard|année=1984|annéeorigine=1754|pages=Première partie, chap. I-II}}</ref>.
Condillac accorde une importance décisive au toucher, seul sens qui nous donne une véritable conscience de l’extériorité et de notre propre corps. Les autres sens nous livrent des « tableaux » sensibles sans que nous puissions distinguer ce qui est en nous et ce qui est hors de nous. C’est par la résistance éprouvée dans le toucher que nous apprenons à opposer notre corps à les corps extérieurs, et à constituer le monde comme un ensemble d’objets<ref>{{Ouvrage|auteur=Condillac, Étienne Bonnot de|titre=Traité des sensations|lieu=Paris|éditeur=Fayard|année=1984|annéeorigine=1754|pages=Troisième partie}}</ref>.
Le sensualisme condillacien a profondément marqué la pensée française du XVIIIe siècle, la pédagogie des Lumières et certains développements matérialistes. Il radicalise l’intuition empiriste en cherchant à dériver non seulement les contenus de la pensée, mais les opérations mêmes de l’entendement, de la seule vie sensible.
== La synthèse kantienne ==
=== Kant : dépasser l’opposition entre empirisme et rationalisme ===
Emmanuel Kant (1724–1804), dans la ''Critique de la raison pure'' (1781), entreprend de dépasser l’opposition entre empirisme et rationalisme par une révolution qu’il qualifie lui-même de « copernicienne ». Il reconnaît à l’empirisme d’avoir insisté sur le rôle irremplaçable de l’expérience comme point de départ de la connaissance, et au rationalisme d’avoir mis en lumière l’existence de structures ''a priori''. Mais il refuse à l’un comme à l’autre leurs prétentions exclusives.
Kant affirme d’emblée que « toute notre connaissance commence avec l’expérience », en ce sens qu’il ne se trouve rien dans l’entendement qui n’ait été d’abord donné sous forme d’intuition sensible. Mais il ajoute aussitôt qu’il ne s’ensuit pas que toute connaissance dérive de l’expérience : il y a des formes et des concepts ''a priori'' par lesquels l’esprit structure ce qui lui est donné<ref>{{Ouvrage|auteur=Kant, Emmanuel|titre=Critique de la raison pure|lieu=Paris|éditeur=PUF|collection=Quadrige|année=2012|annéeorigine=1781|pages=Introduction, B 1}}</ref>.
Kant distingue deux sources de la connaissance : la ''sensibilité'', par laquelle les objets nous sont donnés, et l’''entendement'', par lequel ils sont pensés. La sensibilité possède deux formes ''a priori'' : l’espace et le temps. Ce ne sont pas des propriétés des choses en soi, mais des formes de notre intuition : nous ne pouvons faire l’expérience d’objets qu’en les situant dans l’espace et dans le temps<ref>{{Ouvrage|auteur=Kant, Emmanuel|titre=Critique de la raison pure|lieu=Paris|éditeur=PUF|collection=Quadrige|année=2012|annéeorigine=1781|pages=Esthétique transcendantale, §§ 1-8}}</ref>.
L’entendement, de son côté, dispose de catégories ''a priori'' (substance, causalité, nécessité, etc.) qui unifient et structurent les données sensibles. La causalité, par exemple, n’est pas, comme chez Hume, une simple habitude psychologique ; elle est une condition de possibilité de l’expérience d’un monde d’objets soumis à des lois. Nous ne pourrions même pas reconnaître une succession objective d’événements si nous n’appliquions pas la catégorie de cause à effet<ref>{{Ouvrage|auteur=Kant, Emmanuel|titre=Critique de la raison pure|lieu=Paris|éditeur=PUF|collection=Quadrige|année=2012|annéeorigine=1781|pages=Analogie de l’expérience, seconde analogie}}</ref>.
Ainsi, contre l’empirisme strict, Kant soutient qu’il existe des jugements ''synthétiques a priori'' (en mathématiques et en physique théorique) qui, tout en étant nécessairement vrais, portent sur l’expérience possible et non sur de simples relations conceptuelles. Contre le rationalisme dogmatique, il limite néanmoins la validité de ces structures ''a priori'' au domaine des phénomènes : elles n’autorisent aucune connaissance de l’âme, du monde comme totalité ou de Dieu en tant que réalités en soi<ref>{{Ouvrage|auteur=Kant, Emmanuel|titre=Critique de la raison pure|lieu=Paris|éditeur=PUF|collection=Quadrige|année=2012|annéeorigine=1781|pages=Dialectique transcendantale}}</ref>. La synthèse kantienne reconnaît ainsi à l’empirisme la dépendance matérielle de la connaissance vis-à-vis de l’expérience, tout en réhabilitant des formes rationnelles ''a priori'' irréductibles.
== L’empirisme contemporain ==
=== Le positivisme logique et l’empirisme logique ===
Au XXe siècle, l’empirisme se renouvelle avec le Cercle de Vienne et le courant que l’on appelle ''positivisme logique'' ou ''empirisme logique''. Moritz Schlick, Rudolf Carnap, Otto Neurath et d’autres cherchent à articuler l’héritage empiriste avec la logique mathématique moderne (Frege, Russell) et certaines idées du ''Tractatus'' de Wittgenstein.
Leur projet est de fonder une « conception scientifique du monde » en excluant la métaphysique comme discours dépourvu de sens. Selon le ''principe de vérification'', tel qu’ils le formulent, le sens d’un énoncé est lié à la méthode de sa vérification : un énoncé n’a de signification cognitive que s’il est soit analytique (vrai en vertu de sa forme logique), soit vérifiable empiriquement, c’est-à-dire testable en principe par l’observation ou l’expérience. Les propositions métaphysiques, qui ne sont ni analytiques ni vérifiables, doivent être rejetées comme des pseudo-énoncés<ref>{{Article|auteur=Carnap, Rudolf|titre=Le dépassement de la métaphysique par l’analyse logique du langage|périodique=Manifeste du Cercle de Vienne et autres écrits|éditeur=PUF|lieu=Paris|année=2010|annéeorigine=1931|pages=153-179}}</ref>.
Les empiristes logiques défendent une conception unitaire de la science : tous les énoncés scientifiques, qu’ils portent sur la physique, la biologie ou la psychologie, devraient pouvoir être traduits, directement ou indirectement, en énoncés concernant des données d’expérience. Dans ''La construction logique du monde'' (1928), Carnap tente de reconstruire l’ensemble des concepts scientifiques à partir d’un langage phénoménal de base<ref>{{Ouvrage|auteur=Carnap, Rudolf|titre=Der logische Aufbau der Welt|lieu=Hamburg|éditeur=Felix Meiner Verlag|année=1998|annéeorigine=1928}}</ref>.
Ce programme suscite cependant des objections. Le principe de vérification lui-même n’est ni analytique ni vérifiable : selon son propre critère, il serait donc dépourvu de sens. De plus, la plupart des énoncés scientifiques, notamment les lois générales, ne sont pas strictement vérifiables, car aucune expérience finie ne peut en épuiser le domaine d’application. Ces difficultés conduisent à des affaiblissements successifs du principe (vérifiabilité ''en principe'', puis simple confirmabilité) et à des débats internes au mouvement<ref>{{Article|auteur=Schlick, Moritz|titre=Signification et vérification|périodique=Manifeste du Cercle de Vienne et autres écrits|éditeur=PUF|lieu=Paris|année=2010|annéeorigine=1932|pages=337-370}}</ref>.
=== Karl Popper et le falsificationnisme ===
Karl Popper (1902–1994) propose une alternative à la vérification empiriste. Dans ''La logique de la découverte scientifique'' (1934), il soutient que le critère de démarcation entre science et non-science n’est pas la vérifiabilité, mais la ''réfutabilité'' (ou falsifiabilité). Une théorie est scientifique si elle expose, en principe, à la possibilité d’être réfutée par l’expérience, c’est-à-dire si elle exclut certains états de choses observables<ref>{{Ouvrage|auteur=Popper, Karl|titre=La logique de la découverte scientifique|lieu=Paris|éditeur=Payot|année=1973|annéeorigine=1934|pages=chap. 1}}</ref>.
Popper accepte la critique humienne de l’induction : aucune accumulation d’observations ne peut vérifier définitivement une loi universelle. En revanche, une seule observation contraire suffit, en droit, à la réfuter. La science progresse par « conjectures et réfutations » : des hypothèses audacieuses sont proposées, soumises à des tests sévères, et écartées lorsqu’elles échouent. Les théories qui résistent le mieux à la critique sont ''corroborées'', mais jamais définitivement prouvées<ref>{{Ouvrage|auteur=Popper, Karl|titre=Conjectures et réfutations|lieu=Paris|éditeur=Payot|année=1985|annéeorigine=1963|pages=chap. 1}}</ref>.
Cette conception rencontre à son tour des objections. En pratique, on ne teste jamais une hypothèse isolée, mais un ensemble d’énoncés théoriques et d’hypothèses auxiliaires (ce que Duhem et Quine ont mis en lumière). Face à un résultat expérimental récalcitrant, il est toujours possible de modifier une hypothèse auxiliaire plutôt que la théorie centrale. L’histoire des sciences montre aussi que des théories fécondes ont été maintenues malgré des anomalies persistantes<ref>{{Ouvrage|auteur=Lakatos, Imre|titre=Histoire et méthodologie des sciences|lieu=Paris|éditeur=PUF|année=1994|annéeorigine=1978|pages=chap. 1}}</ref>.
=== Willard Van Orman Quine : holisme et empirisme naturalisé ===
Willard Van Orman Quine (1908–2000) transforme en profondeur l’héritage empiriste. Dans « Les deux dogmes de l’empirisme » (1951), il remet en cause deux piliers du positivisme logique : la distinction nette entre énoncés analytiques (vrais en vertu de la signification) et énoncés synthétiques (vrais en vertu des faits), et le réductionnisme selon lequel chaque énoncé pourrait être testé isolément par l’expérience<ref>{{Ouvrage|auteur=Quine, Willard Van Orman|titre=Du point de vue logique|lieu=Paris|éditeur=Vrin|année=2003|annéeorigine=1953|pages=« Les deux dogmes de l’empirisme », p. 49-81}}</ref>.
Quine défend un ''holisme épistémologique'' : nos croyances ne font pas face à l’expérience une à une, mais comme un système global. Le « tribunal de l’expérience » juge l’ensemble de notre théorie du monde, et nous avons, en principe, une grande liberté pour décider où porter les révisions en cas de conflit. On peut choisir de sacrifier un énoncé particulier, une hypothèse auxiliaire, ou même des principes logiques de base, si l’on juge ce remaniement moins coûteux pour la cohérence globale du système<ref>{{Ouvrage|auteur=Quine, Willard Van Orman|titre=Du point de vue logique|lieu=Paris|éditeur=Vrin|année=2003|annéeorigine=1953|pages=p. 67-73}}</ref>.
Quine propose en outre une ''épistémologie naturalisée''. Plutôt que de chercher à fonder les sciences sur des certitudes indubitables, l’épistémologie doit être intégrée au corpus de la science elle-même, et notamment à la psychologie. Elle étudie alors comment un organisme humain, en interaction avec un environnement physique, en vient à élaborer un réseau de croyances de plus en plus sophistiqué. L’empirisme devient ainsi une thèse méthodologique interne au projet scientifique, et non plus une doctrine fondationnelle située au-dessus de la science<ref>{{Ouvrage|auteur=Quine, Willard Van Orman|titre=Relativité de l’ontologie et autres essais|lieu=Paris|éditeur=Aubier|année=2008|annéeorigine=1969|pages=« L’épistémologie devenue naturelle », p. 83-105}}</ref>.
== Critiques et limites de l’empirisme ==
L’empirisme, sous ses différentes formes, a suscité de nombreuses critiques, portant à la fois sur la fiabilité de l’expérience, sur le statut des vérités nécessaires et sur la conception de l’observation.
=== Les limites de l’expérience sensible ===
Une première famille d’objections met en cause la fiabilité des sens. Les expériences d’illusion, d’hallucination, de mirage, montrent que la perception peut nous tromper. L’empiriste répond qu’il faut corriger les perceptions particulières par d’autres perceptions, croiser les témoignages sensoriels, multiplier les observations. Mais cela suppose déjà que l’on dispose de critères pour distinguer les expériences plus fiables des expériences trompeuses, et ces critères ne sont pas eux-mêmes fournis immédiatement par les sens.
De plus, de nombreux objets des sciences contemporaines (particules élémentaires, champs quantiques, structures cosmologiques lointaines) ne sont pas directement observables. L’empiriste doit alors admettre que la connaissance ne porte pas seulement sur ce qui est immédiatement donné, mais sur des entités théoriques postulé(es) pour expliquer des régularités observables. On parle alors d’« observabilité indirecte » : nous ne voyons pas les électrons, mais les traces qu’ils laissent dans des chambres à bulles ou les effets qu’ils produisent dans des dispositifs expérimentaux. L’empirisme strict, qui voulait fonder la signification sur la seule donnée sensible, se trouve ainsi mis à l’épreuve.
=== Le problème des vérités nécessaires ===
Une difficulté classique pour l’empirisme concerne le statut des vérités nécessaires et universelles, notamment en mathématiques et en logique. Comment expliquer que « 2 + 2 = 4 » ou que « nul n’est à la fois carré et circulaire » soient valables indépendamment de toute expérience, si toute connaissance découle de l’expérience, toujours contingente et particulière ?
Hume, on l’a vu, répond en distinguant nettement les ''relations d’idées'' et les ''questions de fait'' : les premières sont connues indépendamment de l’expérience et ne nous apprennent rien sur le monde ; les secondes dépendent de l’expérience et ne sont jamais nécessaires. Une telle solution revient à reconnaître l’existence d’un domaine de connaissance ''a priori'', mais en le cantonnant aux vérités purement analytiques. D’autres, comme John Stuart Mill, ont tenté de soutenir que même les vérités arithmétiques seraient le résultat d’une induction à partir de manipulations d’objets, ce qui ferait des mathématiques une forme de généralisation empirique ; cette thèse est cependant jugée aujourd’hui peu convaincante.
=== Le problème de l’induction ===
Le problème de l’induction, formulé avec une clarté exemplaire par Hume, frappe au cœur de l’empirisme. Si tout notre savoir des lois naturelles repose sur des inférences inductives, et si ces inférences ne peuvent être justifiées ni par la logique (elles ne sont pas nécessaires) ni par l’expérience (toute tentative de justification empirique serait circulaire), alors la prétention de l’empirisme à fonder solidement la connaissance scientifique semble compromise. Des réponses comme le falsificationnisme de Popper ou le holisme de Quine cherchent moins à « résoudre » le problème qu’à l’intégrer dans une conception plus modeste et plus pragmatique de la rationalité scientifique.
=== La charge théorique de l’observation ===
Les travaux de Norwood Russell Hanson, Thomas Kuhn et Paul Feyerabend ont mis en lumière le caractère ''chargé de théorie'' de l’observation. Nous n’observons pas des « faits bruts » qui viendraient ensuite être interprétés ; nous voyons toujours déjà le réel à travers des catégories, des schèmes, des attentes, des instruments conceptuels. Un même phénomène – par exemple la trajectoire d’un corps dans un champ magnétique – n’est pas « vu » de la même manière par un physicien newtonien et un physicien relativiste<ref>{{Ouvrage|auteur=Kuhn, Thomas|titre=La structure des révolutions scientifiques|lieu=Paris|éditeur=Flammarion|collection=Champs|année=2008|annéeorigine=1962|pages=chap. X}}</ref>.
Cette thèse affaiblit l’idée empiriste d’une base observationnelle neutre, qui servirait de tribunal ultime pour arbitrer entre théories. Elle montre que le rapport entre théorie et expérience est circulaire et réciproque : les théories orientent l’observation autant qu’elles en résultent. Certains en ont tiré des conclusions relativistes ; d’autres, plus modérés, en font un argument pour concevoir l’empirisme non comme une doctrine de fondation, mais comme une exigence méthodologique de confrontation permanente entre théories et phénomènes.
== Prolongements et héritages contemporains ==
L’empirisme, loin d’être une doctrine figée, s’est diversifié au XXe et au XXIe siècles. Sous le nom de ''naturalisme'', de ''pragmatisme'' ou d’''empirisme pragmatique'', on retrouve l’intuition fondamentale selon laquelle notre connaissance du monde, pour être légitime, doit s’enraciner dans l’expérience, tout en reconnaissant la complexité des médiations conceptuelles et sociales qui structurent cette expérience.
Dans les sciences cognitives, les débats entre innéisme et empirisme se poursuivent sous des formes renouvelées. Les travaux de Noam Chomsky sur une « grammaire universelle » innée ont ravivé l’idée de structures mentales préformées, tandis que d’autres approches insistent sur la plasticité des réseaux neuronaux et sur la capacité d’apprendre des régularités complexes à partir de données sensorielles massives.
En philosophie de l’esprit et du langage, de nombreuses théories du contenu mental et de la référence gardent une inspiration empiriste, en faisant dépendre la signification des mots et des états mentaux de nos interactions causales avec l’environnement. Parallèlement, des courants comme le contextualisme, l’encyclopédisme sémantique ou les théories des concepts enracinés dans l’action revisitent la thèse empiriste selon laquelle il n’y a pas de sens sans lien, au moins indirect, avec l’expérience possible.
L’empirisme demeure ainsi, sous des formes souvent transformées, l’une des grandes orientations de la pensée philosophique. Il rappelle constamment une exigence critique : ne pas se satisfaire de constructions spéculatives détachées de toute expérience, mais exiger que nos théories, nos concepts et nos croyances puissent, d’une manière ou d’une autre, être confrontés à ce que le monde nous donne effectivement à vivre et à percevoir.
== Notes et références ==
{{references}}
==Bibliographie==
===Textes classiques===
* Bacon, Francis, ''Novum Organum'', Paris, PUF, 1986 [1620]
* Berkeley, George, ''Principes de la connaissance humaine'', Paris, Flammarion, 1991 [1710]
* Berkeley, George, ''Trois dialogues entre Hylas et Philonous'', Paris, Flammarion, 1998 [1713]
* Condillac, Étienne Bonnot de, ''Traité des sensations'', Paris, Fayard, 1984 [1754]
* Hume, David, ''Traité de la nature humaine'', 3 volumes, Paris, Flammarion, 1995 [1739-1740]
* Hume, David, ''Enquête sur l'entendement humain'', Paris, Flammarion, 1983 [1748]
* Locke, John, ''Essai philosophique concernant l'entendement humain'', édition abrégée, Paris, Vrin, 2001 [1690]
===Études critiques===
* Ayers, Michael, ''Locke: Epistemology and Ontology'', 2 volumes, London, Routledge, 1993
* BonJour, Laurence, ''In Defense of Pure Reason'', Cambridge, Cambridge University Press, 1998
* Chappell, Vere (dir.), ''The Cambridge Companion to Locke'', Cambridge, Cambridge University Press, 1994
* Deleuze, Gilles, ''Empirisme et subjectivité. Essai sur la nature humaine selon Hume'', Paris, PUF, 1953
* Garrett, Don, ''Cognition and Commitment in Hume's Philosophy'', Oxford, Oxford University Press, 1997
* Stroud, Barry, ''Hume'', London, Routledge & Kegan Paul, 1977
* Winkler, Kenneth, ''Berkeley: An Interpretation'', Oxford, Clarendon Press, 1989
===Sur l'empirisme contemporain===
* Carnap, Rudolf, ''La construction logique du monde'', Paris, Vrin, 2002 [1928]
* Popper, Karl, ''La logique de la découverte scientifique'', Paris, Payot, 1973 [1934]
* Quine, Willard Van Orman, ''Du point de vue logique'', Paris, Vrin, 2003 [1953]
* Quine, Willard Van Orman, ''Le mot et la chose'', Paris, Flammarion, 1977 [1960]
==Articles connexes==
* {{Page|Connaissance}}
* {{Page|Épistémologie}}
* {{Page|Expérience}}
* {{Page|Idées innées}}
* {{Page|Induction}}
* {{Page|Rationalisme}}
* {{Page|Scepticisme}}
* {{Page|Sensation}}
{{AutoCat}}
kqwrq5phu0dfg0bzmqmcm874xyvgurt
Dictionnaire de philosophie/Philosophie suédoise contemporaine
0
83134
768693
767517
2026-06-26T04:32:51Z
PandaMystique
119061
Bot : ajout du paramètre lecture=oui au modèle {{DicoPhilo}}
768693
wikitext
text/x-wiki
{{DicoPhilo|Suède|lecture=oui}}
La philosophie pratiquée en Suède occupe, depuis le début des années 2000, une position bien identifiable sur la scène internationale. Quelques indices concrets en témoignent : une revue de stature mondiale, ''Theoria'' ; des programmes de recherche financés sur dix ans, comme le Projet de Responsabilité Lund-Göteborg ; des chaires confiées à des chercheurs recrutés à l’étranger ; l’élection d’une philosophe, Åsa Wikforss, à l’Académie suédoise. À la différence de bien des foyers de la philosophie continentale européenne, cette philosophie s’inscrit très largement dans le courant analytique, héritage d’une transformation intellectuelle opérée au cours du XX{{e}} siècle.<ref name="nordin1984">Svante Nordin, ''Från Hägerström till Hedenius. Den moderna svenska filosofin'' [De Hägerström à Hedenius. La philosophie suédoise moderne], Bodafors, Doxa, 1984.</ref><ref name="strang2010">Johan Strang, ''History, Transfer, Politics. Five Studies on the Legacy of Uppsala Philosophy'', Helsinki, Université d’Helsinki, 2010 ; {{lien web|url=https://helda.helsinki.fi/|titre=Texte intégral|site=Helda, University of Helsinki|consulté le=6 juin 2026}}.</ref> Cette orientation analytique n’exclut pas une diversité notable des domaines de recherche, qui vont de la métaphysique et de l’épistémologie à l’éthique appliquée, en passant par la philosophie politique et les questions soulevées par les technologies émergentes.
Les universités d’Uppsala, de Stockholm, de Göteborg et de Lund constituent les principaux foyers de la recherche philosophique suédoise. Ces institutions abritent des départements de philosophie organisés selon une distinction nette entre philosophie théorique (englobant l’épistémologie, la métaphysique, la philosophie du langage, la philosophie de l’esprit, la logique et la philosophie des sciences) et philosophie pratique (comprenant l’éthique normative, la métaéthique, la philosophie politique et l’éthique appliquée).
Le présent article retrace d’abord, à grands traits, l’histoire longue de la philosophie en Suède, avant de se concentrer sur la période ouverte au tournant des années 2000. Il inclut dans son périmètre les chercheuses et chercheurs d’origine étrangère durablement installés dans les universités suédoises, ainsi que les figures suédoises exerçant à l’étranger : c’est l’ensemble de ce paysage qui fait l’identité philosophique du pays.
== Aperçu historique : de Swedenborg à la percée analytique ==
La Suède n’entre pas dans l’histoire de la philosophie avec le XX{{e}} siècle. Au XVIII{{e}} siècle, Emanuel Swedenborg (1688-1772), d’abord savant et ingénieur des mines, élabore après sa crise religieuse une vaste théosophie visionnaire qui connut un retentissement européen : c’est contre lui que Kant rédigea les ''Rêves d’un visionnaire'' (''Träume eines Geistersehers'', 1766), où s’esquisse déjà sa critique de la métaphysique dogmatique.<ref>Emmanuel Kant, ''Rêves d’un visionnaire expliqués par des rêves métaphysiques'' (1766), trad. Francis Courtès, Paris, Vrin, 1967.</ref>
Au XIX{{e}} siècle, l’université suédoise vit sous l’autorité de Christopher Jacob Boström (1797-1866), professeur à Uppsala, dont l’idéalisme rationnel fournit pendant des décennies la doctrine quasi officielle de la philosophie académique et la matrice intellectuelle des élites administratives du royaume. C’est contre ce boströmianisme que se constitue, au début du XX{{e}} siècle, l’École d’Uppsala, examinée dans la section suivante.
La percée analytique s’opère ensuite en deux temps. La génération d’Ingemar Hedenius (1908-1982), de Konrad Marc-Wogau (1902-1991) et d’Anders Wedberg (1913-1978) acclimate en Suède les méthodes du positivisme logique et de l’analyse conceptuelle : Hedenius porte le débat philosophique sur la place publique avec ''Tro och vetande'' (''Croyance et savoir'', 1949), critique retentissante de la théologie chrétienne ; Marc-Wogau renouvelle les études kantiennes et dirige ''Theoria'' de 1957 à 1964 ; Wedberg, depuis sa chaire de Stockholm, écrit une histoire de la philosophie d’inspiration analytique qui forme des générations d’étudiants.<ref name="nordin1984" /> Une seconde vague, à partir des années 1970, voit émerger des figures de stature internationale comme Peter Gärdenfors (né en 1949), dont les recherches sur la dynamique des croyances puis sur les espaces conceptuels relient la philosophie suédoise aux sciences cognitives.<ref>Peter Gärdenfors, ''Conceptual Spaces. The Geometry of Thought'', Cambridge (Mass.), MIT Press, 2000.</ref>
== L’héritage de l’École d’Uppsala et l’ancrage analytique ==
Pour comprendre la philosophie suédoise actuelle, il convient de remonter à l’École d’Uppsala, fondée par Axel Hägerström (1868-1939) avec son collègue et ancien élève Adolf Phalén (1884-1931), qui rompirent avec l’idéalisme boströmien pour développer une approche critique fondée sur l’analyse conceptuelle ; Phalén consacra pour sa part l’essentiel de son œuvre à la critique du subjectivisme en théorie de la connaissance.<ref name="strang2010" /> Hägerström occupa la chaire de philosophie pratique à Uppsala de 1911 à 1933. Son rejet de la métaphysique se résume dans la devise latine qu’il s’était donnée, ''praeterea censeo metaphysicam esse delendam'' (« au reste, j’estime que la métaphysique doit être détruite »), formule calquée sur celle de Caton l’Ancien à propos de Carthage.<ref>Axel Hägerström, « Selbstdarstellung », in Hans Schwarz (dir.), ''Die Philosophie der Gegenwart in Selbstdarstellungen'', t. VII, Leipzig, Felix Meiner, 1929.</ref> Il développa en outre une théorie connue sous le nom de nihilisme axiologique (en suédois ''värdenihilism''), qui suscita de vives controverses : selon lui, les jugements moraux ne sont ni vrais ni faux, mais expriment des attitudes émotionnelles plutôt qu’ils ne décrivent des faits objectifs.
L’influence de Hägerström sur la philosophie suédoise moderne demeure étendue, même si ses positions ont été largement discutées, nuancées et révisées. Une génération de philosophes plus jeunes, dans les années 1930 et 1940, parmi lesquels Ingemar Hedenius, Konrad Marc-Wogau et Anders Wedberg, s’employa à concilier l’héritage de Hägerström avec les courants émergents du positivisme logique et de la philosophie analytique.<ref name="strang2010" /> Cette période de transition contribua à ancrer durablement la philosophie suédoise dans la tradition analytique, avec une attention particulière portée à l’analyse du langage, à la logique et aux méthodes rigoureuses d’argumentation.
== Les grandes figures du paysage philosophique suédois ==
Les portraits qui suivent décrivent moins une école nationale qu’un paysage : plusieurs de ses acteurs sont nés ou ont été formés hors de Suède, tandis que certains philosophes nés en Suède exercent à l’étranger. La catégorie pertinente est donc celle de la philosophie pratiquée en Suède, complétée par les figures suédoises de la diaspora académique.
=== Torbjörn Tännsjö et l’utilitarisme hédoniste ===
Parmi les philosophes suédois qui ont marqué les vingt-cinq dernières années, Torbjörn Tännsjö occupe une place singulière. Titulaire à partir de 2002 de la chaire de philosophie pratique de l’Université de Stockholm, dont il est aujourd’hui professeur émérite, il s’est imposé comme l’un des rares philosophes suédois régulièrement présents dans le débat public.
Tännsjö défend un utilitarisme hédoniste d’une grande cohérence interne, selon lequel le plaisir et la douleur sont les seuls porteurs de valeur intrinsèque (positive pour le plaisir, négative pour la douleur) et selon lequel nos actions doivent viser à maximiser le bonheur total.<ref>Torbjörn Tännsjö, ''Hedonistic Utilitarianism'', Édimbourg, Edinburgh University Press, 1998.</ref> Cette position l’a conduit à soutenir des conclusions morales qui heurtent souvent le sens commun : l’avortement, l’euthanasie ou certaines pratiques de sélection génétique se trouvent, dans de nombreux cas, justifiés par les principes utilitaristes. Loin de s’en dédire, Tännsjö assume pleinement ces implications contre-intuitives et s’efforce de montrer que c’est notre morale ordinaire qui devrait être révisée plutôt que les principes utilitaristes eux-mêmes.
Au-delà de l’éthique normative, Tännsjö a également contribué à la philosophie politique, en défendant une conception dite populiste de la démocratie, où les décisions seraient prises directement par référendum plutôt que par des représentants élus.<ref>Torbjörn Tännsjö, ''Populist Democracy. A Defence'', Londres, Routledge, 1992.</ref> Il a plaidé par la suite pour l’établissement d’un gouvernement mondial comme réponse aux problèmes globaux que les États-nations se montrent incapables de résoudre par eux-mêmes.<ref>Torbjörn Tännsjö, ''Global Democracy. The Case for a World Government'', Édimbourg, Edinburgh University Press, 2008.</ref>
=== Sven Ove Hansson et la philosophie de la technologie ===
Sven Ove Hansson illustre une autre dimension importante de la philosophie suédoise contemporaine. Professeur à l’Institut royal de technologie (KTH) de Stockholm depuis 2000, où il a aujourd’hui le statut de professeur senior, il s’est spécialisé dans la philosophie de la technologie et des risques.<ref name="kthsoh">{{lien web|url=https://www.kth.se/profile/soh|titre=Sven Ove Hansson, profil institutionnel|site=KTH Royal Institute of Technology|consulté le=6 juin 2026}}.</ref> Ses travaux portent sur l’évaluation des risques environnementaux, la théorie de la décision et la révision des croyances.
Hansson est emblématique de l’interdisciplinarité croissante de la philosophie suédoise. Titulaire d’un premier diplôme en sciences médicales obtenu en 1972, il a ensuite soutenu deux doctorats en philosophie : l’un en philosophie théorique à Uppsala en 1991, l’autre en philosophie pratique à Lund en 1999.<ref name="kthsoh" /> Cette double formation lui permet d’aborder les questions philosophiques avec une sensibilité aiguë aux problèmes concrets posés par le développement technologique et scientifique.
Les contributions de Hansson à la théorie de la révision des croyances, notamment son analyse des postulats dits AGM (d’après les initiales de Carlos Alchourrón, Peter Gärdenfors et David Makinson), lui ont valu une reconnaissance internationale.<ref>Sven Ove Hansson, ''A Textbook of Belief Dynamics. Theory Change and Database Updating'', Dordrecht, Kluwer Academic Publishers, 1999.</ref> Ce champ de recherche possède d’ailleurs un ancrage suédois ancien, puisque Peter Gärdenfors, professeur à Lund, compte parmi les architectes de ce formalisme.<ref>Peter Gärdenfors, ''Knowledge in Flux. Modeling the Dynamics of Epistemic States'', Cambridge (Mass.), MIT Press, 1988.</ref> Les travaux de Hansson, abondamment cités dans la littérature philosophique, lui ont également valu la rédaction de l’article de référence sur la logique de la révision des croyances dans la ''Stanford Encyclopedia of Philosophy'' et la direction de collections internationales d’ouvrages de logique et de philosophie de la technologie. Rédacteur en chef de ''Theoria'' depuis 1999, il partage aujourd’hui cette fonction avec Henrik Lundvall. La revue, principale publication internationale de philosophie éditée en Suède, constitue un vecteur de premier ordre de la philosophie analytique sur la scène internationale.<ref name="theoriakth">{{lien web|url=https://www.kth.se/philhist/phil/nyheter/theoria-1.1464699|titre=Theoria|site=Division of Philosophy, KTH Royal Institute of Technology, page mise à jour le 19 mars 2026|consulté le=6 juin 2026}} ; Sven Ove Hansson, « A history of Theoria », ''Theoria'', vol. 76, n° 4, 2010.</ref>
=== Åsa Wikforss et l’épistémologie sociale ===
Åsa Wikforss représente une génération de philosophes suédois ayant acquis une visibilité notable, tant dans l’univers académique que dans l’espace public. Professeure de philosophie théorique à l’Université de Stockholm depuis 2008, elle compte parmi les premières femmes à avoir accédé à une chaire de philosophie théorique en Suède.
Les recherches de Wikforss se situent à l’intersection de la philosophie du langage, de la philosophie de l’esprit et de l’épistémologie. Elle a travaillé notamment sur la normativité du langage, en remettant en question l’idée reçue selon laquelle le langage serait essentiellement une activité guidée par des règles.<ref>Åsa Wikforss, « Semantic Normativity », ''Philosophical Studies'', vol. 102, n° 2, 2001, p. 203-226.</ref> Ses travaux ultérieurs portent sur la nature de la croyance et sur la connaissance que nous avons de nos propres pensées.
Ce qui distingue particulièrement Wikforss dans le paysage philosophique suédois récent, c’est son engagement dans les débats publics relatifs à la résistance à la connaissance et à la désinformation. En 2017, elle a publié un ouvrage en suédois intitulé ''Alternativa fakta. Om kunskapen och dess fiender'' (''Faits alternatifs. Sur la connaissance et ses ennemis''), qui a rencontré un large succès et a renforcé sa position de figure de la vie intellectuelle suédoise.
En 2019, elle a été élue à l’Académie suédoise, l’institution qui décerne le prix Nobel de littérature et dont la mission première concerne la langue suédoise. Elle occupe ainsi l’un des dix-huit fauteuils de cette académie.
=== Ingmar Persson et le perfectionnement moral ===
Ingmar Persson, professeur de philosophie pratique à l’Université de Göteborg, représente une autre orientation caractéristique de la philosophie morale du pays. Ses recherches couvrent la philosophie de l’esprit et de l’action, la théorie éthique et l’éthique appliquée. Son principal ouvrage, ''The Retreat of Reason. A Dilemma in the Philosophy of Life'' (2005), explore les attitudes rationnelles à adopter face au temps, à l’identité personnelle et à la responsabilité.<ref>Ingmar Persson, ''The Retreat of Reason. A Dilemma in the Philosophy of Life'', Oxford, Clarendon Press, 2005.</ref>
Avec son collègue Julian Savulescu, il a développé l’idée controversée selon laquelle l’humanité pourrait avoir besoin d’un « perfectionnement moral » pour faire face aux problèmes globaux comme le changement climatique ou les risques existentiels liés aux technologies avancées. Dans leur ouvrage ''Unfit for the Future. The Need for Moral Enhancement'' (2012), Persson et Savulescu soutiennent que les êtres humains ne sont pas naturellement dotés d’une psychologie morale leur permettant de gérer les problèmes engendrés par les conditions de vie contemporaines.<ref>Ingmar Persson & Julian Savulescu, ''Unfit for the Future. The Need for Moral Enhancement'', Oxford, Oxford University Press, 2012.</ref>
=== Wlodek Rabinowicz et la théorie de la valeur ===
Wlodek Rabinowicz, philosophe d’origine polonaise établi en Suède et professeur de philosophie pratique à l’Université de Lund depuis 1995, s’est imposé comme une figure de premier plan dans le domaine de la théorie formelle de la valeur, ce que la tradition philosophique nomme l’axiologie. Ses recherches portent sur l’analyse conceptuelle de la valeur, l’incommensurabilité des valeurs, l’éthique normative (notamment le conséquentialisme et l’éthique de la population) et la théorie de la décision séquentielle. Avant Sven Ove Hansson, il avait par ailleurs assuré la rédaction en chef de ''Theoria'' de 1995 à 1998.
L’un des problèmes centraux auxquels Rabinowicz s’est attaqué concerne ce que l’on appelle la « restriction centrée sur la personne » en axiologie. Avec Gustaf Arrhenius, il a développé des approches novatrices pour résoudre ces difficultés, notamment celle de « l’ange gardien » et celle de « l’observateur impartial bienveillant ».<ref>Wlodek Rabinowicz & Gustaf Arrhenius, « The Value of Existence », in Iwao Hirose & Jonas Olson (dir.), ''The Oxford Handbook of Value Theory'', Oxford, Oxford University Press, 2015, p. 424-444.</ref>
Les philosophes suédois ont ainsi élaboré des approches formelles élaborées pour traiter les questions d’axiologie. Rabinowicz et Gustaf Arrhenius ont proposé des modèles influents pour penser la valeur de l’existence et la comparabilité axiologique ; de leur côté, Rabinowicz et Toni Rønnow-Rasmussen ont développé une analyse devenue classique des rapports entre attitudes évaluatives et valeur.<ref>Wlodek Rabinowicz & Toni Rønnow-Rasmussen, « The Strike of the Demon: On Fitting Pro-Attitudes and Value », ''Ethics'', vol. 114, n° 3, 2004, p. 391-423.</ref> Ces travaux techniques en logique déontique et en théorie de la valeur témoignent de la continuité d’un style suédois de philosophie formelle, caractérisé par son attachement à la formalisation et à l’analyse conceptuelle rigoureuse.
Rabinowicz a également participé à la direction éditoriale de revues philosophiques importantes, notamment ''Economics and Philosophy'' et ''Philosophy and Phenomenological Research'', contribuant à l’internationalisation de la philosophie analytique suédoise.
=== Gunnar Björnsson et la responsabilité morale ===
Gunnar Björnsson, professeur de philosophie pratique à l’Université de Stockholm, travaille principalement sur la métaéthique, la psychologie morale, la philosophie du langage et la responsabilité morale. Après avoir obtenu son doctorat à Stockholm en 1998, il a occupé divers postes en Suède et aux États-Unis avant de regagner Stockholm.
Les recherches de Björnsson sur la responsabilité morale se distinguent par une approche naturaliste, qui s’efforce de développer une théorie générale de la responsabilité à la fois conceptuellement rigoureuse et empiriquement adéquate.<ref>Gunnar Björnsson, « Explaining (away) the epistemic condition on moral responsibility », ''Synthese'', vol. 194, 2017, p. 2461-2488.</ref> Il s’intéresse particulièrement à la psychologie des attributions de responsabilité : pourquoi les individus attribuent-ils la responsabilité selon certains schémas plutôt que d’autres ?
Björnsson a coordonné le Projet de Responsabilité de Göteborg (''Gothenburg Responsibility Project'') depuis sa création en 2011 jusqu’en 2015, année où le projet obtint du Conseil suédois de la recherche un financement courant sur dix ans, destiné notamment à recruter le philosophe Paul Russell, spécialiste du libre arbitre et de la philosophie de Hume venu de l’Université de Colombie-Britannique, pour en prendre la direction. Ce projet ambitieux, désormais appelé Projet de Responsabilité Lund-Göteborg (''Lund Gothenburg Responsibility Project'') depuis son extension à l’Université de Lund, rassemble des chercheurs de plusieurs universités suédoises pour étudier la responsabilité morale et le libre arbitre dans une perspective interdisciplinaire alliant philosophie analytique, psychologie expérimentale et neurosciences.<ref>{{lien web|url=https://www.lgrp.lu.se/|titre=Lund Gothenburg Responsibility Project|site=Université de Lund|consulté le=6 juin 2026}}.</ref>
=== Nick Bostrom et le risque existentiel ===
Une figure d’origine suédoise occupe une place à part dans ce paysage : Nick Bostrom, né à Helsingborg en 1973 et formé d’abord aux universités de Göteborg et de Stockholm, a fait l’essentiel de sa carrière à l’Université d’Oxford, où il a fondé puis dirigé, de 2005 à 2024, le Future of Humanity Institute. Son cas illustre les limites d’une définition strictement institutionnelle de la philosophie suédoise : expatrié, il n’appartient pas aux départements du pays, mais il pèse fortement sur l’image internationale de la pensée d’origine suédoise.
Bostrom s’est d’abord fait connaître par son argument de la simulation, selon lequel, sous certaines hypothèses concernant le développement des civilisations technologiques, au moins l’une des trois propositions suivantes doit être vraie : presque toutes les civilisations s’éteignent avant d’atteindre la maturité technologique ; presque aucune civilisation parvenue à maturité ne lance de simulations d’esprits conscients ; ou bien nous vivons très probablement nous-mêmes dans une simulation.<ref>Nick Bostrom, « Are You Living in a Computer Simulation? », ''The Philosophical Quarterly'', vol. 53, n° 211, 2003, p. 243-255.</ref> Son ouvrage ''Superintelligence'' (2014) a ensuite placé au centre du débat public la question du risque existentiel lié à une intelligence artificielle qui dépasserait les capacités humaines, en examinant les problèmes de contrôle et d’alignement des objectifs d’un tel système.<ref>Nick Bostrom, ''Superintelligence. Paths, Dangers, Strategies'', Oxford, Oxford University Press, 2014.</ref> Ces analyses, abondamment discutées, ont contribué à structurer le champ aujourd’hui désigné comme éthique de l’intelligence artificielle, auquel une section ultérieure du présent article est consacrée.
== Les institutions et l’organisation de la recherche ==
=== Les universités et les départements de philosophie ===
L’enseignement et la recherche en philosophie en Suède se concentrent principalement dans quatre grandes universités : Uppsala, Stockholm, Göteborg et Lund. Chacune possède un département de philosophie doté de spécificités et de domaines d’excellence propres.
L’Université d’Uppsala, fondée en 1477 et la plus ancienne des pays nordiques, occupe une place particulière dans l’histoire de la philosophie suédoise. C’est là qu’Axel Hägerström enseigna de 1911 à 1933, établissant l’École d’Uppsala. Aujourd’hui, le département de philosophie d’Uppsala perpétue cette tradition en proposant des cours de philosophie théorique et pratique à tous les niveaux de formation.
L’Université de Stockholm abrite le plus important département de philosophie de Suède, divisé entre philosophie théorique et philosophie pratique. Le département de philosophie théorique se consacre à des questions touchant la réalité, la connaissance, l’esprit, le langage, la logique et la philosophie des sciences. Le département de philosophie pratique se concentre en particulier sur la métaéthique, avec des recherches portant sur le jugement moral, le langage moral, l’agentivité morale, la responsabilité et le blâme.<ref>{{lien web|url=https://www.su.se/department-of-philosophy/|titre=Metaethics – Research Group|site=Department of Philosophy, Stockholm University|consulté le=6 juin 2026}}.</ref>
L’Université de Lund mène des recherches dans trois domaines principaux : philosophie pratique, philosophie théorique et sciences cognitives. En philosophie pratique, Lund jouit d’une reconnaissance internationale pour ses travaux sur la théorie de la valeur, la responsabilité morale et le libre arbitre, le concept de bien-être, les théories de la causalité, l’action collective et la théorie de la décision dynamique.
=== Le financement de la recherche ===
Le Conseil suédois de la recherche (''Vetenskapsrådet'') constitue le principal organisme de financement de la recherche en sciences humaines dans les universités et instituts de recherche suédois. Pour la philosophie, ce financement permet de soutenir des projets de recherche individuels et collectifs, des postes postdoctoraux et des programmes doctoraux. Dans la période récente, les financements alloués à la philosophie ont permis de développer des projets de grande envergure, à l’image du Projet de Responsabilité Lund-Göteborg, témoignant de l’importance reconnue à la discipline.
Les universités suédoises financent également des postes de doctorat en philosophie, généralement pour une durée de quatre ans, incluant au minimum trois ans de recherche et au maximum une année de cours. Ces postes doctoraux constituent des emplois à part entière donnant accès aux prestations sociales suédoises ordinaires, ce qui distingue le système suédois de celui de nombreux autres pays.
=== Les revues et la diffusion de la recherche ===
''Filosofisk tidskrift'', fondée en 1980, est la principale revue de philosophie en langue suédoise. Publiée trimestriellement, elle couvre l’ensemble des domaines de la philosophie tout en s’abstenant de publier des contributions requérant des prérequis techniques trop spécialisés. Elle demeure une plateforme importante pour le débat philosophique en Suède et dans les pays nordiques.
''Theoria'', fondée en 1935 par Åke Petzäll, demeure la principale revue internationale de philosophie publiée en Suède.<ref name="theoriakth" /> Elle a joué un rôle déterminant dans le développement de la philosophie suédoise moderne, servant notamment de tribune aux débats entre l’École d’Uppsala et le positivisme logique ; des textes de Carl Gustav Hempel ou d’Ernst Cassirer y parurent dès les années 1930. Publiée désormais six fois par an et codirigée par Sven Ove Hansson et Henrik Lundvall, ''Theoria'' continue d’accueillir des philosophes suédois et internationaux dans tous les domaines de la discipline et jouit d’une réputation bien établie sur la scène philosophique internationale.<ref name="theoriakth" />
=== La formation doctorale et les cours nationaux ===
La formation doctorale en philosophie en Suède suit un modèle structuré. Pour être admis au programme doctoral, les étudiants doivent déjà posséder un master en philosophie (ou un diplôme jugé équivalent). Le doctorat correspond généralement à quatre années d’études à temps plein, soit 240 crédits ECTS, répartis entre des cours, des séminaires, la supervision et la rédaction de la thèse.<ref>{{lien web|url=https://www.fil.lu.se/en/research/doctoral-studies/|titre=Doctoral studies|site=Department of Philosophy, Lund University|consulté le=6 juin 2026}}.</ref>
Une particularité du système suédois réside dans l’organisation de cours doctoraux nationaux en philosophie. Ces cours, généralement de 7,5 crédits ECTS avec un enseignement concentré sur une semaine, accueillent prioritairement les étudiants de master et les doctorants inscrits dans les universités suédoises.<ref name="umea">{{lien web|url=https://www.umu.se/en/|titre=National Graduate Courses in Philosophy|site=Department of Historical, Philosophical and Religious Studies, Umeå University|consulté le=6 juin 2026}}.</ref> Ce dispositif permet aux doctorants de toutes les universités du pays de suivre des formations spécialisées données par des experts, même si leur université d’attache ne possède pas de spécialiste dans le domaine considéré. L’Université d’Umeå coordonne depuis plusieurs années l’organisation de ces cours nationaux, qui ont abordé des sujets aussi variés que l’éthique médicale, la théorie de la décision ou l’épistémologie morale.<ref name="umea" />
== Les domaines de recherche privilégiés ==
=== Métaéthique et philosophie morale ===
La métaéthique constitue l’un des domaines les plus actifs de la recherche philosophique du pays. Les philosophes suédois travaillant dans ce champ s’interrogent sur la nature des jugements moraux, le statut des faits moraux, les relations entre croyances morales et motivation, ainsi que sur les questions de responsabilité et de blâme.
Plusieurs facteurs expliquent cette concentration. D’abord, l’héritage d’Axel Hägerström et de sa théorie non-cognitiviste continue d’exercer une influence sur les débats actuels. Les philosophes suédois contemporains réexaminent ces questions à l’aide des outils de la philosophie analytique moderne, en intégrant notamment les développements de la philosophie du langage et de la philosophie de l’esprit.
L’Université de Stockholm s’est particulièrement illustrée dans ce domaine, avec un groupe de recherche en métaéthique réunissant Gunnar Björnsson, Jonas Olson et Anandi Hattiangadi. Leurs travaux couvrent des questions sémantiques (sur la signification des concepts et termes moraux), métaphysiques (sur l’existence et la nature des faits et propriétés moraux), épistémologiques (sur la possibilité de la connaissance morale) et psychologiques (sur la nature des jugements moraux et leur rapport à la motivation). Jonas Olson a notamment proposé une défense systématique de la théorie de l’erreur en morale, qui prolonge sous une forme contemporaine certaines intuitions de Hägerström.<ref>Jonas Olson, ''Moral Error Theory. History, Critique, Defence'', Oxford, Oxford University Press, 2014.</ref>
=== Responsabilité morale et libre arbitre ===
La question de la responsabilité morale, bien qu’étroitement liée à la métaéthique, mérite une attention distincte, tant elle a donné lieu à des recherches soutenues en Suède. Le Projet de Responsabilité Lund-Göteborg, financé sur dix ans par le Conseil suédois de la recherche et dirigé par Paul Russell, témoigne de l’importance accordée à ce thème.
Les philosophes suédois travaillant sur la responsabilité adoptent souvent une approche naturaliste, cherchant à comprendre le fonctionnement psychologique des attributions de responsabilité avant de formuler des théories normatives sur qui devrait être tenu responsable et de quoi. Cette démarche permet d’éclairer des questions classiques comme le problème du libre arbitre et du déterminisme, ou les conditions de la responsabilité morale.
=== Théorie de la valeur et éthique de la population ===
La théorie formelle de la valeur constitue un autre domaine dans lequel les philosophes suédois ont apporté des contributions notables. Wlodek Rabinowicz à Lund et Gustaf Arrhenius à Stockholm jouissent d’une reconnaissance particulière pour leurs travaux dans ce champ.
Un problème central en axiologie concerne l’éthique de la population : comment comparer le bien-être de différentes populations ? Cette question, qui peut sembler purement abstraite, possède des implications pratiques étendues pour les politiques environnementales, les politiques de santé publique et les questions de justice intergénérationnelle. Arrhenius a notamment établi une série de théorèmes d’impossibilité montrant qu’aucune axiologie ne peut satisfaire simultanément un ensemble de conditions pourtant intuitivement plausibles.<ref>Gustaf Arrhenius, « An Impossibility Theorem for Welfarist Axiologies », ''Economics and Philosophy'', vol. 16, n° 2, 2000, p. 247-266.</ref>
Les philosophes suédois ont élaboré des approches formelles raffinées pour aborder ces questions. Rabinowicz et Arrhenius ont, par exemple, proposé des modèles influents pour traiter la valeur de l’existence et la comparabilité axiologique ; de leur côté, Rabinowicz et Toni Rønnow-Rasmussen ont développé une analyse fine des rapports entre attitudes évaluatives et valeur, notamment à travers leur discussion du « mauvais type de raisons » dans l’analyse des attitudes appropriées.<ref>Wlodek Rabinowicz & Toni Rønnow-Rasmussen, « The Strike of the Demon: On Fitting Pro-Attitudes and Value », ''Ethics'', vol. 114, n° 3, 2004, p. 391-423.</ref> Ces travaux techniques en logique déontique et en théorie de la valeur s’inscrivent pleinement dans la tradition analytique suédoise.
=== Épistémologie et philosophie de l’esprit ===
L’épistémologie et la philosophie de l’esprit occupent également une place notable dans la philosophie pratiquée en Suède. Åsa Wikforss et Kathrin Glüer-Pagin, philosophe d’origine allemande établie à Stockholm, ont développé des recherches influentes à l’intersection de ces deux domaines.
Un thème central concerne la connaissance de soi : comment connaissons-nous nos propres pensées et croyances ? Cette question, qui peut sembler triviale au premier abord, soulève en réalité des difficultés philosophiques substantielles liées à la transparence épistémique et à l’accès conscient à nos états mentaux.
Wikforss s’est intéressée par la suite aux questions d’épistémologie sociale, en particulier à la résistance à la connaissance et à la diffusion de la désinformation. Ces travaux relient des questions épistémologiques classiques (qu’est-ce que la connaissance ? comment l’acquérons-nous ?) à des préoccupations contemporaines urgentes relatives à la démocratie et à l’espace public.
=== Philosophie des sciences et philosophie de la technologie ===
La philosophie des sciences occupe une place singulière dans la philosophie suédoise, avec une attention particulière portée aux méthodes scientifiques, à l’évaluation des preuves et à la structure des théories scientifiques. Cette orientation reflète en partie l’influence historique du positivisme logique et de l’empirisme, mais aussi le souci de collaborer étroitement avec les scientifiques pour comprendre les pratiques réelles de la recherche.
Sven Ove Hansson incarne particulièrement cette approche par ses travaux sur la philosophie de la technologie et des risques. Au sein de l’Institut royal de technologie (KTH) de Stockholm, Hansson a dirigé un département de philosophie et d’histoire intégré dans une école d’ingénieurs. Cette position lui permet de travailler directement avec des ingénieurs et des scientifiques sur des questions pratiques concernant l’évaluation des risques, la sécurité et l’éthique de la technologie. Ses ouvrages, notamment ''The Ethics of Technology. Methods and Approaches'', ainsi que ses contributions à la théorie de la révision des croyances, font autorité dans ces domaines.<ref>Sven Ove Hansson (dir.), ''The Ethics of Technology. Methods and Approaches'', Londres, Rowman & Littlefield International, 2017.</ref>
=== Philosophie politique et théorie de la démocratie ===
La philosophie politique forme un autre secteur actif de la recherche philosophique suédoise. Les philosophes suédois s’intéressent particulièrement aux fondements normatifs de la démocratie, aux théories de la justice et aux questions de légitimité politique.
Torbjörn Tännsjö a défendu une forme de démocratie populiste selon laquelle les décisions politiques seraient prises directement par référendum plutôt que par des représentants élus. Cette position s’oppose au consensus dominant en philosophie politique, qui tend à privilégier la démocratie représentative avec ses médiations et ses filtres institutionnels. Tännsjö soutient que la démocratie directe respecte mieux l’égalité entre citoyens et évite la constitution d’une classe politique professionnelle détachée du peuple.
== La philosophie suédoise et les enjeux contemporains ==
=== Éthique de l’intelligence artificielle ===
L’émergence de l’intelligence artificielle comme technologie transformatrice a incité plusieurs philosophes et institutions suédoises à s’engager dans l’examen des questions éthiques qu’elle soulève. Ces travaux s’inscrivent dans la tradition suédoise de philosophie appliquée, qui se donne pour tâche d’utiliser les outils philosophiques pour éclairer des problèmes concrets.
AI Sweden, le centre national suédois pour l’intelligence artificielle appliquée, a élaboré des principes régissant une utilisation responsable de l’IA. Ces principes insistent sur la légalité et la transparence (les systèmes d’IA doivent être développés et utilisés dans le respect de la loi et avec une transparence appropriée), le caractère éthique et équitable (les systèmes d’IA doivent être conçus et opérés d’une manière respectant les droits humains, les valeurs et la diversité culturelle), ainsi que sur la sécurité et la robustesse (les systèmes d’IA doivent être fiables, maîtrisables et sûrs).<ref>{{lien web|url=https://www.ai.se/en|titre=Responsible use of AI|site=AI Sweden|consulté le=6 juin 2026}}.</ref>
Il importe de ne pas confondre les registres : AI Sweden est un organisme de coordination technologique, non une institution philosophique, et ses principes fournissent un cadre institutionnel plutôt qu’une analyse normative. Cette analyse revient aux philosophes. La question de la responsabilité relative aux décisions d’agents artificiels rejoint directement les recherches de Gunnar Björnsson sur les obligations partagées et la responsabilité collective ; l’exigence de sûreté et de robustesse prolonge les analyses de Sven Ove Hansson sur la décision en situation d’incertitude et sur l’acceptabilité des risques technologiques ; quant aux débats internationaux sur le risque existentiel, ils doivent beaucoup, on l’a vu, à Nick Bostrom.<ref>Gunnar Björnsson, « Essentially Shared Obligations », ''Midwest Studies in Philosophy'', vol. 38, 2014, p. 103-120 ; Sven Ove Hansson, ''The Ethics of Risk. Ethical Analysis in an Uncertain World'', New York, Palgrave Macmillan, 2013.</ref>
L’Université de Linköping propose un cours spécifique consacré à l’éthique de l’intelligence artificielle. Ce cours aborde trois grands enjeux éthiques soulevés par les systèmes autonomes : la responsabilité relative aux décisions prises par des agents artificiels, les questions de biais et de discrimination résultant de l’utilisation de l’IA, et l’importance de la participation démocratique dans l’élaboration et le déploiement des systèmes d’IA.
=== Éthique environnementale et développement durable ===
L’éthique environnementale constitue un autre domaine dans lequel les philosophes suédois contribuent activement, reflétant les préoccupations de la société suédoise pour l’environnement et le développement durable. La Suède possède une longue tradition d’éducation environnementale, qui articule l’éducation en plein air et les préoccupations écologiques depuis les années 1960.
L’éducation pour le développement durable a connu en Suède une évolution notable. D’une approche traditionnelle centrée sur la transmission de connaissances scientifiques, elle est passée à une approche plus participative, encourageant la pensée critique et l’autonomie de jugement. Ce déplacement reflète un changement dans la conception même de l’éducation environnementale : il ne s’agit plus d’inculquer des valeurs et des comportements prédéterminés, mais de développer des capacités de délibération et d’action citoyenne.
Sur le versant théorique, l’éthique de l’environnement croise en Suède la philosophie du risque. Les analyses de Sven Ove Hansson sur la décision en situation d’incertitude et sur le principe de précaution montrent les limites d’une évaluation des politiques environnementales qui s’en remettrait exclusivement au calcul coût-bénéfice : imposer un risque à autrui constitue un acte moralement chargé, qui appelle une justification et non un simple calcul d’utilité espérée.<ref>Sven Ove Hansson, ''The Ethics of Risk. Ethical Analysis in an Uncertain World'', New York, Palgrave Macmillan, 2013.</ref> Cette approche fournit aux questions de durabilité un cadre conceptuel plus exigeant que la seule invocation de valeurs écologiques.
=== Philosophie publique et engagement démocratique ===
Un trait notable de la période récente réside dans l’importance accordée à la présence des philosophes dans la cité et à leur participation aux débats démocratiques. Contrairement à une philosophie académique qui resterait enfermée dans ses murs, de nombreux philosophes suédois considèrent qu’ils ont une responsabilité envers la société : rendre leurs recherches accessibles et les mettre au service des citoyens.
Torbjörn Tännsjö représente sans doute l’exemple le plus net de cette philosophie publique. Il intervient régulièrement dans les médias suédois, rédige des chroniques pour les journaux, participe à des émissions de radio et de télévision, et publie des ouvrages destinés au grand public.
Åsa Wikforss incarne une autre forme d’engagement public, centrée sur l’éducation citoyenne et la défense de la connaissance. Son ouvrage ''Alternativa fakta'' (''Faits alternatifs'') et ses nombreuses interventions publiques visent à munir les citoyens des instruments intellectuels nécessaires pour naviguer dans un environnement informationnel complexe et résister à la désinformation. Cette approche témoigne d’une conviction constante : la connaissance est indispensable au bon fonctionnement de la démocratie.
La philosophie à l’Université de Södertörn constitue l’exception la plus nette au consensus analytique : la recherche y est officiellement centrée sur la philosophie continentale européenne moderne et sur ses points de contact avec l’histoire, de la philosophie antique à l’idéalisme allemand, et l’établissement abrite depuis 2024 un centre consacré à la philosophie grecque ancienne. Ses membres participent activement à la vie culturelle suédoise : ils donnent des conférences au Moderna museet, à Bonniers Konsthall et dans diverses galeries de Stockholm, interviennent dans des émissions de la radio nationale comme ''Filosofiska rummet'' et contribuent ainsi à la diffusion du discours philosophique auprès d’un public élargi.<ref name="sodertorn">{{lien web|url=https://www.sh.se/english/sodertorn-university/research/subjects/philosophy|titre=Philosophy|site=Södertörn University|consulté le=6 juin 2026}}.</ref>
== Conclusion ==
La philosophie suédoise des vingt-cinq dernières années se caractérise par plusieurs traits distinctifs. D’abord, la prépondérance nette de la tradition analytique, héritée de l’École d’Uppsala et de la transformation intellectuelle opérée au cours du XX{{e}} siècle. Cette orientation se manifeste par l’attachement aux méthodes rigoureuses d’argumentation, à l’analyse conceptuelle et à la clarté d’expression.
Ensuite, une forte internationalisation : les philosophes suédois publient largement dans les revues internationales de premier plan et participent activement aux débats philosophiques mondiaux. Cette ouverture internationale ne fait cependant pas obstacle à un ancrage local solide, incarné par des institutions universitaires de haut niveau, des revues nationales vivantes et un système de formation doctorale bien structuré.
Un autre trait marquant est l’importance accordée à la philosophie appliquée et à l’engagement public. Nombre de philosophes suédois considèrent qu’ils ont une responsabilité envers la société : rendre leurs recherches accessibles et contribuer aux débats démocratiques. Cet engagement se traduit par des interventions médiatiques régulières, la publication d’ouvrages grand public et la participation à des commissions d’expertise.
Les domaines de recherche privilégiés reflètent à la fois la continuité historique et les préoccupations contemporaines. La métaéthique, la responsabilité morale, la théorie de la valeur et l’épistémologie prolongent l’héritage d’Uppsala. Les recherches consacrées à l’éthique de l’intelligence artificielle, à l’éthique environnementale et à la philosophie publique répondent aux enjeux les plus pressants du temps présent.
On peut, pour finir, risquer une thèse d’ensemble. La Suède offre le cas d’une philosophie analytique institutionnellement dominante, mais que trois contrepoids empêchent de se refermer sur elle-même : la philosophie publique, qui la confronte aux citoyens ; l’interdisciplinarité technologique, qui l’expose aux sciences de l’ingénieur et aux choix collectifs concernant les techniques ; l’exception continentale de Södertörn, enfin, qui maintient vivante, aux portes de Stockholm, une autre manière de philosopher. C’est de cette tension réglée entre hégémonie méthodologique et ouvertures correctrices que le paysage philosophique suédois tire sa physionomie propre.
== Références ==
{{references|colonnes=2}}
== Bibliographie ==
=== Ouvrages généraux sur la philosophie suédoise ===
* Lagerlund, Henrik, ''Filosofi i Sverige under tusen år'' [La philosophie en Suède durant mille ans], Lund, Studentlitteratur, 2003.
* Nordin, Svante, ''Från Hägerström till Hedenius. Den moderna svenska filosofin'' [De Hägerström à Hedenius. La philosophie suédoise moderne], Bodafors, Doxa, 1984.
* Nygård, Stefan & Strang, Johan (dir.), ''Mellan idealism och analytisk filosofi. Den moderna filosofin i Finland och Sverige 1880-1950'' [Entre idéalisme et philosophie analytique. La philosophie moderne en Finlande et en Suède, 1880-1950], Helsinki, Svenska litteratursällskapet i Finland, 2006.
* Strang, Johan, ''History, Transfer, Politics. Five Studies on the Legacy of Uppsala Philosophy'', Helsinki, Université d’Helsinki, 2010 (thèse de doctorat).
=== L’École d’Uppsala et l’histoire de la philosophie suédoise ===
* Hägerström, Axel, ''Inquiries into the Nature of Law and Morals'', trad. anglaise C. D. Broad, Uppsala, Almqvist & Wiksell, 1953.
* Hägerström, Axel, ''Philosophy and Religion'', trad. anglaise Robert T. Sandin, Londres, George Allen & Unwin, 1964.
* Hedenius, Ingemar, ''Om rätt och moral'' [Du droit et de la morale], Stockholm, Wahlström & Widstrand, 1941.
* Hedenius, Ingemar, ''Tro och vetande'' [Croyance et savoir], Stockholm, Bonniers, 1949.
* Nordin, Svante, ''Ingemar Hedenius. En filosof och hans tid'' [Ingemar Hedenius. Un philosophe et son temps], Stockholm, Natur & Kultur, 2004.
* Sandin, Robert T., « The Founding of the Uppsala School », ''Journal of the History of Ideas'', vol. 23, n° 4, 1962, p. 496-512.
=== Œuvres des philosophes suédois contemporains ===
==== Torbjörn Tännsjö ====
* Tännsjö, Torbjörn, ''Populist Democracy. A Defence'', Londres, Routledge, 1992.
* Tännsjö, Torbjörn, ''Hedonistic Utilitarianism'', Édimbourg, Edinburgh University Press, 1998.
* Tännsjö, Torbjörn, ''Coercive Care. The Ethics of Choice in Health and Medicine'', Londres, Routledge, 1999.
* Tännsjö, Torbjörn, ''Global Democracy. The Case for a World Government'', Édimbourg, Edinburgh University Press, 2008.
* Tännsjö, Torbjörn, ''Setting Health-Care Priorities. What Ethical Theories Tell Us'', Oxford, Oxford University Press, 2019.
==== Sven Ove Hansson ====
* Hansson, Sven Ove, « Semi-revision », ''Journal of Applied Non-Classical Logics'', vol. 7, n° 1-2, 1997, p. 151-175.
* Hansson, Sven Ove, ''A Textbook of Belief Dynamics. Theory Change and Database Updating'', Dordrecht, Kluwer Academic Publishers, 1999.
* Hansson, Sven Ove, « A history of Theoria », ''Theoria'', vol. 76, n° 4, 2010.
* Hansson, Sven Ove, ''The Ethics of Risk. Ethical Analysis in an Uncertain World'', New York, Palgrave Macmillan, 2013.
* Hansson, Sven Ove (dir.), ''The Ethics of Technology. Methods and Approaches'', Londres, Rowman & Littlefield, 2017.
==== Åsa Wikforss ====
* Wikforss, Åsa, « Semantic Normativity », ''Philosophical Studies'', vol. 102, n° 2, 2001, p. 203-226.
* Glüer, Kathrin & Wikforss, Åsa, « Against Content Normativity », ''Mind'', vol. 118, n° 469, 2009, p. 31-70.
* Glüer, Kathrin & Wikforss, Åsa, « The Normativity of Meaning and Content », in Edward N. Zalta (dir.), ''The Stanford Encyclopedia of Philosophy'', Stanford, Stanford University, en ligne.
* Wikforss, Åsa, ''Alternativa fakta. Om kunskapen och dess fiender'' [Faits alternatifs. Sur la connaissance et ses ennemis], Stockholm, Fri Tanke förlag, 2017.
==== Ingmar Persson ====
* Persson, Ingmar, ''The Retreat of Reason. A Dilemma in the Philosophy of Life'', Oxford, Clarendon Press, 2005.
* Persson, Ingmar & Savulescu, Julian, ''Unfit for the Future. The Need for Moral Enhancement'', Oxford, Oxford University Press, 2012.
* Persson, Ingmar, ''From Morality to the End of Reason. An Essay on Rights, Reasons, and Responsibility'', Oxford, Oxford University Press, 2013.
* Persson, Ingmar, ''Inclusive Ethics. Extending Beneficence and Egalitarian Justice'', Oxford, Oxford University Press, 2017.
==== Wlodek Rabinowicz ====
* Rabinowicz, Wlodek & Rønnow-Rasmussen, Toni, « The Strike of the Demon: On Fitting Pro-Attitudes and Value », ''Ethics'', vol. 114, n° 3, 2004, p. 391-423.
* Rabinowicz, Wlodek, « Value Relations », ''Theoria'', vol. 74, n° 1, 2008, p. 18-49.
* Rabinowicz, Wlodek, « Broome and the Intuition of Neutrality », ''Philosophical Issues'', vol. 19, 2009, p. 389-411.
* Rabinowicz, Wlodek & Arrhenius, Gustaf, « The Value of Existence », in Iwao Hirose & Jonas Olson (dir.), ''The Oxford Handbook of Value Theory'', Oxford, Oxford University Press, 2015, p. 424-444.
==== Gunnar Björnsson ====
* Björnsson, Gunnar & Persson, Karl, « The Explanatory Component of Moral Responsibility », ''Noûs'', vol. 46, n° 2, 2012, p. 326-354.
* Björnsson, Gunnar, « Essentially Shared Obligations », ''Midwest Studies in Philosophy'', vol. 38, 2014, p. 103-120.
* Björnsson, Gunnar & Pereboom, Derk, « Traditional and Experimental Approaches to Free Will and Moral Responsibility », in Justin Sytsma & Wesley Buckwalter (dir.), ''A Companion to Experimental Philosophy'', Chichester, Wiley Blackwell, 2016, p. 142-157.
* Björnsson, Gunnar, « Explaining (away) the epistemic condition on moral responsibility », ''Synthese'', vol. 194, 2017, p. 2461-2488.
==== Nick Bostrom ====
* Bostrom, Nick, ''Anthropic Bias. Observation Selection Effects in Science and Philosophy'', New York, Routledge, 2002.
* Bostrom, Nick, « Are You Living in a Computer Simulation? », ''The Philosophical Quarterly'', vol. 53, n° 211, 2003, p. 243-255.
* Bostrom, Nick, ''Superintelligence. Paths, Dangers, Strategies'', Oxford, Oxford University Press, 2014.
==== Autres philosophes suédois contemporains ====
* Arrhenius, Gustaf, « An Impossibility Theorem for Welfarist Axiologies », ''Economics and Philosophy'', vol. 16, n° 2, 2000, p. 247-266.
* Gärdenfors, Peter, ''Knowledge in Flux. Modeling the Dynamics of Epistemic States'', Cambridge (Mass.), MIT Press, 1988.
* Gärdenfors, Peter, ''Conceptual Spaces. The Geometry of Thought'', Cambridge (Mass.), MIT Press, 2000.
* Olson, Jonas, ''Moral Error Theory. History, Critique, Defence'', Oxford, Oxford University Press, 2014.
=== Études sur la philosophie suédoise ===
* Hansson, Jonas & Nordin, Svante, ''Ernst Cassirer. The Swedish Years'', Berne, Peter Lang, 2006.
* Strang, Johan, « Why “Nordic Democracy”? », in Jussi Kurunmäki & Johan Strang (dir.), ''Rhetorics of Nordic Democracy'', Helsinki, Suomalaisen Kirjallisuuden Seura, 2010.
=== Revues philosophiques suédoises ===
* ''Filosofisk tidskrift'' [Revue philosophique], revue trimestrielle, Stockholm, 1980-.
* ''Theoria. A Swedish Journal of Philosophy'', revue internationale fondée en 1935 par Åke Petzäll, publiée par Wiley-Blackwell pour le compte de la fondation Stiftelsen Theoria, 1935-.
=== Encyclopédies et instruments de référence ===
* Craig, Edward (dir.), ''Routledge Encyclopedia of Philosophy'', Londres, Routledge, 1998 ; entrées consacrées à Axel Hägerström et à la philosophie scandinave.
* Zalta, Edward N. (dir.), ''The Stanford Encyclopedia of Philosophy'', Stanford, Stanford University ; en ligne, entrées « Logic of Belief Revision » (rédigée par Sven Ove Hansson) et « The Normativity of Meaning and Content » (rédigée par Kathrin Glüer et Åsa Wikforss), consultées le 6 juin 2026.
=== Sites web et ressources électroniques ===
* AI Sweden : {{lien web|url=https://www.ai.se/en|titre=Centre national suédois pour l'intelligence artificielle appliquée|consulté le=6 juin 2026}}.
* Filosofisk tidskrift : {{lien web|url=https://www.filosofisktidskrift.se/|titre=Site officiel de la revue|consulté le=6 juin 2026}}.
* Lund Gothenburg Responsibility Project : {{lien web|url=https://www.lgrp.lu.se/|titre=Site officiel du projet|consulté le=6 juin 2026}}.
* KTH, Division of Philosophy : {{lien web|url=https://www.kth.se/philhist/phil|titre=Recherche en philosophie au KTH et revue Theoria|consulté le=6 juin 2026}}.
* Nick Bostrom : {{lien web|url=https://nickbostrom.com/|titre=Site personnel|consulté le=6 juin 2026}}.
* Department of Philosophy, Lund University : {{lien web|url=https://www.fil.lu.se/en/|titre=Site du département de philosophie de Lund|consulté le=6 juin 2026}}.
* Department of Philosophy, Stockholm University : {{lien web|url=https://www.su.se/department-of-philosophy/|titre=Site du département de philosophie de Stockholm|consulté le=6 juin 2026}}.
* Department of Philosophy, Linguistics and Theory of Science (FLoV), University of Gothenburg : {{lien web|url=https://www.gu.se/en/flov|titre=Site du département de philosophie de Göteborg|consulté le=6 juin 2026}}.
* Department of Philosophy, Uppsala University : {{lien web|url=https://www.uu.se/en/department/philosophy|titre=Site du département de philosophie d'Uppsala|consulté le=6 juin 2026}}.
* Svenska Akademien : {{lien web|url=https://www.svenskaakademien.se/en|titre=Site officiel de l'Académie suédoise|consulté le=6 juin 2026}}.
* Södertörn University : {{lien web|url=https://www.sh.se/english/sodertorn-university/research/subjects/philosophy|titre=Philosophy|consulté le=6 juin 2026}}.
* Swedish Research Council (Vetenskapsrådet) : {{lien web|url=https://www.vr.se/english.html|titre=Financement de la recherche en philosophie|consulté le=6 juin 2026}}.
* Umeå University : {{lien web|url=https://www.umu.se/en/|titre=National Graduate Courses in Philosophy|consulté le=6 juin 2026}}.
{{AutoCat}}
nih3g4ubpb7seshrqdfq535vopqi89c
Python pour le calcul scientifique/Annexe/Index
0
83349
768602
768575
2026-06-25T14:47:53Z
Cdang
1202
/* C */ itertools.compress
768602
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>itertools.compress()</code> : [[../../Découverte_de_Python_et_de_Jupyter#Itérable|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]] < [[../../|↑]] >
[[Catégorie:Python pour le calcul scientifique (livre)]]
3bbufoizate3istwyteib3lwz009n0d
768633
768602
2026-06-25T15:32:52Z
Cdang
1202
/* M */ matrix
768633
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>itertools.compress()</code> : [[../../Découverte_de_Python_et_de_Jupyter#Itérable|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 />→ [[#P|Pyplot]]
* <code>np.matrix()</code> (déprécié) : [[../../Manipulation_de_matrices#Différence_entre_array_et_matrix|1]]<br />→ [[#A|<code>np.array()</code>]], [(../../Algèbre_linéaire#Opérations_vectorielles|2]]
* <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]] < [[../../|↑]] >
[[Catégorie:Python pour le calcul scientifique (livre)]]
8a6lxrqgzvr7dsnss527fkyrs77s0tk
768634
768633
2026-06-25T15:33:19Z
Cdang
1202
/* M */ typo
768634
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>itertools.compress()</code> : [[../../Découverte_de_Python_et_de_Jupyter#Itérable|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 />→ [[#P|Pyplot]]
* <code>np.matrix()</code> (déprécié) : [[../../Manipulation_de_matrices#Différence_entre_array_et_matrix|1]], [[../../Algèbre_linéaire#Opérations_vectorielles|2]]<br />→ [[#A|<code>np.array()</code>]]
* <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]] < [[../../|↑]] >
[[Catégorie:Python pour le calcul scientifique (livre)]]
qvlrn32tqtvevcceukrd4qz3o1khvyo
768707
768634
2026-06-26T08:48:04Z
Cdang
1202
/* W */ where()
768707
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>itertools.compress()</code> : [[../../Découverte_de_Python_et_de_Jupyter#Itérable|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 />→ [[#P|Pyplot]]
* <code>np.matrix()</code> (déprécié) : [[../../Manipulation_de_matrices#Différence_entre_array_et_matrix|1]], [[../../Algèbre_linéaire#Opérations_vectorielles|2]]<br />→ [[#A|<code>np.array()</code>]]
* <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>where()</code> : [[../../Manipulation_de_matrices#Trouver_une_valeur|1]]
* <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]] < [[../../|↑]] >
[[Catégorie:Python pour le calcul scientifique (livre)]]
s16cfuc9xsj84xdw6sz4iny0nj6h5ve
Discussion Wikilivres:Le Bistro/2026
5
83406
768596
768431
2026-06-25T13:30:32Z
MediaWiki message delivery
36013
/* Intégration du lien vers les contacts juridiques et de sécurité dans le pied de page de votre wiki */ nouvelle section
768596
wikitext
text/x-wiki
== Actualités techniques n° 2026-03 ==
<section begin="technews-2026-W03"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/03|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* La Fondation Wikimedia a publié des questions directrices pour son plan annuel de juillet 2026 à juin 2027 sur les plateformes [[m:Special:MyLanguage/Wikimedia Foundation Annual Plan/2026-2027/Product & Technology OKRs|Meta]] et ''[[diffblog:2025/12/10/shaping-wikimedia-foundations-2026-2027-annual-goals-key-questions-for-the-wikimedia-movement/|Diff]]''. Celles-ci portent sur les tendances mondiales, une expérimentation plus rapide et plus constructive, un meilleur accompagnement des nouveaux contributeurs, le renforcement du rôle des éditeurs et des utilisateurs avancés, l'amélioration de la collaboration entre les projets, ainsi que le développement et la fidélisation du lectorat. Des commentaires et suggestions sont les bienvenus sur la [[m:Talk:Wikimedia Foundation Annual Plan/2026-2027|page de discussion]].
'''Actualités pour la contribution'''
* Dans le cadre des travaux en cours de l'équipe technique communautaire sur le projet [[m:Special:MyLanguage/Community Wishlist/W372|Listes de surveillance multiples]], l'affichage de [[Special:EditWatchlist|Modifier la liste de surveillance]] sera mis à jour entant que qu'une première étape vers la prise en charge de plusieurs listes de surveillance. De plus, la pagination de [[Special:Search|Recherche]] sera également mise à jour, dans le cadre du travail sur le souhait [[m:Special:MyLanguage/Community Wishlist/W186|Refonte de la pagination / navigation des pages]]. [https://phabricator.wikimedia.org/T411596]
* [[m:Special:GlobalWatchlist|La Liste de Surveillance Globale]] est une [[mw:Special:MyLanguage/Extension:GlobalWatchlist|extension]] de MediaWiki qui vous permet de voir vos listes de surveillance provenant de différents wikis sur la même page. Il a récemment été mis à jour pour ressembler davantage à la [[Special:Watchlist|Liste de surveillance]] régulière, par exemple en le préparant pour les comptes temporaires dans le masquage IP (y compris le réacheminement des liens des utilisateurs vers les pages de contributions), en mettant les titres de page en gras et en ouvrant les liens dans les résumés d'édition et les balises dans de nouveaux onglets du navigateur. [https://phabricator.wikimedia.org/T398361][https://phabricator.wikimedia.org/T298919][https://phabricator.wikimedia.org/T273526][https://phabricator.wikimedia.org/T286309]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:28|la tâche soumise|les {{formatnum:28}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:28||s}} la semaine dernière]]. Par exemple, le problème selon lequel les blocs globaux ne disposaient pas de l'option permettant de désactiver l'envoi d'e-mails a maintenant été résolu et sera disponible à l'utilisation à partir de la semaine du 13 janvier. [https://phabricator.wikimedia.org/T401293]
'''Actualités pour la contribution technique'''
* L'[[mw:Special:MyLanguage/VisualEditor/Citation tool|outil de citation VisualEditor]] et les [[mw:Special:MyLanguage/Help:Reference Previews|Aperçus de référence]] prennent désormais en charge "carte" comme type de référence. [https://phabricator.wikimedia.org/T411083]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.10|MediaWiki]]/[[mw:MediaWiki 1.46/wmf.11|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/03|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W03"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 12 janvier 2026 à 20:33 (CET)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=29907192 -->
== Thank You for Last Year – Join Wiki Loves Ramadan 2026 ==
Dear Wikimedia communities,
We hope you are doing well, and we wish you a happy New Year.
''Last year, we captured light. This year, we’ll capture legacy.''
In 2025, communities around the world shared the glow of Ramadan nights and the warmth of collective iftars. In 2026, ''Wiki Loves Ramadan'' is expanding, bringing more stories, more cultures, and deeper global connections across Wikimedia projects.
We invite you to explore the ''Wiki Loves Ramadan 2026'' [[m:Special:MyLanguage/Wiki Loves Ramadan 2026|Meta page]] to learn how you can participate and [[m:Special:MyLanguage/Wiki Loves Ramadan 2026/Participating communities|sign up]] your community.
📷 ''Photo campaign on '' [[c:Special:MyLanguage/Commons:Wiki Loves Ramadan 2026|Wikimedia Commons]]
If you have questions about the project, please refer to the FAQs:
* [[m:Special:MyLanguage/Wiki Loves Ramadan/FAQ/|Meta-Wiki]]
* [[c:Special:MyLanguage/Commons:Wiki Loves Ramadan/FAQ|Wikimedia Commons]]
''Early registration for updates is now open via the '''[[m:Special:RegisterForEvent/2710|Event page]]'''''
''Stay connected and receive updates:''
* [https://t.me/WikiLovesRamadan Telegram channel]
* [https://lists.wikimedia.org/postorius/lists/wikilovesramadan.lists.wikimedia.org/ Mailing list]
We look forward to collaborating with you and your community.
'''The Wiki Loves Ramadan 2026 Organizing Team''' 16 janvier 2026 à 20:44 (CET)
<!-- Message envoyé par User:ZI Jony@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Non-Technical_Village_Pumps_distribution_list&oldid=29879549 -->
== <span lang="en" dir="ltr">Tech News: 2026-04</span> ==
<div lang="en" dir="ltr">
<section begin="technews-2026-W04"/><div class="plainlinks">
Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/04|Translations]] are available.
'''Updates for editors'''
* The tray shown on [[Special:Diff|Special:Diff]] in mobile view has been redesigned. It is now collapsed by default, and incorporates a link to undo the edit being viewed, making it easier for mobile editors and reviewers to take action while keeping the interface uncluttered. [https://phabricator.wikimedia.org/T402297]
* [[m:Special:GlobalWatchlist|The Global Watchlist]] lets you view your watchlists from multiple wikis on one page. The [[mw:Special:MyLanguage/Extension:GlobalWatchlist|extension]] continues to improve — it now automatically determines the text direction (ensuring correct display of sites with unusual domain names) and shows detailed descriptions for log actions. Later this week, a new permanent link for page creations and CSS classes for each entry element will be added. [https://phabricator.wikimedia.org/T412505][https://phabricator.wikimedia.org/T287929][https://phabricator.wikimedia.org/T262768][https://phabricator.wikimedia.org/T414135]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:32}} community-submitted {{PLURAL:32|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, the previously observed issue in Vector 2022, where anchor link targets were obscured by the sticky header, has now been addressed. [https://phabricator.wikimedia.org/T406114]
'''Updates for technical contributors'''
* As mentioned in the [[m:Special:MyLanguage/Tech/News/2025/44|October 2025 deprecation announcement]], MediaWiki Interfaces team will begin sunsetting all transform endpoints containing a trailing slash from the MediaWiki REST API the week of January 26. Changes are expected to roll out to all wikis on or before January 30th. All API users currently calling them are encouraged to transition to the non-trailing slash versions. Both endpoint variations can be found, compared, and tested using the [https://test.wikipedia.org/wiki/Special:RestSandbox REST Sandbox]. If you have questions or encounter any problems, please file a ticket in Phabricator to the [https://phabricator.wikimedia.org/project/view/6931/ #MW-Interfaces-Team board].
* Interactive reference documentation for the [[mw:Special:MyLanguage/Wikimedia REST API|Wikimedia REST API]] has moved. Requests to API docs previously hosted through [[mw:Special:MyLanguage/RESTBase|RESTBase]] (e.g.: <code dir=ltr>https://en.wikipedia.org/api/rest_v1/</code>) are now redirected to the [[w:en:Special:RestSandbox|REST Sandbox]].
* The [[mw:Special:MyLanguage/Wikidata Platform|WMF Wikidata Platform team]] (WDP) has published its [[d:Special:MyLanguage/Wikidata:Wikidata Platform team/Newsletter|January 2026 newsletter]]. It includes updates on the legacy full-graph endpoint decommissioning, the User-Agent policy change, the monthly Blazegraph migration office hours, and efforts to reduce regressions caused by the legacy endpoint shutdown. As a reminder, you can [[m:Special:MyLanguage/Global message delivery/Targets/WDP team updates|subscribe to the WDP newsletter]]!
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.12|MediaWiki]]
'''Meetings and events'''
* The [[mw:Wikimedia Hackathon Northwestern Europe 2026|Wikimedia Hackathon Northwestern Europe 2026]] will take place on 13-14 March 2026 in Arnhem, the Netherlands. Applications opened mid-December and will close soon or when capacity is reached. It's a two-day, technically oriented hackathon bringing together Wikimedians from the region. Hope to see you there!
'''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]] • [[m:Special:MyLanguage/Tech/News#contribute|Contribute]] • [[m:Special:MyLanguage/Tech/News/2026/04|Translate]] • [[m:Tech|Get help]] • [[m:Talk:Tech/News|Give feedback]] • [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].''
</div><section end="technews-2026-W04"/>
</div>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 19 janvier 2026 à 21:29 (CET)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=29943403 -->
== Révision annuelle du code universel de conduite et des lignes directrices de l'application ==
<section begin="announcement-content" />
Nous vous informons que la période de relecture annuelle du Code de conduite universel et des règles d'applications est actuellement ouverte. Vous pouvez faire vos commentaires sur les modifications que vous souhaitez apporter jusqu'au 9 février 2026. C'est la première d'une série d'étapes nécessaires pour la révision annuelle. Vous trouverez [[m:Special:MyLanguage/Universal Code of Conduct/Annual review/2026|d'autres informations et les discussions auxquelles participer sur la page UCoC de Meta]].
Le [[m:Special:MyLanguage/Universal Code of Conduct/Coordinating Committee|Comité de coordination du code universel de conduite]] (U4C — Universal Code of Conduct Coordinating Committee) est un groupe global dont le rôle est de fournir une implémentation équitable et cohérente de l'UCoC. Cette relecture annuelle a été envisagée et mise en place par l'U4C. Pour plus d'informations et les responsabilités de l'U4C, veuillez lire la [[m:Special:MyLanguage/Universal Code of Conduct/Coordinating Committee/Charter|Charte de l'U4C]].
Veuillez partager ces informations avec les autres membres concernés de votre communauté.
-- En coopération avec l'U4C, [[m:User:Keegan (WMF)|Keegan (WMF)]] ([[m:User talk:Keegan (WMF)|discussion]])<section end="announcement-content" />
19 janvier 2026 à 22:01 (CET)
<!-- Message envoyé par User:Keegan (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=29905753 -->
== Actualités techniques n° 2026-05 ==
<section begin="technews-2026-W05"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/05|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* La Fondation Wikimedia invite à donner des commentaires sur [[m:Special:MyLanguage/Product and Technology Advisory Council/Year1 Reflections and Proposed Way Forward 2026 Update|l’avenir proposé]] du [[:m:Special:MyLanguage/Product and Technology Advisory Council|Conseil consultatif des produits et technologies]] jusqu’au 28 février.
* Tous les utilisateurs disposant d'un compte enregistré peuvent désormais utiliser des clés d'accès pour la [[m:Special:MyLanguage/Help:Two-factor authentication|double authentification]] (2FA). Les clés d'accès sont un moyen simple de se connecter sans utiliser un second appareil. Elles vérifient l'identité de l'utilisateur à l'aide d'une empreinte digitale, d'une reconnaissance faciale ou d'un code PIN. Pour configurer une clé d'accès, configurez d'abord une méthode 2FA classique. Actuellement, pour se connecter avec une clé d'accès, les utilisateurs doivent également utiliser un mot de passe. Plus tard ce trimestre, la connexion sans mot de passe permettra aux utilisateurs de se connecter d'un simple clic avec une clé d'accès. Les utilisateurs disposant de droits avancés devront également avoir la 2FA activée. Cela fait partie du projet [[mw:Special:MyLanguage/Product Safety and Integrity/Account Security|Sécurité du compte]].
* Les contributeurs non enregistrés sur des IP bloquées ou des plages d'IP bloquées peuvent désormais interagir sur le wiki pour faire appel d'un blocage en créant un compte temporaire afin de contester un blocage sur la page de discussion de l'utilisateur, sauf si l'option « empêcher cet utilisateur de modifier sa propre page de discussion » est activée. Cela résout le problème des utilisateurs déconnectés incapables d'utiliser le processus de déblocage par défaut via la page de discussion de l'utilisateur. [https://phabricator.wikimedia.org/T398673]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:20|la tâche soumise|les {{formatnum:20}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:20||s}} la semaine dernière]]. Par exemple, la description des méthodes d'authentification à deux facteurs (2FA) sur la page de gestion a été mise à jour. Il est désormais plus clair et plus facile pour les utilisateurs à comprendre et à utiliser. [https://phabricator.wikimedia.org/T332385]
'''Actualités pour la contribution technique'''
* Une nouvelle variable AbuseFilter, <code>account_type</code>, a été ajoutée pour fournir un moyen fiable de déterminer le type de compte créé dans les actions <code>createaccount</code> et <code>autocreateaccount</code>. Dans le cadre de ce changement, la variable <code>accountname</code> a été renommée en <code>account_name</code>, et <code>accountname</code> est désormais obsolète. Les gestionnaires de filtres doivent mettre à jour tous les filtres qui utilisent des vérifications de type de compte codées en dur ou la variable obsolète. [https://phabricator.wikimedia.org/T414049]
* Les vignettes d'images demandées dans des tailles non standard, et en utilisant des méthodes non standard telles que les requêtes directes à <code dir=ltr><nowiki>upload.wikimedia.org/…</nowiki></code>, cesseront de fonctionner dans un proche avenir. Ce changement vise à prévenir les abus externes continus par des robots et des aspirateurs web. Certains utilisateurs ayant des CSS/JS personnalisés, les administrateurs d'interface qui peuvent corriger les gadgets et les thèmes locaux, ainsi que les auteurs d'outils, devront mettre à jour leur code pour utiliser des tailles de vignettes standard. [[phab:T414805|Des détails, des liens de recherche et des exemples de correction sont disponibles dans la tâche]].
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.13|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/05|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W05"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 26 janvier 2026 à 22:17 (CET)
<!-- Message envoyé par User:UOzurumba (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=29969530 -->
== <span lang="en" dir="ltr">Tech News: 2026-06</span> ==
<div lang="en" dir="ltr">
<section begin="technews-2026-W06"/><div class="plainlinks">
Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/06|Translations]] are available.
'''Updates for editors'''
* The "{{int:pageinfo-toolboxlink}}" feature, which gives validating information about a page ([{{fullurl:{{FULLPAGENAME}}|action=info}} example]), now automatically includes a table of contents. If there is a local [[{{ns:8}}:Pageinfo-header]] page created by individual users, it can now be removed. [https://phabricator.wikimedia.org/T363726]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:21}} community-submitted {{PLURAL:21|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, VisualEditor previously added bold or italic formatting inside link descriptions, making the wikicode complex. This has now been fixed. [https://phabricator.wikimedia.org/T409669]
'''Updates for technical contributors'''
* There was no XML dump on 20 January. Additionally, from now on, dumps will be generated once per month only. [https://phabricator.wikimedia.org/T414389]
* The MediaWiki Interfaces team removed support for all transform endpoints containing a trailing slash from the [https://www.mediawiki.org/wiki/Special:MyLanguage/API:REST%20API MediaWiki REST API]. All API users currently calling those endpoints are encouraged to transition to the non-trailing slash versions. If you have questions or encounter any problems, please file a ticket in phabricator to the [https://phabricator.wikimedia.org/project/view/6931/ #MW-Interfaces-Team board].
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.14|MediaWiki]]
'''Weekly highlight'''
* Users are reminded that the Wikimedia Foundation has shared some guiding questions for the July 2026–June 2027 Annual Plan on [[m:Special:MyLanguage/Wikimedia Foundation Annual Plan/2026-2027/Product & Technology OKRs|Meta]] and ''[[diffblog:2025/12/10/shaping-wikimedia-foundations-2026-2027-annual-goals-key-questions-for-the-wikimedia-movement/|Diff]]''. These focus on global trends, faster and healthier experimentation, better support for newcomers, strengthening editors and advanced users, improving collaboration across projects, and growing and retaining readership. Feedback and ideas are welcome on the [[m:Talk:Wikimedia Foundation Annual Plan/2026-2027|talk page]].
'''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]] • [[m:Special:MyLanguage/Tech/News#contribute|Contribute]] • [[m:Special:MyLanguage/Tech/News/2026/06|Translate]] • [[m:Tech|Get help]] • [[m:Talk:Tech/News|Give feedback]] • [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].''
</div><section end="technews-2026-W06"/>
</div>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 2 février 2026 à 18:43 (CET)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30000986 -->
== Actualités techniques n° 2026-07 ==
<section begin="technews-2026-W07"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/07|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* [[File:Maki-gift-15.svg|12px|link=|class=skin-invert|Concerne un souhait]] Les contributeurs connectés qui gèrent de grandes ou complexes listes de suivi peuvent désormais organiser et filtrer les pages surveillées de manière à améliorer leurs flux de travail grâce à la nouvelle fonctionnalité [[mw:Special:MyLanguage/Help:Watchlist labels|Étiquettes de liste de suivi]]. En ajoutant des étiquettes personnalisées (par exemple : pages que vous avez créées, pages surveillées pour vandalisme, ou pages de discussion), les utilisateurs peuvent identifier plus rapidement ce qui nécessite une attention, réduire la charge cognitive et répondre plus efficacement. Cela améliore l'utilisabilité de la liste de suivi, en particulier pour les éditeurs très actifs.
* Une nouvelle fonctionnalité disponible sur [[Special:Contributions|Special:Contributions]] montre [[mw:Special:MyLanguage/Trust and Safety Product/Temporary Accounts|des comptes temporaires]] qui sont probablement utilisés par la même personne, et rend ainsi le patrouillage moins chronophage. En vérifiant les contributions d'un compte temporaire, les utilisateurs ayant accès aux adresses IP des comptes temporaires peuvent désormais avoir une vue des contributions des comptes temporaires associés. La fonctionnalité recherche toutes les adresses IP associées à un compte temporaire donné pendant la période de conservation des données et affiche toutes les contributions de tous les comptes temporaires ayant utilisé ces adresses IP. [[mw:Special:MyLanguage/Trust and Safety Product/Temporary Accounts#February 2026: Improvements to the patroller tooling|Plus...]] [https://phabricator.wikimedia.org/T415674]
* Lorsque les éditeurs prévisualisent une modification de wikitexte, la boîte de rappel indiquant qu'ils ne voient qu'une prévisualisation (qui est affichée en haut) a désormais un fond gris/neutre au lieu d'un fond jaune/d'avertissement. Cela facilite la distinction entre les notes de prévisualisation et les avertissements réels (par exemple, les conflits de modification ou les cibles de redirection problématiques), qui seront désormais affichés dans des boîtes d'avertissement ou d'erreur séparées. [https://phabricator.wikimedia.org/T414742]
* La [[m:Special:GlobalWatchlist|Liste de suivi globale]] vous permet de consulter vos listes de suivi provenant de plusieurs wikis sur une seule page. L' [[mw:Special:MyLanguage/Extension:GlobalWatchlist|extension]] continue de s'améliorer — elle prend désormais en charge correctement plus d'un site Wikibase, par exemple à la fois [[d:|Wikidata]] et [[testwikidata:|testwikidata]]. De plus, des problèmes concernant la direction du texte ont été résolus pour les utilisateurs qui préfèrent Wikidata ou d'autres sites Wikibase dans des langues de droite à gauche (RTL). [https://phabricator.wikimedia.org/T415440][https://phabricator.wikimedia.org/T415458]
* <span lang="en" dir="ltr" class="mw-content-ltr">The automatic "magic links" for ISBN, RFC, and PMID numbers have been [[mw:Special:MyLanguage/Help:Magic links|deprecated in wikitext since 2021]] due to inflexibility and difficulties with localization. Several wikis have successfully replaced RFC and PMID magic links with equivalent external links, but a template was often required to replace the functionality of the ISBN magic link. There is now a new [[mw:Special:MyLanguage/Help:Magic words#isbn|built-in parser function]] <code dir=ltr><nowiki>{{#isbn}}</nowiki></code> available to replace the basic functionality of the ISBN magic link. This makes it easier for wikis who wish to migrate off of the deprecated magic link functionality to do so.</span> [https://phabricator.wikimedia.org/T145604]
* Deux nouveaux wikis ont été créés :
** un {{int:project-localized-name-group-wikipedia}} dans [[d:Q35401|Jju]] ([[w:kaj:|<code>w:kaj:</code>]]) [https://phabricator.wikimedia.org/T413283]
** un {{int:project-localized-name-group-wikipedia}} dans [[d:Q1186896|Nawat]] ([[w:ppl:|<code>w:ppl:</code>]]) [https://phabricator.wikimedia.org/T413273]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:23|la tâche soumise|les {{formatnum:23}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:23||s}} la semaine dernière]].
'''Actualités pour la contribution technique'''
* Un nouveau groupe d'utilisateurs global a été créé : [[{{int:grouppage-local-bot}}|{{int:group-local-bot}}]]. Il sera utilisé en interne par le logiciel pour permettre aux robots communautaires de contourner les limites de débit appliquées aux [[w:en:Web_scraping|web scrapers]] abusifs. Les comptes approuvés en tant que robots sur au moins un wiki Wikimedia seront automatiquement ajoutés à ce groupe. Cela ne changera pas les autorisations dont dispose le robot. [https://phabricator.wikimedia.org/T415588]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.15|MediaWiki]]
'''Rencontres et évènements'''
* La [[mw:Special:MyLanguage/MediaWiki Users and Developers Conference Spring 2026|Conférence des utilisateurs et des développeurs de MediaWiki, Printemps 2026]] se tiendra du 25 au 27 mars à Salt Lake City, États-Unis. Cet événement est organisé par et pour la communauté MediaWiki de tiers. Vous pouvez proposer des sessions et vous inscrire pour y assister. [https://lists.wikimedia.org/hyperkitty/list/wikitech-l@lists.wikimedia.org/thread/AZBWVI46SDEB65PGR5J6E4TYOQQEZXM7/]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/07|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W07"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 10 février 2026 à 00:30 (CET)
<!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30026671 -->
== Actualités techniques n° 2026-08 ==
<section begin="technews-2026-W08"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/08|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* <span class="mw-translate-fuzzy">L'[[mw:Special:MyLanguage/Wikimedia Site Reliability Engineering|équipe SRE]] va procéder au nettoyage d'[[m:Special:MyLanguage/Etherpad|Etherpad]], l'éditeur web open source de documents collaboratifs en temps réel. Tous les blocs-notes seront définitivement supprimés après le 30 avril 2026 – si des projets de migration sont encore en cours à cette date, l'équipe pourra réexaminer la date au cas par cas. Veuillez effectuer des sauvegardes locales de tout contenu que vous souhaitez conserver, car les données supprimées ne pourront pas être récupérées. Ce nettoyage permet de réduire la taille de la base de données et l'empreinte de l'infrastructure. Etherpad continuera de prendre en charge la collaboration en temps réel, mais le stockage à long terme n'est plus assuré. D'autres nettoyages pourront avoir lieu ultérieurement sans préavis.</span> [https://phabricator.wikimedia.org/T415237]
'''Actualités pour la contribution'''
* L'équipe de Recherche d'Informations lancera une [[mw:Special:MyLanguage/Readers/Information Retrieval/Phase 1|expérimentation sur l'application mobile Android]], afin de tester des fonctionnalités de recherche hybrides capables de gérer à la fois les requêtes sémantiques et par mots-clés. L'amélioration de la recherche sur la plateforme permettra aux lecteurs de trouver plus facilement ce qu'ils cherchent, directement sur Wikipédia. L'expérimentation sera d'abord lancée sur Wikipédia en grec fin février, puis sur les versions anglaise, française et portugaise en mars. [https://diff.wikimedia.org/2026/01/08/semantic-search-making-it-easier-to-find-the-information-readers-want/ En savoir plus] sur le blog ''Diff''. [https://www.mediawiki.org/wiki/Readers/Information_Retrieval]
* L'équipe « Croissance des lecteurs » mènera [[mw:Special:MyLanguage/Readers/Reader Growth/WE3.10.2 Mobile Table of Contents|une expérience]] auprès des utilisateurs de la version mobile du site web qui ajoute une table des matières et développe automatiquement toutes les sections des articles, afin de mieux comprendre les problèmes de navigation qu'ils rencontrent. Le test sera disponible sur les versions arabe, chinoise, anglaise, française, indonésienne et vietnamienne de Wikipedia.
* Auparavant, les notifications ([[{{ns:8}}:Sitenotice]] et [[{{ns:8}}:Anonnotice]]) du site ne s'affichaient que sur la version ordinateur. Maintenant, elles s'afficheront désormais sur toutes les plateformes. Les utilisateurs mobiles verront ces notifications. Les administrateurs du site doivent être prêts à tester et à corriger les notifications sur les appareils mobiles afin d'éviter toute interférence avec les articles. Pour désactiver ces notifications, les administrateurs d'interface peuvent ajouter <code dir="ltr">#siteNotice { display: none; }</code> à [[{{ns:8}}:Minerva.css]]. [https://phabricator.wikimedia.org/T138572][https://phabricator.wikimedia.org/T416644]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:19|la tâche soumise|les {{formatnum:19}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:19||s}} la semaine dernière]]. Par exemple, un problème concernant la section ''[[Special:RecentChanges|Spécial:Modifications récentes]]'' a été résolu. Auparavant, cliquer sur « Masquer » dans les filtres actifs entraînait la disparition du bouton « Afficher les nouvelles modifications depuis… », alors qu'il aurait dû rester visible. Ce bouton fonctionne désormais correctement. [https://phabricator.wikimedia.org/T406339]
'''Actualités pour la contribution technique'''
* Une nouvelle documentation est désormais disponible pour aider les rédacteurs à déboguer les fonctionnalités de recherche interne. Elle facilite le dépannage lorsque des pages n'apparaissent pas dans les résultats, lorsque le classement semble inattendu et lorsqu'il est nécessaire d'inspecter le contenu indexé, ce qui permet de mieux comprendre et d'analyser le comportement de la recherche. [[mw:Help:CirrusSearch/Debug|En savoir plus]]. [https://phabricator.wikimedia.org/T411169]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.16|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/08|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W08"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 16 février 2026 à 20:17 (CET)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30086330 -->
== <span lang="en" dir="ltr">Tech News: 2026-09</span> ==
<div lang="en" dir="ltr">
<section begin="technews-2026-W09"/><div class="plainlinks">
Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/09|Translations]] are available.
'''Weekly highlight'''
* [[mw:Special:MyLanguage/Edit check/Reference Check|Reference Check]] has been deployed to English Wikipedia, completing its rollout across all Wikipedias. The feature prompts newcomers to add a citation before publishing new content, helping reduce common citation-related reverts and improve verifiability. In A/B testing, the impact was substantial: newcomers shown Reference Check were approximately 2.2 times more likely to include a reference on desktop and about 17.5 times more likely on mobile web. [https://analytics.wikimedia.org/published/reports/editing/reference_check_ab_test_report_final_2025.html]
'''Updates for editors'''
* The [[mw:Special:MyLanguage/Extension:InterwikiSorting|InterwikiSorting extension]], which allowed for the [[m:Special:MyLanguage/Interwiki sorting order|sorting of interwiki links]], has been undeployed from Wikipedia. As a result, editors who had enabled interwiki link sorting in non-compact mode (full list format) will now see links reordered. The links moving forward will be listed in the alphabetical order of language code. [https://phabricator.wikimedia.org/T253764]
* Later this week, people who are editing a page-section using the mobile visual editor, will notice a new "Edit full page" button. When tapped, you will be able to edit the entire article. This helps when the change you want to make is outside the section you initially opened. [https://phabricator.wikimedia.org/T387175][https://phabricator.wikimedia.org/T409112]
* [[mw:Special:MyLanguage/Readers/Reader Experience|The Reader Experience team]] is inviting editors to assess whether dark mode should still be considered "beta" on their wiki, based on their experience of how well it functions on desktop and mobile. If the feature is deemed mature, editors can update the interface messages in <code dir=ltr>MediaWiki:skin-theme-description</code> and <code dir=ltr>MediaWiki:Vector-night-mode-beta-tag</code> to indicate that dark mode is ready and no longer considered beta.
* The improved [[mw:Wikimedia_Apps/Team/iOS/Activity_Tab|Activity tab]] which displays user-insights is now available to all users of the Wikipedia iOS app (version 7.9.0 and later). Following earlier A/B testing that showed higher account creation among users with access to the feature, it has been rolled out to 100% of users along with some updates. The Activity tab now shows your edited articles in the timeline, offers editing impact insights like contribution counts and article view trends, and customization options to improve in-app experience for users.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:21}} community-submitted {{PLURAL:21|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, a bug that prevented [[mw:Special:MyLanguage/Extension:DiscussionTools|DiscussionTools]] from working on mobile has now been fixed, restoring full functionality. [https://phabricator.wikimedia.org/T415303]
'''Updates for technical contributors'''
* The [[m:Special:GlobalWatchlist|Global Watchlist]] lets you view your watchlists from multiple wikis on one page. The [[mw:Special:MyLanguage/Extension:GlobalWatchlist|extension]] that makes this possible continues to improve. The latest upgrade is the inclusion of a [[mw:Extension:GlobalWatchlist#hook|new hook]], <code dir=ltr>ext.globalwatchlist.rebuild</code>, which fires after each watchlist rebuild. This allows you to run gadgets and user scripts for the Special page. [https://phabricator.wikimedia.org/T275159]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.17|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]] • [[m:Special:MyLanguage/Tech/News#contribute|Contribute]] • [[m:Special:MyLanguage/Tech/News/2026/09|Translate]] • [[m:Tech|Get help]] • [[m:Talk:Tech/News|Give feedback]] • [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].''
</div><section end="technews-2026-W09"/>
</div>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 23 février 2026 à 20:03 (CET)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30119102 -->
== Actualités techniques n° 2026-10 ==
<section begin="technews-2026-W10"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/10|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* Le [[m:Special:MyLanguage/Wikipedia 25/Easter egg experiments|mode Anniversaire]] Wikipedia 25 est maintenant disponible sur Wikipédia en français, anglais, betawi, breton, chinois, espagnol, gorontalo, indonésien, italien, luxembourgeois, madurais, néerlandais, sicilien, tchèque, thaï et vietnamien ! Cette campagne à temps limitée célèbre 25 ans de Wikipédia avec une mascotte : « Baby Globe », disponible sous la forme d'un réglage. Lorsque ce réglage est activé, Baby Globe est montrée sur [[m:Special:MyLanguage/Wikipedia 25/Easter egg experiments/article configuration|environ 2 500 articles]], attendant d'être découverte par des lecteurs. Chaque communauté peut choisir d'activer le mode Anniversaire par consensus et en demandant à un administrateur de le rendre disponible et de le personaliser via une [[m:Special:MyLanguage/Wikipedia 25/Easter egg experiments#Community Configuration Demo|configuration]] sur le wiki local.
'''Actualités pour la contribution'''
* Le [[:m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing|sous-référencement]], une nouvelle fonctionalité pour réutiliser des références avec des détails différents est maintenant disponible sur Wikipédia en suédois, polonais et [[:phab:T418209|quelques autres]]. Vous pouvez [[:m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing#test|essayer la fonctionalité]] sur ces projets ou sur testwiki et [https://en.wikipedia.beta.wmcloud.org/wiki/Sub-referencing betawiki]. Les retours des premiers essais sur Wikipédia en allemand ont été [[:m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing/Learnings|publiés dans un rapport]]. Contactez l'équipe de Wikimédia Allemagne si vous êtes [[:m:Talk:WMDE Technical Wishes/Sub-referencing#Pilot wikis|intéressés pour devenir un wiki pilote]].
* La [[mw:Special:MyLanguage/Help:Edit check#Paste check|vérification du collage clavier]] sera disponible sur tous les Wikipédias cette semaine. Cette fonctionalité avertit les nouveaux contributeurs qui collent du texte qu'ils n'ont probablement pas écrit de vérifier si laisser celui-ci risque de causer une violation du droit d'auteur. La vérification du collage clavier [[mw:Special:MyLanguage/Edit check/Tags|marque]] toutes les modifications où l'avertissement a été montré pour permettre leur vérification. Les administrateurs locaux peuvent configurer les différents aspects de cette fonctionalité à travers [[{{#special:EditChecks}}]]. Des [[mw:Special:MyLanguage/Edit check/Paste Check#A/B Experiment|études]] sur 22 wikis ont montré que cette vérification permet une réduction de 18% des annulations comparé au groupe de contrôle. Les traducteurs peuvent [https://translatewiki.net/w/i.php?title=Special%3ATranslate&group=ext-visualeditor-ve-mw-editcheck&filter=&optional=1&action=translate aider à traduire] cette fonctionalité.
* <span lang="en" dir="ltr" class="mw-content-ltr">The [[mw:Special:MyLanguage/Readers/Reader Experience|Reader Experience team]] will be standardizing the user menu in the top right for all mobile users so that it is closer to the desktop experience. Currently this user menu is only visible to users with Advanced Mobile Controls (AMC) turned on. The only change is that a couple buttons previously in the left-side menu will move to the top right for users who do not have AMC turned on. This change is expected to go out March 9 and seeks to improve the user interface.</span> [https://phabricator.wikimedia.org/T413912]
* À partir de la semaine du 2 mars, les emails envoyés lorsqu'une adresse email a été ajoutée, supprimée ou changée pour un compte changera pour adopter un formattage HTML beaucoup plus agréable et plus clair que le texte brut précédent. [https://phabricator.wikimedia.org/T410807]
* Les notifications sont actuellement limitées à 2 000 entrées historiques par utilisateur et remontent à 2013 lorsque la fonctionnalité a été publiée. Le système va être modifié pour ne stocker que les notifications des 5 dernières années, mais jusqu'à 10 000 d'entre elles. Cela contribuera à la santé à long terme des infrastructures et à empêcher que les notifications plus récentes disparaissent trop tôt. [https://phabricator.wikimedia.org/T383948]
* <span lang="en" dir="ltr" class="mw-content-ltr">The [[m:Special:GlobalWatchlist|Global Watchlist]] which lets you view your watchlists from multiple wikis on a single page continues to see improvements. The latest update improves label usage experience. The [[mw:Special:MyLanguage/Extension:GlobalWatchlist|extension]] now allows activating the [[mw:Special:MyLanguage/Manual:Language#Fallback languages|language fallback system]] for Wikidata items without labels in the viewed language, and showing those labels in the user’s preferred Wikidata language if no <code dir=ltr>uselang=</code> URL parameter is provided.</span> [https://phabricator.wikimedia.org/T373686][https://phabricator.wikimedia.org/T416111]
* L'équipe Wikipédia Android a commencé un test beta de la [[mw:Special:MyLanguage/Readers/Information Retrieval/Phase 1|recherche hybride]] sur Wikipédia en grec. Cette recherche hybride supporte les requêtes sémantique et par mot clés, permettant aux utilisateurs de trouver ce qu'ils cherchent plus facilement.
* Pour des raisons de sécurité, les membres de certains groupes sont [[m:Special:MyLanguage/Mandatory two-factor authentication for users with some extended rights|forcés d'avoir la double authentification]] (A2F) d'activée. Actuellement, l'A2F n'est nécessaire que pour utiliser les droits du groupe, et non pour en faire partie. Vu que ce système admet certaines failles, il sera [[phab:T418580|changé graduellement en mars]]. Les membres de ces groupes ne pourront plus désactiver la dernière méthose d'A2F sur leur compte, et il sera impossible d'ajouter des utilisateurs sans A2F à ces groupes. Il sera toujours possible de rajouter d'autres méthodes d'authentification et d'en enlever, tant qu'une est toujours activée. Dans la seconde moitié de mars, les utilisateurs sans A2F seront retirés de ces groupes. Cela s'applique aux administrateurs CentralNotice, aux vérificateurs d'utilisateurs, aux administrateurs d'interface, aux masqueurs, aux staff de Wikidata et Wikifonctions ainsi qu'aux bureaux IT et Confiance et sécurité de la WMF. Rien ne changera pour les autres utilisateurs. Voir la tâche liée pour le calendrier de déploiement. [https://phabricator.wikimedia.org/T418580]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:27|la tâche soumise|les {{formatnum:27}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:27||s}} la semaine dernière]]. Par exemple, le problème empêchant les utilisateurs de créer une instance dans [https://www.wikibase.cloud/ Wikibase.cloud] a maintenant été résolu. [https://phabricator.wikimedia.org/T416807]
'''Actualités pour la contribution technique'''
* <span lang="en" dir="ltr" class="mw-content-ltr">To help ensure [[mw:Special:MyLanguage/MediaWiki Product Insights/Responsible Reuse|fair use of infrastructure]], over the next month the Wikimedia Foundation will implement global API rate limits across our APIs. In early March, stricter limits will be applied to unidentified requests from outside Toolforge/WMCS and API requests that are made from web browsers. In April, higher limits will be applied to identified traffic. These limits are intentionally set as high as possible to minimise impact on the community. Bots running in Toolforge/WMCS or with the bot user right on any wiki should not be affected for now. However, all developers are advised to follow updated best practices. For more information, see [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits|Wikimedia APIs/Rate limits]].</span>
* <span lang="en" dir="ltr" class="mw-content-ltr">The Wikidata Query Service Linked Data Fragment (LDF) endpoint will be decommissioned in February. This endpoint served limited traffic, which was successfully migrated to other data access methods that were better suited to support existing use cases. The hardware used to support the LDF endpoint will be reallocated to support the ongoing backend migration efforts.</span> [https://phabricator.wikimedia.org/T415696]
* Le nouvel analyseur syntaxique Parsoid [[mw:Special:MyLanguage/Parsoid/Parser Unification/Updates|continue d'être déployés sur plus de wikis]], améliorant la pérennité de la platforme et rendant plus facile l'ajout de nouvelles fonctionalités de lecture et de modification. Parsoid est maintenant l'analyseur par défaut sur 488 wikis de la WMF (268 Wikipédias), couvrant plus de 10% de toutes les lectures de pages Wikipédia.
* Le processus et les critères pour [[Special:MyLanguage/Wikimedia Enterprise#Access|demander un accès exceptionnel]] au flux à fort volume de l'API ''Wikimédia Entreprise'' (sans coût pour des utilisations en rapport à notre mission) [[m:Talk:Wikimedia Enterprise#Exceptional access criteria|ont maintenant été publiés]]. Notre but est de donner une documentation plus claire et plus complète aux utilisateurs.
* [https://techblog.wikimedia.org/ Le blog Tech], dédié à la communité technique de Wikimédia [https://techblog.wikimedia.org/2026/02/24/a-tech-blog-diff/ va migrer] vers [[diffblog:|Diff]], le blog pour les nouvelles et événements de la communauté. La migration devrait être terminée en Avril 2026, après quoi les nouveaux posts seront acceptés pour être publiés. Les lecteurs pourront lire les posts - anciens ou nouveaux - sur https://diff.wikimedia.org/.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.18|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/10|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W10"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 2 mars 2026 à 18:51 (CET)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30137798 -->
== <span lang="en" dir="ltr">Tech News: 2026-11</span> ==
<div lang="en" dir="ltr">
<section begin="technews-2026-W11"/><div class="plainlinks">
Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/11|Translations]] are available.
'''Weekly highlight'''
* [[m:Special:MyLanguage/Tech/Server switch|All wikis will be read-only]] for a few minutes on Wednesday, 25 March 2026 at [https://zonestamp.toolforge.org/1774450800 15:00 UTC]. This is for the datacenter server switchover backup tests, [[wikitech:Deployments/Yearly calendar|which happen twice a year]]. During the switchover, all Wikimedia website traffic is shifted from one primary data center to the backup data center to test availability and prevent service disruption even in emergencies.
* Last week, all wikis had 2 hours of read-only time, and extended unavailability for user-scripts and gadgets. This was due to a security incident which has since been resolved. Work is ongoing to prevent re-occurrences. For current information please see the [[m:Steward's noticeboard#Statement on Meta about today's user script security incident|post on the Stewards' noticeboard]] ([[m:Special:MyLanguage/Wikimedia Foundation/Product and Technology/Product Safety and Integrity/March 2026 User Script Incident|translations]]).
'''Updates for editors'''
* Users facing multiple blocks on mobile will now see the reasons for each block separately, instead of a generic message. This helps them understand why they are blocked and what steps they can take to resolve the issue. For example, users affected for using common VPNs (such as [[Special:MyLanguage/Apple iCloud Private Relay|iCloud Private Relay]]) will receive clearer guidance on what they need to do to start editing again. [https://phabricator.wikimedia.org/T357118]
* Later this week, [[mw:Special:MyLanguage/VisualEditor/Suggestion Mode|Suggestion Mode]] will become available as a beta feature within the visual editor at all Wikipedias. This feature proactively suggests various types of actions that people can consider taking to improve Wikipedia articles, and learn about related guidelines. The feature is locally configurable, and can also be locally expanded with custom Suggestions. Current settings can be seen at [[Special:EditChecks]] and there are [[mw:Special:MyLanguage/Help:Suggestion mode#For administrators %E2%80%93 local customization|instructions for how administrators can customize]] the links to point to local guidelines. The feature is connected to [[mw:Special:MyLanguage/Help:Edit check|Edit check]] which suggests improvements while someone is writing new content. In the future, the Editing team plans to evaluate the feature's impact with newcomers through a controlled experiment. [https://phabricator.wikimedia.org/T404600]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:23}} community-submitted {{PLURAL:23|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, the issue where the cursor became misaligned during the use of CodeMirror’s syntax highlighting, which makes wikitext and code easier to read, has now been fixed. This problem specifically affected users who defined a font rule in a custom stylesheet while creating a new topic with DiscussionTools. [https://phabricator.wikimedia.org/T418793]
'''Updates for technical contributors'''
* API rate limiting update: To help ensure [[mw:Special:MyLanguage/MediaWiki Product Insights/Responsible Reuse|fair use of infrastructure]], global API rate limits will be applied this week to requests without a compliant User-Agent that originate from outside Toolforge/WMCS and to unauthenticated requests made from web browsers. Higher limits will be applied to identified traffic in April. Bots running in Toolforge/WMCS or with the bot user right on any wiki should not be affected for now. However, all developers are advised to follow updated best practices. For more information, see [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits|Wikimedia APIs/Rate limits]].
* The new GraphQL API has been released. The API was developed as a flexible alternative to select features of the Wikidata Query Service (WDQS), to improve developer experience and foster adaptability, and efficient data access. Try it out and [[d:Wikidata:Wikibase GraphQL#Feedback and development|give feedback]]. You can also [https://greatquestion.co/wikimediadeutschland/GraphQLAPI/apply sign up for usability tests].
* The [[m:Special:MyLanguage/Product and Technology Advisory Council/Unsupported Tools Working Group|PTAC Unsupported Tools Working Group]] continued improvements to [[commons:Special:MyLanguage/Commons:Video2commons#|Video2Commons]] in February, with fixes addressing authentication errors, large-file handling, task queue visibility, and clearer upload behavior. Work is still ongoing in some areas, including changes related to deprecated server-side uploads. Read [[m:Special:MyLanguage/Product and Technology Advisory Council/Unsupported Tools Working Group#February 2026|this update]] to learn more.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.19|MediaWiki]]
'''In depth'''
* The Article Guidance team invites experienced Wikipedia editors from selected [[mw:Special:MyLanguage/Article guidance/Pilot wikis and collaborators#Collaborators|pilot wikis]] and interested contributors from other Wikipedias to fill out this questionnaire which is available in [https://docs.google.com/forms/d/e/1FAIpQLSfmLeVWnxmsCbPoI_UF2jyRcn73WRGWCVPHzerXb4Cz97X_Ag/viewform English], [https://docs.google.com/forms/d/e/1FAIpQLSd6rzr4XXQw8r4024fE3geTPFe13M_6w7Mitj-YJi0sOlWTAw/viewform?usp=header Arabic], [https://docs.google.com/forms/d/e/1FAIpQLSdok3-RfB18lcugYTUMGkpwmqG_8p760Wv4dCXitOXOszjUDw/viewform?usp=header Bengali], [https://docs.google.com/forms/d/e/1FAIpQLSfjTfYp4jEo0akA4B1e-Nfg3QZPCudUjhJzHzzDi6AHyAaMGA/viewform?usp=header Japanese], [https://docs.google.com/forms/d/e/1FAIpQLScteVoI29Aue4xc72dekk-6RYtvmMgQxzMI900UOawrFrSTWg/viewform?usp=header Portuguese], [https://docs.google.com/forms/d/e/1FAIpQLSetdxnYwL3ub2vqA7awCg5hJZPMIYcDPaiTe12rY9h0GYnVlw/viewform?usp=header Persian], and [https://docs.google.com/forms/d/e/1FAIpQLScNvfJF-Ot-4pzA4qAN771_0QDJ4Li19YcUsaTgSKW8Nc7U_Q/viewform?usp=header Turkish]. Your answers will help the team customize guidance for less experienced editors and help them learn community policies and practices while creating an article. Learn more [[mw:Special:MyLanguage/Article guidance|on the project page]].
'''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]] • [[m:Special:MyLanguage/Tech/News#contribute|Contribute]] • [[m:Special:MyLanguage/Tech/News/2026/11|Translate]] • [[m:Tech|Get help]] • [[m:Talk:Tech/News|Give feedback]] • [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].''
</div><section end="technews-2026-W11"/>
</div>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 9 mars 2026 à 19:52 (CET)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30213008 -->
== <span lang="en" dir="ltr">Tech News: 2026-12</span> ==
<div lang="en" dir="ltr">
<section begin="technews-2026-W12"/><div class="plainlinks">
Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/12|Translations]] are available.
'''Updates for editors'''
* The [[mw:Special:MyLanguage/Help:Extension:CodeMirror|{{int:codemirror-beta-feature-title}}]] beta feature, also known as [[mw:Special:MyLanguage/Extension:CodeMirror|CodeMirror 6]], has been used for wikitext syntax highlighting since November 2024. It will be promoted out of beta by May 2026 in order to bring improvements and new [[mw:Special:MyLanguage/Help:Extension:CodeMirror#Features|features]] to all editors who use the standard syntax highlighter. If you have any questions or concerns about promoting the feature out of beta, [[mw:Special:MyLanguage/Help talk:Extension:CodeMirror|please share]]. [https://phabricator.wikimedia.org/T259059]
* Some changes to local user groups are performed by stewards on Meta-Wiki and logged there only. Now, interwiki rights changes will be logged both on Meta-Wiki and the wiki of the target user to make it easier to access a full record of user's rights changes on a local wiki. Past log entries for such changes will be backfilled in the coming weeks. [https://phabricator.wikimedia.org/T6055]
* On wikis using [[m:Special:MyLanguage/Flagged Revisions|Flagged Revisions]], the number of pending changes shown on [[{{#Special:PendingChanges}}]] previously counted pages which were no longer pending review, because they have been removed from the system without being reviewed, e.g. due to being deleted, moved to a different namespace, or due to wiki configuration changes. The count will be correct now. On some wikis the number shown will be much smaller than before. There should be no change to the list of pages itself. [https://phabricator.wikimedia.org/T413016]
* Wikifunctions composition language has been rewritten, resulting in a new version of the language. This change aims to increase service stability by reducing the orchestrator's memory consumption. This rewrite also enables substantial latency reduction, code simplification, and better abstractions, which will open the door to later feature additions. Read more about [[f:Special:MyLanguage/Wikifunctions:Status updates/2026-03-11|the changes]].
* Users can now sort search results alphabetically by page title. The update gives an additional option to finding pages more easily and quickly. Previously, results could be sorted by Edit date, Creation date, or Relevance. To use the new option, open 'Advanced Search' on the search results page and select 'Alphabetically' under 'Sorting Order'. [https://phabricator.wikimedia.org/T403775]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:28}} community-submitted {{PLURAL:28|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, the bug that prevented UploadWizard on Wikimedia Commons from importing files from Flickr has now been fixed. [https://phabricator.wikimedia.org/T419263]
'''Updates for technical contributors'''
* A new special page, [[{{#special:LintTemplateErrors}}]], has been created to list transcluded pages that are flagged as containing lint errors to help users discover them easily. The list is sorted by the number of transclusions with errors. For example: [[{{#special:LintTemplateErrors}}/night-mode-unaware-background-color]]. [https://phabricator.wikimedia.org/T170874]
* Users of the [[mw:Special:MyLanguage/Help:Extension:CodeMirror|{{int:codemirror-beta-feature-title}}]] beta feature have been using [[mw:Special:MyLanguage/Extension:CodeMirror|CodeMirror]] instead of [[mw:Special:MyLanguage/Extension:CodeEditor|CodeEditor]] for syntax highlighting when editing JavaScript, CSS, JSON, Vue and Lua content pages, for some time now. Along with promoting CodeMirror 6 out of beta, the plan is to replace CodeEditor as the standard editor for these content models by May 2026. [[mw:Special:MyLanguage/Help talk:Extension:CodeMirror|Feedback or concerns are welcome]]. [https://phabricator.wikimedia.org/T419332]
* The [[mw:Special:MyLanguage/Extension:CodeMirror|CodeMirror]] JavaScript modules will soon be upgraded to CodeMirror 6. Leading up to the upgrade, loading the <code dir=ltr>ext.CodeMirror</code> or <code dir=ltr>ext.CodeMirror.lib</code> modules from gadgets and user scripts was deprecated in July 2025. The use of the <code dir=ltr>ext.CodeMirror.switch</code> hook was also deprecated in March 2025. Contributors can now make their scripts or gadgets compatible with CodeMirror 6. See the [[mw:Special:MyLanguage/Extension:CodeMirror#Gadgets and user scripts|migration guide]] for more information. [https://phabricator.wikimedia.org/T373720]
* The MediaWiki Interfaces team is expanding coverage of REST API module definitions to include [[mw:Special:MyLanguage/API:REST API/Extensions|extension APIs]]. REST API modules are groups of related endpoints that can be independently managed and versioned. Modules now exist for [https://phabricator.wikimedia.org/T414470 GrowthExperiments] and [https://phabricator.wikimedia.org/T419053 Wikifunctions] APIs. As we migrate extension APIs to this structure, documentation will move out of the main MediaWiki OpenAPI spec and REST Sandbox view, and will instead be accessible via module-specific options in the dropdown on the [https://test.wikipedia.org/wiki/Special:RestSandbox REST Sandbox] (i.e., [[{{#Special:RestSandbox}}]], available on all wiki projects).
* The [[mw:Special:MyLanguage/Extension:Scribunto|Scribunto]] extension provides different pieces of information about the wiki where the module is being used via the [[mw:Special:MyLanguage/Extension:Scribunto/Lua reference manual|mw.site]] library. Starting last week, the library also provides a [[mw:Special:MyLanguage/Extension:Scribunto/Lua reference manual#mw.site.wikiId|way]] of accessing the [[mw:Special:MyLanguage/Manual:Wiki ID|wiki ID]] that can be used to facilitate cross-wiki module maintenance. [https://phabricator.wikimedia.org/T146616]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.20|MediaWiki]]
'''In depth'''
* The [[m:Special:MyLanguage/Coolest Tool Award|2026 Coolest Tool Award]] celebrating outstanding community tools, is now open for nominations! Nominate your favorite tool using the [https://wikimediafoundation.limesurvey.net/435684?lang=en nomination survey] form by 23 March 2026. For more information on privacy and data handling, please see the [[foundation:Special:MyLanguage/Legal:Coolest_Tool_Award_2026_Survey_Privacy_Statement|survey privacy statement]].
'''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]] • [[m:Special:MyLanguage/Tech/News#contribute|Contribute]] • [[m:Special:MyLanguage/Tech/News/2026/12|Translate]] • [[m:Tech|Get help]] • [[m:Talk:Tech/News|Give feedback]] • [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].''
</div><section end="technews-2026-W12"/>
</div>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 16 mars 2026 à 20:35 (CET)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30260505 -->
== <span lang="en" dir="ltr">Upcoming deployment of CampaignEvents extension to Wikibooks</span> ==
<div lang="en" dir="ltr">
<section begin="message"/>
Hello everyone,
We are writing to inform you that the [[mw:Help:Extension:CampaignEvents|CampaignEvents extension]] will be deployed to all Wikibooks projects during the week of '''23 March 2026'''.
This follows last year’s broader rollout across Wikimedia projects. We realized that Wikibooks was not included at the time, and we’re now addressing that to ensure consistency across all communities.
The CampaignEvents extension provides tools to support event and campaign organization on-wiki, including features like on-wiki event registration and collaboration lists(global event list).
We welcome any questions, feedback, or concerns you may have. We are also happy to support anyone interested in trying out the tools.
''Apologies if this message is not in your preferred language. If you’re able to help translate it for your community, please feel free to do so.''
<section end="message"/>
</div>
<bdi lang="en" dir="ltr">[[User:Udehb-WMF|Udehb-WMF]] ([[User talk:Udehb-WMF|discussion]]) 19 mars 2026 à 19:22 (CET)</bdi>
<!-- Message envoyé par User:Udehb-WMF@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=User:Udehb-WMF/sandbox/MM_target&oldid=30284073 -->
== <span lang="en" dir="ltr">Tech News: 2026-13</span> ==
<div lang="en" dir="ltr">
<section begin="technews-2026-W13"/><div class="plainlinks">
Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/13|Translations]] are available.
'''Weekly highlight'''
* Wikimedia site users can now log in without a password using passkeys. This is a secure method supported by fingerprint, facial recognition, or PIN. With this change, all users who opt for passwordless login will find it easier, faster, and more secure to log in to their accounts using any device. The new passkey login option currently appears as an autofill suggestion in the username field. An additional [[phab:T417120|"Log in with passkey" button]] will soon be available for users who have already registered a passkey. This update will improve security and user experience. The [[c:File:Passwordless_login_screencast.webm|screen recording]] demonstrates the passwordless login process step by step.
* [[m:Special:MyLanguage/Tech/Server switch|All wikis will be read-only]] for a few minutes on Wednesday, 25 March 2026 at [https://zonestamp.toolforge.org/1774450800 15:00 UTC]. This is for the datacenter server switchover backup tests, [[wikitech:Deployments/Yearly calendar|which happen twice a year]]. During the switchover, all Wikimedia website traffic is shifted from one primary data center to the backup data center to test availability and prevent service disruption even in emergencies.
'''Updates for editors'''
* Wikimedia site users can now export their notifications older than 5 years using a [[toolforge:echo-chamber|new Toolforge tool]]. This will ensure that users retain their important notifications and avoid them being lost based on the planned change to delete notifications older than 5 years, as previously announced. [https://phabricator.wikimedia.org/T383948]
* Wikipedia editors in Indonesian, Thai, Turkish, and Simple English now have access to Special:PersonalDashboard. This is an [[mw:Special:MyLanguage/Moderator Tools/Dashboard|early version of an experience]] that introduces newer editors to patrolling workflows, making it easier for them to move from making edits to participating in more advanced moderation work on their project. [https://phabricator.wikimedia.org/T402647]
* The [[Special:Block]] now has two minor interface changes. Administrators can now easily perform indefinite blocks through a dedicated radio button in the expiry section. Also, choosing an indefinite expiry provides a different set of common reasons to select from, which can be changed at: [[MediaWiki:Ipbreason-indef-dropdown]]. [https://phabricator.wikimedia.org/T401823]
* Mobile editors [[mw:Special:MyLanguage/Contributors/Account Creation Experiments#Logged-out|at several wikis]] can now see an improved logged-out edit warning, thanks to the recent updates from the Growth team. These changes released last week are part of ongoing efforts and tests to enhance [[mw:Special:MyLanguage/Contributors/Account Creation Experiments|account creation experience on mobile]] and then increase participation. [https://phabricator.wikimedia.org/T408484]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:36}} community-submitted {{PLURAL:36|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, the bug that prevented mobile web users from seeing the block information when affected by multiple blocks has been fixed. They can now see messages of all the blocks currently affecting them when they access Wikipedia.
'''Updates for technical contributors'''
* Images built using Toolforge will soon get the upgraded buildpacks version, bringing support for newer language versions and other upstream improvements and fixes. If you use Toolforge Build Service, review the recent [https://lists.wikimedia.org/hyperkitty/list/cloud-announce@lists.wikimedia.org/thread/EMYTA32EV2V5SQ2JIEOD2CL66YFIZEKV/ cloud-announce email] and update your build configuration as necessary to ensure your tools are compatible. [https://wikitech.wikimedia.org/w/index.php?title=Help:Toolforge/Building_container_images&oldid=2392097#Buildpack_environment_upgrade_process][https://phabricator.wikimedia.org/T380127]
* The [https://api.wikimedia.org/wiki/Main_Page API Portal] documentation wiki will shut down in June 2026. API keys created on the API Portal will continue to work normally. api.wikimedia.org endpoints will be deprecated gradually starting in July 2026. Documentation on the API Portal is moving to [[mw:Wikimedia APIs|mediawiki.org]]. Learn more on the [[wikitech:API Portal/Deprecation|project page]].
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.21|MediaWiki]]
'''In depth'''
* [[m:Special:MyLanguage/WMDE Technical Wishes|WMDE Technical Wishes]] is considering improvements to [[m:WMDE Technical Wishes/References/VisualEditor automatic reference names|automatically generated reference names in VisualEditor]]. Please check out the [[m:WMDE Technical Wishes/References/VisualEditor automatic reference names#Proposed solutions|proposed solutions]] and participate in the [[m:Talk:WMDE Technical Wishes/References/VisualEditor automatic reference names#Request for comment|request for comment]].
'''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]] • [[m:Special:MyLanguage/Tech/News#contribute|Contribute]] • [[m:Special:MyLanguage/Tech/News/2026/13|Translate]] • [[m:Tech|Get help]] • [[m:Talk:Tech/News|Give feedback]] • [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].''
</div><section end="technews-2026-W13"/>
</div>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 23 mars 2026 à 17:51 (CET)
<!-- Message envoyé par User:UOzurumba (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30268305 -->
== Actualités techniques n° 2026-14 ==
<section begin="technews-2026-W14"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/14|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* Le version Beta de [[abstract:|Abstract Wikipedia]], un nouveau projet Wikimédia indépendant du langage, a été lancée la semaine dernière. Ce projet permet aux communautés de construire des articles Wikipédia dans leur langue natale, qui peuvent directement être lus par les autres utilisateurs et utilisatrices dans leur propre langage. Le wiki fonctionne grâce à des instructions de Wikifunctions et au contenu structuré issu de Wikidata. [[:f:Special:MyLanguage/Wikifunctions:Status updates/2026-03-26|En savoir plus]].
'''Actualités pour la contribution'''
* L'équipe Croissance mène un test A/B afin d'évaluer l'effet d'un message plus clair et plus convivial encourageant à la création de comptes sur les wikis. Actuellement, lorsqu'un utilisateur mobile non connecté lance la modification, un message d'avertissement s'affiche, pouvant paraître abrupt et décourageant. Il présente également la modification par compte temporaire comme option par défaut, au lieu d'inciter à la création d'un compte. Le test est mené sur dix Wikipédia, dont les versions en arabe, français, espagnol et allemand. [[mw:Special:MyLanguage/Contributors/Account Creation Experiments#2. Improve logged-out warning message (T415160)|En savoir plus]].
* L'équipe des applications Wikimédia sollicite vos commentaires sur [[mw:Special:MyLanguage/Wikimedia Apps/Team/Future of Editing on the Mobile Apps|comment devrait fonctionner l'édition dans les applications mobiles Wikipédia]]. La discussion porte sur l'amélioration de l'accès aux outils d'édition lorsque les utilisateurs appuient sur « Modifier ». Cette initiative s'inscrit dans un effort plus large visant à offrir aux lecteurs intéressés par la contribution une expérience utilisateur plus intuitive.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:45|la tâche soumise|les {{formatnum:45}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:45||s}} la semaine dernière]]. Par exemple, un problème avec la récupération de citations à partir du site d'archive de journaux [https://www.newspapers.com Newspapers.com], qui ne fonctionnait plus en raison d'un blocage des requêtes [[mw:Special:MyLanguage/Citoid|Citoid]], a maintenant été résolu. [https://phabricator.wikimedia.org/T419903]
'''Actualités pour la contribution technique'''
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.22|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/14|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W14"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 30 mars 2026 à 21:25 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30329462 -->
== Action Required: Update templates/modules for electoral maps (Migrating from P1846 to P14226) ==
Hello everyone,
This is a notice regarding an ongoing data migration on Wikidata that may affect your election-related templates and Lua modules (such as <code>Module:Itemgroup/list</code>).
'''The Change:'''<br />
Currently, many templates pull electoral maps from Wikidata using the property [[:d:Property:P1846|P1846]], combined with the qualifier [[:d:Property:P180|P180]]: [[:d:Q19571328|Q19571328]].
We are migrating this data (across roughly 4,000 items) to a newly created, dedicated property: '''[[:d:Property:P14226|P14226]]'''.
'''What You Need To Do:'''<br />
To ensure your templates and infoboxes do not break or lose their maps, please update your local code to fetch data from [[:d:Property:P14226|P14226]] instead of the old [[:d:Property:P1846|P1846]] + [[:d:Property:P180|P180]] structure. A [[m:Wikidata/Property Migration: P1846 to P14226/List|list of pages]] was generated using Wikimedia Global Search.
'''Deadline:'''<br />
We are temporarily retaining the old data on [[:d:Property:P1846|P1846]] to allow for a smooth transition. However, to complete the data cleanup on Wikidata, the old [[:d:Property:P1846|P1846]] statements will be removed after '''May 1, 2026'''. Please update your modules and templates before this date to prevent any disruption to your wiki's election articles.
Let us know if you have any questions or need assistance with the query logic. Thank you for your help! [[User:ZI Jony|ZI Jony]] using [[Utilisateur:MediaWiki message delivery|MediaWiki message delivery]] ([[Discussion utilisateur:MediaWiki message delivery|discussion]]) 3 avril 2026 à 19:11 (CEST)
<!-- Message envoyé par User:ZI Jony@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Non-Technical_Village_Pumps_distribution_list&oldid=29941252 -->
== Actualités techniques n° 2026-15 ==
<section begin="technews-2026-W15"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/15|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* L’[[mw:Special:MyLanguage/Help:Extension:CampaignEvents|extension CampaignEvents]] comprend désormais une nouvelle fonctionnalité de définition d’objectifs de groupe, permettant aux organisateurs de définir et de suivre les objectifs de l’événement, tels que le nombre d’articles créés et de contributeurs participants en temps réel. De même, les participants peuvent travailler vers des cibles communes et voir leur impact collectif au fur et à mesure que l’événement se déroule. Cette fonctionnalité est désormais disponible sur tous les wikis Wikimedia. Pour en savoir plus, consultez [[mw:Special:MyLanguage/Help:Extension:CampaignEvents/Registration/Collaborative contributions#Goal setting|la documentation]].
* [[File:Maki-gift-15.svg|12px|link=|class=skin-invert|Concerne un souhait]] La nouvelle fonctionnalité d'[[mw:Special:MyLanguage/Help:Watchlist labels|étiquettes de liste de suivi]] (annoncée dans les [[m:Special:MyLanguage/Tech/News/2026/07|Actualités techniques 2026-07 ]]) est désormais disponible via l'ÉditeurVisuel, l'éditeur de code et l'«étoile de suivi»(ou le lien de suivi, pour les habillages qui n'ont pas d'icône d'étoile). Auparavant, il n'était possible d'attribuer des étiquettes que via [[Special:EditWatchlist|Modifier la liste de suivi]]. Dans ces trois emplacements, il s'agit d'un nouveau champ situé après le champ d'expiration.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:23|la tâche soumise|les {{formatnum:23}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:23||s}} la semaine dernière]]. Par exemple, le problème où les pages de discussion sur mobile avec Parsoid sont inutilisables après les en-têtes de section vides, a maintenant été résolu. [https://phabricator.wikimedia.org/T419171]
'''Actualités pour la contribution technique'''
* La [[m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing|fonctionnalité de sous-référencement]], qui permet aux contributeurs d'ajouter des détails à une référence existante sans la dupliquer, sera progressivement déployée sur [[phab:T414094|davantage de wikis]] plus tard cette année. Les wikis utilisant le gadget [[mw:Special:MyLanguage/Reference Tooltips|Reference Tooltips]] sont encouragés à mettre à jour leur version (généralement sur [[m:MediaWiki:Gadget-ReferenceTooltips.js|MediaWiki:Gadget-ReferenceTooltips.js]] comme indiqué [https://en.wikipedia.org/w/index.php?diff=1344408362 ici]) pour assurer la compatibilité. D'autres gadgets liés aux références pourraient également être affectés. [https://phabricator.wikimedia.org/T416304]
* Toutes les éditions de Wikinews seront fermées et passeront en mode lecture seule le 4 mai 2026. Le contenu restera accessible, mais aucune nouvelle modification ni aucun nouvel article ne pourra être ajouté. Cette fermeture a été approuvée par le Conseil d'administration de la Fondation Wikimedia à la suite de discussions prolongées. [[m:Wikimedia Foundation Board noticeboard#Board of Trustees Approves Closure of Wikinews|En savoir plus]].
* L'[[:mw:Special:MyLanguage/API:Action API|API d'action]] a proposé plusieurs formats pour les résultats demandés. L'un d'entre eux, <bdi lang="zxx" dir="ltr"><code><nowiki>format=php</nowiki></code></bdi>, sera bientôt supprimé. Veuillez vous assurer que vos scripts ou robots utilisent le [[mw:Special:MyLanguage/API:Data formats#Output|format JSON]]. Cette suppression devrait affecter très peu de scripts et de robots. [https://phabricator.wikimedia.org/T118538]
* La page [[Special:NamespaceInfo|Special:NamespaceInfo]] inclut désormais les alias d'espace de noms. Par exemple «WP» pour l'espace de noms ''Projet'' (''Wikipédia'') sur la Wikipédia en allemand. [https://phabricator.wikimedia.org/T381455]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.23|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/15|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W15"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 6 avril 2026 à 18:19 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30362761 -->
== <span lang="en" dir="ltr">Tech News: 2026-16</span> ==
<div lang="en" dir="ltr">
<section begin="technews-2026-W16"/><div class="plainlinks">
Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/16|Translations]] are available.
'''Weekly highlight'''
* Experienced editors are invited to [https://b24e11a4f1.catalyst.wmcloud.org/wiki/Main_Page test] the [[mw:Special:MyLanguage/Article guidance|Article guidance]] feature, designed to help less-experienced editors create well-structured, policy-compliant Wikipedia articles. Testing instructions are [[mw:Special:MyLanguage/Article guidance/Test feature guide|available]]. Also, after reviewing [https://b24e11a4f1.catalyst.wmcloud.org/wiki/Category:Pages_using_article_guidance the outlines], please provide feedback on the [[mw:Talk:Article guidance|project talk page]]. Based on your input, the feature will be refined and transferred to the pilot Wikipedias to translate and adapt. Check out [[c:File:Article Guidance workflow demo - April 2026.webm|the video]] explaining the feature.
'''Updates for editors'''
* On most wikis, all autoconfirmed users can now use [[Special:ChangeContentModel|Special:ChangeContentModel]] page to [[mw:Special:MyLanguage/Help:ChangeContentModel|create new pages with custom content models]], such as mass message lists, making custom page formats more accessible. Check [[Special:ListGroupRights|Special:ListGroupRights]] for the status of your wiki. [https://phabricator.wikimedia.org/T248294]
* The Growth team has launched an [[mw:Special:MyLanguage/Contributors/Account_Creation_Experiments|account creation experiment]] to evaluate whether adding an account creation button to the mobile web header increases new account registrations and encourages more mobile users to contribute to the wikis. The experiment is currently live on Hindi, Indonesian, Bengali, Thai, and Hebrew Wikipedia, and targets 10% of logged-out mobile web users.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:30}} community-submitted {{PLURAL:30|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, an issue where VisualEditor could get stuck loading on Windows devices with animations turned off, has now been fixed. [https://phabricator.wikimedia.org/T382856]
'''Updates for technical contributors'''
* Starting later this week, {{int:group-abusefilter}} who have the [[mw:Special:MyLanguage/Help:Extension:CodeMirror|{{int:codemirror-beta-feature-title}}]] beta feature enabled will have [[mw:Special:MyLanguage/Extension:CodeMirror|CodeMirror]] instead of [[mw:Special:MyLanguage/Extension:CodeEditor|CodeEditor]] as the editor at [[Special:AbuseFilter|Special:AbuseFilter]]. This is part of the broader effort to make the user experience more consistent across all editors. [https://phabricator.wikimedia.org/T399673][https://phabricator.wikimedia.org/T419332]
* Tools and bots that access the [[mw:Special:MyLanguage/Notifications/API|Notifications API]] (<bdi lang="zxx" dir="ltr"><code><nowiki>action=query&meta=notifications</nowiki></code></bdi>) will need to update their OAuth or BotPassword grants to also include access to private notifications. [https://phabricator.wikimedia.org/T421991]
* Due to a library upgrade, listings on category pages may be displayed out of order starting on Monday, 20th April. A migration script will be run to correct this, and will take hours to days depending on the size of the wiki (up to a week for English Wikipedia). [https://phabricator.wikimedia.org/T422544]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.24|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]] • [[m:Special:MyLanguage/Tech/News#contribute|Contribute]] • [[m:Special:MyLanguage/Tech/News/2026/16|Translate]] • [[m:Tech|Get help]] • [[m:Talk:Tech/News|Give feedback]] • [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].''
</div><section end="technews-2026-W16"/>
</div>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 13 avril 2026 à 17:19 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30380527 -->
== Actualités techniques n° 2026-17 ==
<section begin="technews-2026-W17"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/17|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* Après deux ans de développement, la version [[mw:Special:MyLanguage/Help:Extension:CodeMirror|{{int:codemirror-beta-feature-title}}]], également connue sous le nom de [[mw:Special:MyLanguage/Extension:CodeMirror|CodeMirror 6]], sortira de sa phase bêta le mardi 21 avril. Elle offrira une meilleure lisibilité du code et du wikitext, une réduction des fautes de frappe et d'autres [[mw:Special:MyLanguage/Help:Extension:CodeMirror|avantages]] à tous les utilisateurs du surligneur de syntaxe standard. Un grand merci au bénévole [https://phabricator.wikimedia.org/p/Bhsd/ Bhsd] qui a développé de nombreuses nouvelles fonctionnalités, notamment [[mw:Special:MyLanguage/Help:Extension:CodeMirror#Code folding|le repliement de code]], [[mw:Special:MyLanguage/Help:Extension:CodeMirror#Autocompletion|la saisie semi-automatique]] et [[mw:Special:MyLanguage/Help:Extension:CodeMirror#Linting|l'analyse statique du code]]. [https://phabricator.wikimedia.org/T259059]
* Une mise à jour majeure de l'application Wikipédia pour iOS est en cours de déploiement, en restructurant l'interface pour s'harmoniser avec le tout nouveau design visuel "Liquid Glass" d'Apple. [https://apps.apple.com/us/app/wikipedia/id324715238 Télécharger la dernière version] et découvrez les nouveautés.
'''Actualités pour la contribution'''
* [[mw:Special:MyLanguage/Readers/Reader Experience/WE3.3.4 Reading lists|Les listes de lecture]] est une fonctionnalité qui permet aux lecteurs d'enregistrer des articles dans une liste pour les lire ultérieurement. Cette fonctionnalité est actuellement en version bêta sur les Wikipédias en arabe, français, indonésien, vietnamien et chinois, et activée par défaut pour tous les nouveaux comptes sur toutes les Wikipédias.
* Une expérimentation visant à étendre [[mw:Special:MyLanguage/Readers/Reader Growth/Mobile page previews|les aperçus de page au web mobile]] sera lancée la semaine du 20 avril sur les versions arabe, anglaise, française, italienne, polonaise et vietnamienne de Wikipédia. Les aperçus de page sont des fenêtres contextuelles affichant une miniature, un premier paragraphe et un lien bleu permettant d'ouvrir l'article complet, facilitant ainsi la découverte de contenu. Cette fonctionnalité est déjà disponible sur ordinateur et dans les applications. [[m:Special:MyLanguage/List of experiments in Product and Technology#Template|En savoir plus sur cette expérimentation et d'autres]].
* Sur plusieurs wikis, les contributeurs connectés qui n'ont pas [[mw:Special:MyLanguage/Help:Email confirmation|confirmé leur adresse électronique]] peuvent désormais voir une bannière les invitant à le faire. La confirmation de l'adresse électronique permet à un utilisateur de récupérer l'accès à son compte en cas de perte. [[mw:Special:MyLanguage/Product Safety and Integrity/Account Security#Encouraging users to confirm their email addresses|En savoir plus]]. [https://phabricator.wikimedia.org/T421366]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:15|la tâche soumise|les {{formatnum:15}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:15||s}} la semaine dernière]]. Par exemple, un problème qui entraînait des ralentissements lors de la modification de très grandes pages wiki dans l'éditeur wikitext de 2017, des problèmes de chargement, de prévisualisation et de défilement, ainsi que des problèmes de performance lors de la sélection, de la découpe ou du collage de contenu, a maintenant été résolu. [https://phabricator.wikimedia.org/T184857]
'''Actualités pour la contribution technique'''
* Dans le cadre de la promotion de [[mw:Special:MyLanguage/Help:Extension:CodeMirror|CodeMirror]] à partir d'une fonctionnalité bêta, tous les utilisateurs se serviront de [[mw:Special:MyLanguage/Extension:CodeMirror|CodeMirror]] au lieu de [[mw:Special:MyLanguage/Extension:CodeEditor|CodeEditor]] pour la coloration syntaxique lors de l'édition de pages de contenu JavaScript, CSS, JSON, Vue et Lua. [https://phabricator.wikimedia.org/T419332]
* <span class="mw-translate-fuzzy">Le service <code>mirrors.wikimedia.org</code> pour les utilisateurs de Debian et Ubuntu sera définitivement arrêté le 15 mai. Le matériel du serveur sera remplacé par des solutions plus performantes. Certains utilisateurs devront peut-être migrer vers un autre serveur qui ne devra prendre qu'une minute. [https://lists.wikimedia.org/hyperkitty/list/wikitech-l@lists.wikimedia.org/thread/LJYRIS4WB66HIRCAO4GIDTXCMDVZRBMA/ Vous pouvez en savoir plus].</span> [https://phabricator.wikimedia.org/T416707]
* Les tables <bdi lang="zxx" dir="ltr"><code><nowiki>image</nowiki></code></bdi> et <bdi lang="zxx" dir="ltr"><code><nowiki>oldimage</nowiki></code></bdi> seront supprimées de [[wikitech:Help:Wiki Replicas|wikireplicas]]. Si vos outils ou requêtes accèdent directement à <bdi lang="zxx" dir="ltr"><code><nowiki>image</nowiki></code></bdi> ou <bdi lang="zxx" dir="ltr"><code><nowiki>oldimage</nowiki></code></bdi>, veuillez les mettre à jour pour utiliser les tables <bdi lang="zxx" dir="ltr"><code><nowiki>file</nowiki></code></bdi> et <bdi lang="zxx" dir="ltr"><code><nowiki>filerevision</nowiki></code></bdi> avant le 28 mai. [https://phabricator.wikimedia.org/T28741]
* Suite à la récente mise en place de limites de débit globales pour les API non identifiées, la Fondation Wikimedia poursuit ses efforts pour garantir [[mw:Special:MyLanguage/MediaWiki Product Insights/Responsible Reuse|une utilisation équitable de l'infrastructure]] en appliquant des limites globales au trafic des API identifiées à partir de la dernière semaine d'avril. Ces limites sont volontairement fixées au niveau le plus élevé possible afin de minimiser l'impact sur la communauté. Les bots exécutés dans Toolforge/WMCS ou disposant des droits d'utilisateur de bot sur un wiki ne devraient pas être affectés pour le moment. Toutefois, il est conseillé à tous les développeurs de suivre les bonnes pratiques mises à jour. Pour plus d'informations, consultez la page [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits|API Wikimedia/Limites de débit]] et la [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits/FAQ|Foire aux questions]].
* L'[[mw:Special:MyLanguage/Attribution API|API d'attribution]] est désormais disponible en [[mw:Special:MyLanguage/Wikimedia APIs/Stability policy|version bêta]]. Elle récupère les informations nécessaires pour créditer les articles et les fichiers multimédias de Wikimedia, quel que soit leur lieu d'utilisation. La documentation de référence est disponible sur la page dédiée au Sandbox REST, accessible sur tous les wikis Wikimedia (comme [https://en.wikipedia.org/w/index.php?api=attribution.v0-beta&title=Special%3ARestSandbox le sandbox REST de Wikipédia en anglais]). N'hésitez pas à partager vos commentaires sur la [[mw:Talk:Attribution API|page de discussion du projet]].
* Il n'y aura pas de nouvelle version de MediaWiki cette semaine.
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/17|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W17"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 20 avril 2026 à 17:00 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30432763 -->
== Request for comment (global AI policy) ==
<bdi lang="en" dir="ltr" class="mw-content-ltr">
Apologies for writing in English. {{int:Please-translate}}
A [[:m:Requests for comment/Artificial intelligence policy|request for comment]] is currently being held to decide on a global AI policy. {{int:Feedback-thanks-title}}
[[Utilisateur:MediaWiki message delivery|MediaWiki message delivery]] ([[Discussion utilisateur:MediaWiki message delivery|discussion]]) 26 avril 2026 à 02:57 (CEST)
</bdi>
<!-- Message envoyé par User:Codename Noreste@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=30424282 -->
== Actualités techniques n° 2026-18 ==
<section begin="technews-2026-W18"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/18|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* Un changement dans la manière dont les utilisateurs et utilisatrices sont automatiquement confirmés est en cours pour améliorer la protection contre le vandalisme. Actuellement, il suffit d’avoir un compte depuis quelques jours avec quelques contributions pour être ajouté au groupe [[{{int:grouppage-autoconfirmed/{{CONTENTLANGUAGE}}}}|{{int:group-autoconfirmed}}]]. Cette configuration tend à être exploitée par certains vandales qui créent des comptes et commencent à les utiliser après un certain temps. Pour réduire ce problème, la configuration va changer la semaine prochaine afin que l’âge du compte minimum pour être confirmé automatiquement ne soit calculé qu’à partir de la première modification, au lieu de la date d’inscription. L’âge minimum du compte restera le même, c’est seulement le point de départ pour calculer cet âge qui change. Ce changement ne sera déployé que sur les wikis qui nécessitent au moins une contribution pour satisfaire les conditions de confirmation automatique. [https://phabricator.wikimedia.org/T418484]
* Tous les utilisateurs et utilisatrices de Wikipédia avec un nouveau compte et ceux qui ont activé l’option « activer automatiquement la plupart des fonctionnalités bêta » peuvent désormais utiliser la fonctionnalité bêta de [[mw:Special:MyLanguage/Readers/Reader Experience/WE3.3.4 Reading lists|listes de lecture]] pour enregistrer des articles à lire plus tard. Cela permet d’organiser les lectures qui nous intéressent à un endroit unique pour y accéder facilement.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:30|la tâche soumise|les {{formatnum:30}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:30||s}} la semaine dernière]]. Par exemple, le problème avec les images d’infoboite qui avaient une marge intérieure immense dans Firefox a été corrigé. [https://phabricator.wikimedia.org/T423676]
'''Actualités pour la contribution technique'''
* Pour rappel, la limite globale d’accès à l’API sera appliquée cette semaine pour identifier le trafic de l’API. Le but est d’aider à garantir un [[mw:MediaWiki Product Insights/Responsible Reuse|accès équitable à l’infrastructure]]. Les robots qui s’exécutent dans Toolforge ou WMCS, ou avec le droit utilisateur ''robot'' sur les wikis, ne devraient pas être affectés pour le moment. Cependant, il est conseillé à tous les développeurs et développeuses de se conformer aux nouvelles bonnes pratiques à suivre. Pour plus d’informations, notamment la limite globale d’accès effective, consultez [[mw:Wikimedia APIs/Rate limits|la page sur la limite d’accès des API de Wikimedia]] et les [[mw:Wikimedia APIs/Rate limits/FAQ|questions-réponses]].
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.26|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/18|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W18"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 27 avril 2026 à 20:06 (CEST)
<!-- Message envoyé par User:UOzurumba (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30458046 -->
== Actualités techniques n° 2026-19 ==
<section begin="technews-2026-W19"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/19|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* L’équipe chargée des fonctionnalités de [[mw:Special:MyLanguage/Article guidance|Guidage des articles]] invite les contributeurs et contributrices expérimentés des [[mw:Special:MyLanguage/Article guidance/Pilot wikis and collaborators|Wikipédia pilotes]] (arabe, bangla, japonais, portugais, persan, turc, anglais simplifié, espagnol et français) à contribuer à la traduction et à l’adaptation des [https://b24e11a4f1.catalyst.wmcloud.org/wiki/Category:Pages_using_article_guidance exemples de trames d’articles]. Ces trames guideront les contributeurs dans la création d’articles clairs, bien structurés et conformes aux règles lors de l’utilisation de [https://b24e11a4f1.catalyst.wmcloud.org/wiki/Special:NewArticle la fonctionnalité] dès son lancement en mai 2026. Des [[mw:Special:MyLanguage/Article guidance#Adapting a sample outline in a Wikipedia|instructions simples]] expliquant comment traduire et adapter ces trames sont disponibles.
'''Actualités pour la contribution'''
* Le [[:m:Special:MyLanguage/Product and Technology Advisory Council|Conseil consultatif sur les produits et les technologies]] a publié [[:m:Special:MyLanguage/Product and Technology Advisory Council/May 2026 draft PTAC recommendation for feedback|une proposition de recommandation]] d’une procédure type que les organisations affiliées à Wikimedia pourraient suivre pour contribuer au domaine technique. Les membres de la communauté sont invités à donner leur avis sur cette recommandation avant le 8 mai [[:m:Talk:Product and Technology Advisory Council/May 2026 draft PTAC recommendation for feedback|sur la page de discussion]].
* Le nombre de préférences de taille de la miniature disponibles dans MediaWiki va être réduit à trois options standardisées : ''petite'' (180 px), ''moyenne'' (250 px) et ''large'' (400 px), dans le cadre du travail en cours pour améliorer les performances et réduire la pression sur les services de miniatures. Par conséquent, les préférences existantes seront automatiquement adaptées à la nouvelle taille la plus proche (par exemple, les petites tailles comme 120 px ou 150 px s’afficheront à 180 px, tandis que les grandes tailles comme 300 px ou 360 px s’afficheront à 400 px). L’interface des préférences sera bientôt mise à jour pour refléter ces changements, et les utilisateurs qui souhaitent s’y opposer ou donner leur avis peuvent le faire. [https://phabricator.wikimedia.org/T424909]
* Dorénavant, même lorsqu’une permission expire automatiquement, les utilisateurs recevront une notification Echo similaire à la notification normale pour les changements de permissions. Quant au [[m:Special:MyLanguage/Global reminder bot|robot global de rappel]], il continue de prévenir les utilisateurs une semaine ''avant'' que leurs droits ne soient sur le point d’expirer, afin qu’ils puissent les faire renouveler.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:32|la tâche soumise|les {{formatnum:32}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:32||s}} la semaine dernière]]. Par exemple, le problème du sélecteur de langue ULS dans [[m:Special:Translate|Special:Translate]] qui faisait défiler verticalement alors qu’il ne devait pas, a été résolu. Auparavant, lorsque les utilisateurs ouvraient le menu déroulant « Traduire en français » et commençaient à saisir le nom d’une langue, la boîte de dialogue défilait verticalement de quelques pixels même lorsqu'il y avait suffisamment d’espace pour afficher tous les résultats. Le menu déroulant ne se déplace plus inutilement lors du filtrage des langues. [https://phabricator.wikimedia.org/T358864]
* La [[m:Special:GlobalWatchlist|liste de suivi globale]], qui vous permet de consulter vos listes de suivi provenant de plusieurs wikis sur une seule page, continue de s’améliorer. Par exemple, les listes de suivi pour les sites avec Wikibase tels que [[:d:|Wikidata]] prennent désormais en charge les éléments [[mw:Special:MyLanguage/Extension:EntitySchema|EntitySchema]] pour un meilleur suivi. Le mode Mises à jour en direct actualise désormais la page spéciale toutes les 60 secondes afin de se conformer aux [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits|nouvelles limites globales d’accès à l’API]] pour une meilleure réactivité en temps réel. Par ailleurs, un bug de directionnalité du texte qui affichait les liens comme « changements 3 » au lieu de « 3 changements » dans les listes à directions mixtes a été corrigé. [https://phabricator.wikimedia.org/T415450][https://phabricator.wikimedia.org/T424422][https://phabricator.wikimedia.org/T418091]
'''Actualités pour la contribution technique'''
* La deuxième phase de [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits|limitations globales d’accès à l’API]] a été déployée pour réduire l’[[diffblog:2026/03/26/quo-vadis-crawlers-progress-and-whats-next-on-safeguarding-our-infrastructure/|impact des robots IA]] et assurer un accès équitable et durable aux ressources de Wikimedia, en donnant la priorité au trafic humain et conforme à notre mission. Les [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits#Limits|limites]] ne s’appliquent plus par heure mais par minute, produisant une meilleure répartition dans les structures de trafic ainsi qu’une meilleure prévisibilité de la charge de l’API. Les utilisateurs de la communauté ne devraient pas être affectés, et aucune action n’est requise. Les premières indications montrent que certains requérants basés sur l'agent utilisateur ajustent leur comportement, et environ 64 % du trafic API automatisé a été identifié. La surveillance continue, et Wikimedia Enterprise reste disponible pour l’assistance commerciale.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.27|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/19|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W19"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 4 mai 2026 à 22:43 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30498077 -->
== Actualités techniques n° 2026-20 ==
<section begin="technews-2026-W20"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/20|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* La Communauté Technique a publié [[m:Special:MyLanguage/Community Wishlist/How to write a good wish|de nouvelles directives]] expliquant comment les souhaits sur la Liste de souhaits de la communauté sont triés et priorisés. La documentation vise à aider les contributeurs à rédiger des propositions plus solides en clarifiant les facteurs qui influencent les décisions de priorisation. Au-delà du nombre de votes, les directives mettent en avant des considérations telles que l'impact potentiel sur la communauté pour déterminer quels souhaits avanceront.
'''Actualités pour la contribution'''
* L'équipe de croissance des lecteurs lance une expérience pour tester une nouvelle [[mw:Special:MyLanguage/Readers/Reader_Growth/Share_Card|fonctionnalité de Partage de Carte]] qui permet aux lecteurs de créer des cartes visuellement attrayantes à partir d'articles Wikipédia ou de sections d'articles sélectionnées et de les partager en ligne, chaque carte renvoyant à l'article original afin d'aider à augmenter le lectorat et la découverte des articles. Le test A/B réservé aux mobiles ne sera disponible qu'à une partie des lecteurs sur les Wikipédia en arabe, chinois, français, vietnamien et anglais afin de mieux comprendre les habitudes de lecture et de partage, et est prévu pour commencer la semaine du 18 mai pour une durée de quatre semaines.
* Les applications Wikipedia pour Android et iOS ont récemment publié en version bêta le [[mw:Special:MyLanguage/Wikimedia_Apps/Team/25th_Birthday_Reading_Challenge|défi de lecture de 25 jours]], dans le cadre des efforts visant à stimuler l'engagement des lecteurs en encourageant les utilisateurs à atteindre des objectifs de lecture. Pour suivre leur série de lectures pendant le défi, les utilisateurs de l'application peuvent ajouter un widget avec Baby Globe à leur écran d'accueil. Le défi commence officiellement le 11 mai.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:17|la tâche soumise|les {{formatnum:17}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:17||s}} la semaine dernière]]. Par exemple, un problème où la préférence globale pour activer la coloration syntaxique dans le wikitexte pouvait s'éteindre de manière inattendue après avoir été activée a maintenant été corrigé. [https://phabricator.wikimedia.org/T425286]
'''Actualités pour la contribution technique'''
* [[File:Octicons-tools.svg|12px|link=|alt=|Sujet technique]] Le module ResourceLoader <bdi lang="zxx" dir="ltr"><code><nowiki>mediawiki.ui.input</nowiki></code></bdi>, obsolète depuis [[m:Special:MyLanguage/Tech/News/2023/39|septembre 2023]], sera supprimé cette semaine. Il existe un [[mw:Special:MyLanguage/Codex/Migrating_from_MediaWiki_UI|guide pour migrer de l’interface MediaWiki UI vers Codex]] pour tous les outils qui l’utilisent. [https://phabricator.wikimedia.org/T420125]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.2|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/20|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W20"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 11 mai 2026 à 21:20 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30524429 -->
== Actualités techniques n° 2026-21 ==
<section begin="technews-2026-W21"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/21|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* L'équipe de Wikipédia abstraite a identifié cinq wikis pilotes potentiels pour évaluer leur intérêt à adopter des articles abstraits sur leurs wikis. Les pilotes sont Wikipédia en Malayalam, en Bengali, en Dagbani, en Arabe et en Indonésien. La période de retour d'information sera ouverte jusqu'au 22 mai. Si votre communauté est intéressée à devenir un pilote, [[m:Talk:Abstract Wikipedia|faites-nous savoir sur Meta]].
'''Actualités pour la contribution'''
* Une expérience visant à afficher [[mw:Special:MyLanguage/Readers/Reader Experience/Reading lists|les listes de lecture]] aux lecteurs non connectés sur le web mobile sera lancée le 18 mai sur les Wikipédias Allemande, Espagnole, Italienne, Portugaise, Polonaise, Néerlandaise, Turque et Ourdou, et durera un mois. Cet effort soutient des objectifs plus larges consistant à aider les lecteurs à enregistrer et organiser des articles pour une lecture ultérieure, tout en encourageant des habitudes qui pourraient mener à de futures contributions sur Wikipédia.
* Pour prendre en charge un bouton de marquage dans la fonctionnalité bêta Liste de lecture, le menu "Outils > Action" a été mis à jour pour afficher des icônes, y compris l'indicateur en forme d'étoile de suivi qui aide les éditeurs à identifier les articles suivis temporairement. Les icônes correspondent désormais également à celles utilisées sur mobile, améliorant la cohérence entre les plateformes. Le changement est actuellement limité au menu des actions et concerne principalement les éditeurs ayant des droits d'utilisateur privilégié. [https://phabricator.wikimedia.org/T426008]
* [[mw:Special:MyLanguage/VisualEditor/Suggestion Mode|Mode de Suggestion]] a été publié en tant qu'[[w:en:A/B test|test A/B]] pour les nouveaux éditeurs sur le site mobile à [[phab:T421189|~15 Wikipédias]]. L'expérience mesurera l'impact que le Mode de Suggestion a sur la proportion de sessions d'édition sur le web mobile par des nouveaux éditeurs qui aboutissent à des modifications constructives (non annulées) des articles. L'expérience évaluera également l'impact de la fonctionnalité sur la rétention des éditeurs et surveillera les changements dans les taux d'annulation et de blocage.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:27|la tâche soumise|les {{formatnum:27}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:27||s}} la semaine dernière]]. Par exemple, un problème dans l'application Android de Wikipédia où les images pourraient parfois ne pas se charger après avoir ouvert une notification de liste de lecture recommandée, a maintenant été corrigé. [https://phabricator.wikimedia.org/T418231]
'''Actualités pour la contribution technique'''
* L'[[mw:Special:MyLanguage/Wikidata Platform|équipe de la Plateforme Wikidata]] a publié sa [[d:Special:MyLanguage/Wikidata:SPARQL query service/WDQS backend update/Backend Replacement|recommandation de remplacement du backend]] et l'[[wikitech:Wikidata Query Service/WDQS Architecture re-design|architecture technique]] qui l'accompagne pour la migration du Wikidata Query Service (WDQS) hors de Blazegraph grap. Les retours sont attendus jusqu'au 25 mai 2026, en particulier sur les éventuelles lacunes et impacts sur les cas d'utilisation avancés. Les membres de la communauté Wikidata et les utilisateurs de WDQS sont également encouragés à aider à identifier les outils et flux de travail à fort impact qui pourraient nécessiter une attention sur [[d:Wikidata:SPARQL query service/WDQS backend update/High-Impact Use Cases|cette page]]. Les retours peuvent être partagés sur la [[d:Wikidata talk:SPARQL query service/WDQS backend update|page de discussion de la migration]] ou lors de la [[d:Special:MyLanguage/Wikidata:Blazegraph Migration Office Hours|prochaine heure de bureau]]. Voir le [[d:Special:MyLanguage/Wikidata:Wikidata Platform team/Newsletter|bulletin de l'équipe WDP]] pour plus de détails.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.3|MediaWiki]]
'''En détails'''
* Sur les Wikipédia en anglais, en français, en japonais et quelques autres, il y a eu un [[diffblog:2025/09/02/better-detecting-bots-and-replacing-our-captcha/|essai de hCaptcha]], un service tiers de détection de robots. L'essai a montré que hCaptcha détecte et dissuade efficacement certaines activités automatisées de mauvaise foi, à la fois par lui-même et en donnant des signaux aux [[w:en:Wikipedia:Village pump (technical)/Archive 225#Introducing SuggestedInvestigations|checkusers et stewards]] pour qu'ils enquêtent. Comme les résultats étaient positifs, hCaptcha sera déployé sur toutes les wikis au cours des prochaines semaines. [[mw:Special:MyLanguage/Product Safety and Integrity/Anti-abuse signals/hCaptcha|Voir la page du projet hCaptcha]] pour des informations techniques sur la mise en œuvre et les protections de la vie privée. [[diffblog:2026/05/04/better-detecting-bots-and-replacing-our-captcha-part-2/|En savoir plus]].
* La dernière mise à jour de la Technologie communautaire est désormais disponible, avec des progrès dans plusieurs initiatives de la Liste de souhaits communautaire, y compris l'extension des listes de lecture de l'application mobile au site web, la prise en charge de nouvelles langues pour "Who Wrote That" et le Tableau de bord personnel, des améliorations du rendu 3D et des graphiques, ainsi que des travaux à venir sur le tri des pages de discussion, la lecture audio et les flux de travail d'édition. La mise à jour partage également les priorités actuelles, les tendances de l'état de la Liste de souhaits et les opportunités de retour d'information de la communauté sur les domaines de concentration futurs et le Plan annuel 2026–2027 de la Wikimedia Foundation. [[m:Special:MyLanguage/Community Wishlist/Updates#May 13, 2026: Latest updates from the Community Tech team|Lisez le bulletin d'information complet pour plus de détails]].
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/21|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W21"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 18 mai 2026 à 22:21 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30539262 -->
== Actualités techniques n° 2026-22 ==
<section begin="technews-2026-W22"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/22|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* Faisant suite à une [[mw:Special:MyLanguage/Contributors/Account Creation Experiments#LOWM|expérience fructueuse sur la création de comptes]], un message d'avertissement pour les personnes déconnectées sera déployé sur les wikis Wikimédia durant la première semaine de juin. Ce changement n'affectera que les personnes déconnectées sur l'interface web mobile qui commencent à modifier. Cette nouvelle expérience est faite pour encourager la création de comptes, tout en autorisant aux utilisateurs de modifier à l'aide de comptes temporaires. Les résultats de l'expérience ont montré une augmentation de la création de compte d'environ 27 % pour ceux ayant vu le nouveau message. Comme prévu, puisque plus de personnes créent un compte, la création de comptes temporaires a diminué de 16 %. L'expérience n'a pas montré d'autres changements sur la qualité des modifications ou sur les autres indicateurs surveillés. [https://phabricator.wikimedia.org/T424595]
'''Actualités pour la contribution'''
* Pour des raisons de sécurité, les membres de certains groupes d’utilisateurs sont [[m:Special:MyLanguage/Mandatory two-factor authentication for users with some extended rights|forcés d'avoir l'authentification à 2 facteurs]] (A2F) d'activée. Les membres de ces groupes seront dans l'impossibilité de désactiver la dernière méthode d'A2F sur leur compte, et il sera impossible d'ajouter des utilisateurs sans A2F à ces groupes. Ces utilisateurs auront toujours la possibilité d'ajouter ou d'enlever des nouvelles méthodes d'authentification, tant qu'une de ces méthodes est toujours activée. Dans les prochaines semaines, les utilisateurs sans A2F seront retirés de ces groupes. Cela s'applique entre autres aux bureaucrates. Veuillez lire les tâches liées pour les dates de déploiement. [https://phabricator.wikimedia.org/T423119][https://phabricator.wikimedia.org/T423120]
* L'[[m:Special:MyLanguage/WMDE Technical Wishes|équipe des souhaits techniques de Wikimédia Allemagne (WMDE)]] va lancer un [[w:fr:Test A/B|test A/B]] sur [[:phab:T415904|10 wikis]], pour essayer des [[m:WMDE Technical Wishes/References/Reference Previews|améliorations potentielles pour les aperçus de références]]. Cette expérience durera environ 2 semaines à la fin mai ou début juin et affectera 10 % du lectorat sur ordinateur sur les wikis participants.
* Après deux expériences fructueuses, l'équipe Croissance du lectorat déploiera une fonctionnalité de [[mw:Special:MyLanguage/Readers/Reader Growth/Image Browsing|visionnage d'images]] en bêta pour toutes les Wikipédia sur mobile le 25 mai. Cela veut dire que toutes les personnes ayant les fonctionnalités bêtas activées verront cette fonctionnalité. Les autres pourront l’activer dans leurs préférences. Cette fonctionnalité inclura un carrousel de toutes les images d'un article en haut de celui-ci, avec la possibilité pour les contributeurs d’[[mw:Readers/Reader_Growth/Image_Browsing#Phase_2.1_beta_feature|exclure des images du carrousel d'un article ou d'enlever la fonctionnalité pour l'entièreté de l'article]].
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:30|la tâche soumise|les {{formatnum:30}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:30||s}} la semaine dernière]]. Par exemple, les fichiers STL tridimensionnels étaient affichés incorrectement par l'extension 3D du lecteur multimédia, ce qui est maintenant corrigé. [https://phabricator.wikimedia.org/T416723]
'''Actualités pour la contribution technique'''
* Les classes CSS dépréciées <bdi lang="zxx" dir="ltr"><code><nowiki>tleft</nowiki></code></bdi> et <bdi lang="zxx" dir="ltr"><code><nowiki>tright</nowiki></code></bdi> ont été remplacées par <bdi lang="zxx" dir="ltr"><code><nowiki>floatleft</nowiki></code></bdi> et <bdi lang="zxx" dir="ltr"><code><nowiki>floatright</nowiki></code></bdi> car les premières ne fonctionnent pas correctement sur toutes les plateformes, dont l'interface web mobile et l'application mobile. Les projets se servant de ces classes sont encouragés à vérifier leur usage et à planifier leur migration. Sachez que <bdi lang="zxx" dir="ltr"><code><nowiki>floatleft</nowiki></code></bdi> et <bdi lang="zxx" dir="ltr"><code><nowiki>floatright</nowiki></code></bdi> pourraient aussi être dépréciées dans le futur, même s’il n'y a pas de calendrier défini. [[phab:T426452|En savoir plus]].
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.4|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/22|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W22"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 25 mai 2026 à 23:52 (CEST)
<!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30584502 -->
== Votez maintenant aux élections 2026 de l'U4C ==
<section begin="announcement-content" />
Les votants éligibles sont invités à participer à l'élection 2026 du [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee|Comité de coordination du Code de conduite universel]]. De plus amples informations – notamment sur la vérification de l'éligibilité, le processus de vote, les candidats et un lien vers le scrutin – sont disponibles sur Meta à la [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee/Election/2026|page d'informations sur les élections de 2026]]. Le scrutin se termine le 2 juin 2026 à [https://zonestamp.toolforge.org/1780358400 00 h 00 UTC].
Veuillez voter si votre compte est éligble. Les résultats seront disponibles avant le 14 juin 2026. -- en coopération avec l'U4C.<section end="announcement-content" />
[[m:User:Keegan (WMF)|Keegan (WMF)]] ([[m:User talk:Keegan (WMF)|talk]]) 27 mai 2026 à 19:14 (CEST)
<!-- Message envoyé par User:Keegan (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=30513860 -->
== Actualités techniques n° 2026-23 ==
<section begin="technews-2026-W23"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/23|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* L'équipe [[mw:Special:MyLanguage/Readers/Reader Experience|Reader Experience]] mène une expérience pour montrer la fonctionnalité [[mw:Special:MyLanguage/Readers/Reader Experience/Reading lists|listes de lecture]], qui est encore en développement, aux lecteurs non connectés sur mobile afin de tester si elle encourage la création de compte à un rythme plus élevé que le bouton watchstar. L'[[mw:Special:MyLanguage/Readers/Reader Experience/Reading lists#Experiment timeline|expérience]] a été lancée le 18 mai sur les wikis en allemand, espagnol, italien, portugais, polonais, néerlandais, turc et ourdou, et elle durera un mois.
* L'équipe Wikimedia Apps a publié la [[mw:Special:MyLanguage/Wikimedia Apps/Team/Explore Feed Refresh/Phase 1|Phase 1]] du flux d'accueil repensé pour l'application Android Beta. Le nouveau flux d'accueil comprend un onglet « Communauté » actualisé et un onglet « Pour vous » personnalisé contenant des recommandations de lecture mises à jour quotidiennement. La refonte fait partie d'un effort plus large visant à améliorer la découverte de contenu et à créer des expériences d'apprentissage plus engageantes dans les applications Wikipédia.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:18|la tâche soumise|les {{formatnum:18}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:18||s}} la semaine dernière]]. Par exemple, un problème où les images pouvaient ne pas se charger pour certaines modifications suggérées sur [[w:Special:Homepage|Special:Homepage]], laissant la vignette bloquée dans un état de chargement, a maintenant été corrigé. [https://phabricator.wikimedia.org/T424048]
'''Actualités pour la contribution technique'''
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.5|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/23|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W23"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 1 juin 2026 à 23:08 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30613639 -->
== Actualités techniques n° 2026-24 ==
<section begin="technews-2026-W24"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/24|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* Wikimedia Entreprise a relevé les limites d’utilisation gratuite de ses API. La limite mensuelle de requêtes pour l’API « à la demande » (<i lang="en">On-demand</i>) est passée de {{formatnum:5000}} à {{formatnum:50000}} requêtes, tandis que celle de l’API des instantanés (<i lang="en">Snapshot</i>) est passée de 15 à 30 requêtes par mois. De plus, les instantanés de contenus structurés sont désormais accessibles aux comptes gratuits. Ces changements élargissent l’accès aux données de Wikimedia Entreprise pour les développeurs et développeuses, les chercheurs et chercheuses et les organisations qui utilisent les contenus Wikimédia. [https://enterprise.wikimedia.com/blog/enhanced-free-api]
'''Actualités pour la contribution'''
* La [[mw:Special:MyLanguage/Wikimedia_Apps/Team/Explore Feed Refresh/Phase 1|nouvelle version du Fil d’exploration]], désormais appelé « Fil d’accueil », est en cours de déploiement auprès de 50 % des utilisateurs de l’application Wikipédia pour Android. Le fil d’accueil aide le lectorat à découvrir du contenu pertinent grâce à deux nouveaux onglets : « Communauté » et « Pour vous ». L’onglet « Communauté » propose un flux défilant de contenus sélectionnés et d’actualités provenant de l’ensemble de la communauté et du mouvement Wikimédia, tandis que l’onglet « Pour vous » offre une expérience en plein écran et par glissement qui présente des contenus adaptés aux centres d’intérêt de l’utilisateur ou utilisatrice. Cette refonte s’inscrit dans le cadre d’un travail en cours visant à améliorer la découverte et à enrichir l’expérience d’apprentissage au sein de l’application Wikipédia.
* Le jeu-questionnaire quotidien [[mw:Special:MyLanguage/Wikimedia Apps/Team/iOS/"Which came first?" Game|Qu’est-ce qui est arrivé en premier ?]] est désormais disponible dans la version bêta de l’application Wikipédia pour iOS en anglais, allemand, français, portugais, russe, espagnol, arabe, chinois et turc. Le jeu s’appuie sur des événements historiques tirés de la rubrique « Éphéméride » de Wikipédia et met les lecteurs au défi de deviner lequel des deux événements s’est produit en premier. Le jeu avait déjà été lancé sur Android. Les communautés souhaitant rendre le jeu disponible dans leur langue peuvent [[mw:Special:MyLanguage/Wikimedia_Apps/Team/Games#Game availability by language|consulter les instructions et les conditions requises]].
* [[m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing|Les sous-références]], une nouvelle fonctionnalité de MediaWiki permettant aux contributeurs de réutiliser des références avec des détails différents, va commencer à être déployée sur les wikis Wikimédia après une phase pilote réussie. Le déploiement débutera le 8 juin pour la plupart des [[wikitech:Deployments/Train#Wednesday|wikis du groupe 1]] et Wikipédia en français, puis d'autres éditions linguistiques de Wikipédia bénéficieront de cette fonctionnalité au cours des prochains mois. Les communautés sont invitées à se préparer en vérifiant s’il existe des [https://translatewiki.net/w/i.php?title=Special%3ATranslate&group=ext-cite&language=en&action_source=search&filter=%21translated&optional=1&action=translate messages non traduits de l’extension Cite] dans leur langue et en passant en revue toute utilisation de l’outil [[mw:Special:MyLanguage/Reference Tooltips|Infobulles des références]], qui pourraient nécessiter des [[:phab:T416304#11668731|mises à jour]] pour prendre en charge la nouvelle fonctionnalité. Les wikis utilisant les [[mw:Special:MyLanguage/Help:Reference Previews|aperçus de référence]] n’ont aucune action à entreprendre. Les communautés peuvent également créer la [[Special:TrackingCategories|catégorie de suivi]] ''cite-tracking-category-ref-details'' en tant que catégorie cachée à l’aide de <code><nowiki>__HIDDENCAT__</nowiki></code> (ou d’un modèle dédié), et la relier à l’élément Wikidata correspondant [[d:Q129764848]]. [https://phabricator.wikimedia.org/T425662]
* L'[[mw:Special:MyLanguage/Readers/Reader Growth/Mobile page previews#Experimentation|expérience d'Aperçus de page]] sur le Web mobile a pris fin. L'équipe a décidé de ne pas déployer cette fonctionnalité après que les résultats ont montré qu'elle n'avait pas d'impact statistiquement significatif sur la fidélisation des lecteurs, l'amélioration de la fidélisation étant le principal indicateur de réussite. Les « Aperçus de page », déjà disponibles sur ordinateur et dans les applications, affichent une vignette, le premier paragraphe et un lien vers l'article complet lorsque les lecteurs cliquent sur un lien bleu. L'expérience a testé cette fonctionnalité sur le Web mobile sur six versions de Wikipédia.
* La [[mw:Special:MyLanguage/Codex/Design/Icons|bibliothèque d'icônes de l'interface utilisateur]] sera [[phab:T399175|mise à jour dans le courant de cette semaine ou la semaine prochaine]]. La plupart des quelque 300 icônes ont été légèrement peaufinées et une trentaine de nouvelles icônes ont été ajoutées. Ces modifications améliorent les icônes afin de les rendre plus cohérentes et plus compréhensibles, et d'offrir un meilleur équilibre visuel lorsqu'elles sont utilisées en groupe.
* L'interface [[mw:Special:MyLanguage/Universal Language Selector|Sélecteur universel de langue]] (ULS) de MediaWiki, qui aide les utilisateurs à sélectionner du contenu dans d'autres langues, a été mise à jour. La nouvelle version améliore la rapidité et l'accessibilité, et les utilisateurs des projets Wikimédia peuvent désormais épingler des langues pour changer de langue plus rapidement. Le déploiement sur les sites Wikimédia se fera progressivement au cours des prochaines semaines. Vous pouvez la tester dès maintenant en tant que fonctionnalité bêta en sélectionnant [[Special:Preferences#mw-prefsection-betafeatures|les fonctionnalités bêta]] dans les préférences de votre profil et partager vos commentaires sur [[mw:Special:MyLanguage/Universal Language Selector/New ULS|la page du projet]].
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:21|la tâche soumise|les {{formatnum:21}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:21||s}} la semaine dernière]]. Par exemple, un problème du le tableau de bord d'analyse des pages vues sur pageviews.wmcloud.org qui a arrêté de mettre à jour les données graphiques en mai 2026, affectant tous les utilisateurs, a été résolu. [https://phabricator.wikimedia.org/T427171]
'''Actualités pour la contribution technique'''
* La signature de la fonction <bdi lang="zxx" dir="ltr"><code><nowiki>mw.util.addPortletLink()</nowiki></code></bdi> a été simplifiée. Les développeurs peuvent désormais passer un objet de configuration à la place d'une liste de paramètres positionnels lors de la création de liens vers des portlets. L'ancienne signature de la fonction reste prise en charge à des fins de compatibilité ascendante. Par exemple, au lieu de : <bdi lang="zxx" dir="ltr"><code><nowiki>mw.util.addPortletLink('p-cactions', '#', 'Stub', 'ca-stubtag', 'Add a stub tag to this page');</nowiki></code></bdi>, utilisez <bdi lang="zxx" dir="ltr"><code><nowiki>mw.util.addPortletLink('p-cactions', { href: '#', text: 'Stub', id: 'ca-stubtag', tooltip: 'Add a stub tag to this page' });</nowiki></code></bdi>. Les responsables de la maintenance des scripts sont invités à passer en revue les utilisations existantes de <bdi lang="zxx" dir="ltr"><code><nowiki>addPortletLink()</nowiki></code></bdi> et à les mettre à jour si nécessaire. Cette modification sera disponible sur tous les wikis à partir du 11 juin. Merci à Gerges, bénévole de la communauté, d'avoir apporté cette amélioration. [https://phabricator.wikimedia.org/T427945]
* '''Discussion sur la liste de souhaits de la communauté''': les [[m:Special:MyLanguage/Community Wishlist/Updates#May 20, 2026: Community Tech becomes a program|changements introduits]] par les équipes Produit et Technologie visent à augmenter le nombre et la complexité des souhaits exaucés, notamment par la dissolution de l'équipe Community Tech. Ils [[m:Special:MyLanguage/Community Wishlist/Updates|mènent actuellement des discussions]] sur une [[m:Talk:Community Wishlist#Proposed direction for Wishlist|orientation proposée pour la liste de souhaits]] émanant des membres de la communauté. Cela inclut des moyens de structurer le vote annuel, un meilleur suivi des souhaits, la suppression de certains domaines prioritaires et des [[m:Special:MyLanguage/Community Wishlist/Updates|mises à jour concernant le personnel]].
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.6|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/24|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W24"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 8 juin 2026 à 23:29 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30650573 -->
== Actualités techniques n° 2026-25 ==
<section begin="technews-2026-W25"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/25|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* L'[[mw:Special:MyLanguage/Readers/Reader Growth|équipe chargée de la croissance du lectorat]] a lancé une fonctionnalité bêta d'[[mw:Special:MyLanguage/Readers/Reader Growth/Image Browsing|exploration des images]] sur la version mobile de toutes les Wikipédias. Cette fonctionnalité affiche un carrousel d'images en haut des articles contenant au moins trois images. Les contributeurs peuvent configurer cette fonctionnalité à l'aide des commandes suivantes : pour masquer une image spécifique sur une page, utilisez soit <code>class=notpageimage</code> pour l'exclure des aperçus miniatures, soit <code>class=noviewer</code> pour l'exclure de MediaViewer. Le carrousel peut également être désactivé complètement sur une page à l'aide du mot magique <code><nowiki>__NOMEDIAVIEWERCAROUSEL__</nowiki></code>. Pour faire des retours ou signaler des bugs, rendez-vous sur la [[mw:Talk:Readers/Reader Growth/Image Browsing|page de discussion du projet]].
* Les [[mw:Special:MyLanguage/Help:Tables#class="wikitable"|Wikitables]] peuvent désormais être [[mw:Special:MyLanguage/Help:Sortable tables#Forcing the initial sort direction|triées par ordre décroissant]] dès le premier clic en ajoutant <code dir=ltr>data-sort-order="desc"</code> à la cellule d'en-tête. Auparavant, par défaut, cliquer une première fois sur l'en-tête d'une colonne entraînait un tri par ordre croissant. Cette nouveauté offre davantage de contrôle et de flexibilité pour les Wikitables, tandis que le comportement par défaut pour les clics suivants reste inchangé. [https://phabricator.wikimedia.org/T398416]
'''Actualités pour la contribution'''
* La fonctionnalité d'[[mw:Special:MyLanguage/Article guidance|Aide à la rédaction d'articles]] est actuellement en phase de test auprès de certains contributeurs qui créent de nouveaux articles sur les Wikipédias en anglais simplifié, en français et en turc. L'expérience débutera bientôt sur les Wikipédias en arabe et en bengali également. [[w:simple:Special:NewArticle|Cette fonctionnalité]] fournit aux contributeurs des conseils élaborés par la communauté afin de les aider à créer des articles conformes aux normes communautaires. Les contributeurs expérimentés peuvent continuer à créer ou à adapter des modèles pour des types d'articles spécifiques qui sont couramment créés par des contributeurs moins expérimentés. Ces modèles guident les contributeurs moins expérimentés dans la création d'articles de haute qualité. Un guide rapide des balises utilisées dans les modèles est disponible sur [[mw:Special:MyLanguage/Article guidance/Test feature guide#Markups in outlines|cette page]]. [[w:fr:Projet:Aide à la rédaction d'articles#Liste de plans d'aide à la rédaction|Des exemples de modèles]] pouvant être adaptés, ainsi que des instructions sur la manière de les adapter, se trouvent dans [[mw:Special:MyLanguage/Article guidance#Adapting a sample outline in a Wikipedia|cette section]] de la page du projet.
* Les wikis qui souhaitent remplacer le bouton « indéfiniment » dans la page Special:Block pour les comptes temporaires (par exemple, les wikis qui bloquent les utilisateurs temporaires uniquement jusqu'à l'expiration de leur compte) pourront le faire en créant [[MediaWiki:ipb-indefinite-expiry-temporary-account]] avec la durée de blocage souhaitée. [https://phabricator.wikimedia.org/T427125]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:41|la tâche soumise|les {{formatnum:41}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:41||s}} la semaine dernière]].
'''Actualités pour la contribution technique'''
* D'ici la fin du mois de juin, une chaîne « user-agent » valide sera requise pour les téléchargements automatisés de sauvegardes depuis le site dumps.wikimedia.org. Les requêtes automatisées fournissant une chaîne « user-agent » générique ou vide seront bloquées. Cette mesure [[phab:T400119|renforce l'application]] de la [[foundation:Special:MyLanguage/Policy:Wikimedia Foundation User-Agent Policy|politique relative à l'agent utilisateur]] en vigueur depuis longtemps. L'accès aux sauvegardes via Wikimedia Cloud Services restera inchangé.
* La mise en place des [[mw:Wikimedia APIs/Rate limits|limites de débit des API]] à l'échelle mondiale est désormais achevée ; ces limites s'appliquent à toutes les API et sont fixées aux niveaux indiqués dans la documentation pour tous les groupes. Les bots fonctionnant sur Toolforge/WMCS ou disposant du droit d'utilisateur « bot » sur n'importe quel wiki restent exemptés. Tous les bots doivent continuer à respecter les bonnes pratiques décrites dans la documentation afin d'éviter d'être soumis à des limites de débit.
* Le [https://api.wikimedia.org/wiki/Main_Page wiki du portail API] sera en lecture seule à partir de cette semaine (du 15 au 18 juin). La semaine suivante (du 22 au 25 juin), toutes les URL du wiki du portail API redirigeront vers [[mw:Wikimedia APIs|les API Wikimedia sur mediawiki.org]]. Pour en savoir plus, consultez la [[wikitech:API Portal/Deprecation|page du projet]].
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.7|MediaWiki]]
'''Rencontres et évènements'''
* Le 17 juin à 18 h (UTC), la WMF organisera une réunion sur Discord consacrée à la revue de code. L'[[mw:Special:MyLanguage/Developer Satisfaction Survey/2026|enquête sur la satisfaction des développeurs]] nous a permis de constater que les bénévoles rencontrent des difficultés avec la revue de code, et nous souhaitons discuter de ces expériences afin de trouver des solutions concrètes. Vous pouvez rejoindre la réunion [https://discord.gg/wikipedia?event=1514727511102062664 via le serveur Discord de la communauté Wikimedia].
* La [[m:Special:MyLanguage/Conferencia Wikimedia de América Latina 2026|Conférence Wikimedia d'Amérique latine]] organisera un hackathon régional qui réunira la communauté technique du mouvement Wikimedia, notamment des développeurs, des administrateurs système, des data scientists et des utilisateurs disposant de droits étendus. Les contributeurs techniques intéressés peuvent [https://docs.google.com/forms/d/e/1FAIpQLSf4osJzTHBJjQbYJk7TMVEJjTEQv7IgtsUDfP-o-qTgeRQQxw/viewform postuler à une bourse] pour y participer jusqu'au 21 juin à minuit (heure de la Bolivie, UTC-4).
* Inscrivez-vous aux Wikimania Team Challenges pour participer à cet événement exceptionnel. Les défis par équipe se dérouleront en ligne et en présentiel les 21 et 22 juillet, avant la conférence Wikimania. Tout le monde est le bienvenu, quelles que soient ses compétences ou son inscription à Wikimania. Les équipes travailleront sur 10 défis importants visant à soutenir la communauté Wikimedia. Pour plus de détails, rendez-vous sur [[wmania:Special:MyLanguage/2026:Team challenges|la page des défis par équipe]] et [https://wikimedia.eventyay.com/wm/teamchallenges/ inscrivez-vous ici]. Les inscriptions se terminent le 20 juin à 23 h UTC.
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/25|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W25"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 15 juin 2026 à 18:48 (CEST)
<!-- Message envoyé par User:UOzurumba (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30689604 -->
== Actualités techniques n° 2026-26 ==
<section begin="technews-2026-W26"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/26|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* Les [[mw:Special:MyLanguage/Growth/Feature summary|fonctionnalités de croissance]] sont [[phab:T418115|désormais disponibles sur Wikidata]]. Cette mise à jour permet d'accéder au mentorat ([[mw:Special:MyLanguage/Help:Growth/Mentorship|s'il est configuré]]), au module Impact, au panneau d'aide et à une page d'accueil simplifiée pour les nouveaux arrivants (sans les suggestions de modifications). Les administrateurs de Wikidata continuent de paramétrer ces fonctionnalités via la configuration communautaire.
'''Actualités pour la contribution'''
* La page spéciale [[{{#special:RangeCalculator}}]] a été créée. Elle permet aux utilisateurs de trouver une plage d'adresses IP sans avoir à recourir à des outils externes. Jusqu'à présent, cet outil n'était accessible qu'aux CheckUsers. [https://phabricator.wikimedia.org/T268429]
* Les [[m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing|sous-références]] sont une nouvelle fonctionnalité de MediaWiki qui permet aux contributeurs de réutiliser des références en modifiant certains détails. Elle sera déployée le 23 juin, sur la plupart des versions de Wikipédia de petite et moyenne taille. La [[m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing#deployment|FAQ]] répertorie les mesures à prendre sur votre wiki pour faciliter ce déploiement. Consultez le [[:phab:T414094|plan de déploiement]] pour connaître les prochaines étapes. [https://phabricator.wikimedia.org/T428902]
* À partir de la semaine prochaine, les utilisateurs recevront une notification lorsqu'ils seront bloqués ou débloqués pour l'édition, ou si ce blocage venait à changer. [https://phabricator.wikimedia.org/T100974]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:32|la tâche soumise|les {{formatnum:32}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:32||s}} la semaine dernière]].
'''Actualités pour la contribution technique'''
* À partir de la semaine prochaine, les filtres anti-abus configurés pour « exiger une vérification par CAPTCHA » s'appliqueront également aux utilisateurs disposant du droit <code>skipcaptcha</code>, ce qui inclut la plupart des utilisateurs auto-confirmés. Les bots en sont exemptés. Ce changement ne concerne que les modifications qui déclenchent un filtre anti-abus. Le droit <code>skipcaptcha</code> continuera à exempter les utilisateurs de l'obligation de résoudre des CAPTCHA dans le cadre d'une utilisation normale des wikis. [https://phabricator.wikimedia.org/T402595]
* La documentation de référence relative à l'[[wikitech:Machine_Learning/LiftWing/API|API Lift Wing]] a été déplacée du portail API vers le [https://wikitech.wikimedia.org/w/index.php?api=lift-wing&title=Special%3ARestSandbox bac à sable REST] interactif.
* Le wiki du Portail API est désormais fermé. Pour consulter la documentation relative aux API, rendez-vous sur [[mw:Special:MyLanguage/Wikimedia_APIs|Wikimedia APIs sur mediawiki.org]]. À compter du 22 juin, toutes les URL du wiki du Portail API (https://api.wikimedia.org/wiki/) redirigeront vers la page de mediawiki.org. [https://phabricator.wikimedia.org/T427537]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.8|MediaWiki]]
'''Rencontres et évènements'''
* Participez à une visioconférence le 25 juin à 14 h 30 UTC pour rencontrer les stagiaires actuels de Wikimédia participant au [[mw:Google_Summer_of_Code/2026|Google Summer of Code]] et à [[mw:Outreachy/Round_32|Outreachy]]. Les stagiaires présenteront leurs projets et feront une brève démonstration du travail qu'ils ont réalisé jusqu'à présent. Les participants sont invités à [[mw:event:Google_Summer_of_Code/Summer_2026_June_Internship_open_session|partager leurs idées et leurs contacts au sein de leur communauté]].
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/26|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W26"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 23 juin 2026 à 15:05 (CEST)
<!-- Message envoyé par User:Trizek (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30722494 -->
== RFC about AI-generated content in Wikimedia Commons ==
<bdi lang="en" dir="ltr">Apologies for writing in English, please help translate this message to your language. You are invited to participate in a [[c:Commons:Requests for comment/Policy update for AI content|request for comment on Wikimedia Commons about a policy update for AI content]]. This may affect files that are uploaded to Wikimedia Commons for use on this project. Thank you. [[m:User:Codename Noreste|Codename Noreste]] ([[m:User talk:Codename Noreste|discussion]])</bdi> 23 juin 2026 à 19:11 (CEST)
<!-- Message envoyé par User:Codename Noreste@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=30513860 -->
== Intégration du lien vers les contacts juridiques et de sécurité dans le pied de page de votre wiki ==
<section begin="Message"/>
'''Contacts juridiques et de sécurité'''
Bonjour à toute la communauté, la Fondation Wikimedia a mis à disposition une [[wmf:Special:MyLanguage/Legal:Wikimedia Foundation Legal and Safety Contact Information|page unique dédiée aux mentions légales et à la sécurité]], à ajouter en pied de page de votre wiki, afin de garantir l'accès à des informations juridiques exactes. Il s'agit d'une exigence réglementaire. Nous avons déjà mis en place des liens vers les wikis en anglais, allemand, italien, espagnol et d'autres langues de Wikipedia, et nous les déploierons bientôt sur votre wiki. Pour en savoir plus, [[m:Special:MyLanguage/Wikimedia_Foundation_Legal_and_Safety_Contacts_FAQ|consultez la page du projet]] et n'hésitez pas à laisser vos commentaires dans ce fil de discussion ou sur la [[m:Special:MyLanguage/Talk:Wikimedia Foundation Legal and Safety Contacts FAQ|page de discussion]].
<section end="Message"/>
-- [[User:Sannita (WMF)|User:Sannita (WMF)]] ([[User talk:Sannita (WMF)|talk]]) 25 juin 2026 à 15:30 (CEST)
<!-- Message envoyé par User:Sannita (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=User:Sannita_(WMF)/Mass_sending_test&oldid=30731267 -->
gevulzeakgonu9d1uljd82ajlvh6cbp
Dictionnaire de philosophie/Métaphysique
0
83489
768686
768241
2026-06-26T04:31:41Z
PandaMystique
119061
Bot : ajout du paramètre lecture=oui au modèle {{DicoPhilo}}
768686
wikitext
text/x-wiki
{{DicoPhilo|Métaphysique|lecture=oui}}
[[Fichier:Sanzio 01 Plato Aristotle.jpg|vignette|Platon et Aristote, détail de ''L'École d'Athènes'' de Raphaël. La métaphysique occidentale prend forme avec la philosophie grecque.]]
La métaphysique est la branche de la philosophie qui étudie les principes fondamentaux de la réalité, la nature de l'être en tant qu'être, et les structures les plus générales de ce qui existe. Elle s'interroge sur des questions que les sciences particulières ne peuvent traiter : qu'est-ce qui existe véritablement ? Quelle est la nature ultime des choses ? Quelles sont les catégories fondamentales de l'être ? Comment se rapportent l'esprit et la matière, la nécessité et la contingence, le temps et l'éternité ?
La métaphysique se distingue des sciences empiriques par sa méthode et par l'extension de ses questions. Là où le physicien étudie les propriétés de la matière et de l'énergie, le métaphysicien demande ce qu'est fondamentalement la matière, ce que sont les propriétés elles-mêmes, et dans quelle mesure les entités postulées par la physique existent réellement. Là où le biologiste étudie les organismes vivants, le métaphysicien s'interroge sur ce qui distingue un être vivant d'un simple agrégat de particules, et sur ce qui fait qu'un organisme reste le même au fil du temps malgré le renouvellement de ses parties.
Contrairement à une image répandue, la métaphysique contemporaine n'est pas une entreprise purement spéculative, détachée des connaissances scientifiques. Beaucoup de débats métaphysiques actuels sont étroitement liés aux sciences : la philosophie du temps dialogue avec la physique relativiste, le problème du rapport entre l'esprit et le corps avec les neurosciences, les questions sur la [[Dictionnaire de philosophie/Causalité|causalité]] avec la statistique et l'épidémiologie. La métaphysique conserve néanmoins son autonomie : elle pose des questions que la science ne peut trancher seule, parce qu'elles portent sur l'interprétation des résultats scientifiques ou sur des concepts que la science emploie sans les analyser.
Cet article présente les grands problèmes de la métaphysique selon une organisation thématique plutôt que chronologique, afin de montrer comment des questions posées depuis l'Antiquité continuent d'animer les débats philosophiques contemporains. Il puise dans les traditions analytique et continentale, tout en offrant ponctuellement des contrepoints issus d'autres traditions philosophiques, notamment africaines, indiennes et chinoises.
== Nature et définition de la métaphysique ==
=== Origine du terme ===
[[Fichier:Aristotle Altemps Inv8575.jpg|vignette|Buste d'Aristote. Le mot « métaphysique » provient du classement de ses traités par les éditeurs de l'Antiquité.]]
Le mot « métaphysique » provient du grec {{lang|grc|τὰ μετὰ τὰ φυσικά}} (''ta meta ta physika''), expression qui signifie littéralement « ce qui vient après les choses physiques ». Cette appellation n'est pas d'[[Dictionnaire de philosophie/Aristote|Aristote]] lui-même : selon le récit traditionnel, elle remonte aux éditeurs qui, au {{s-|I|er}} av. J.-C., ordonnèrent les traités du Stagirite et placèrent quatorze textes consacrés aux sujets les plus fondamentaux à la suite des livres portant sur la nature ({{lang|grc|τὰ φυσικά}})<ref>{{Ouvrage|auteur1=Jaegwon Kim|auteur2=Ernest Sosa|auteur3=Gary S. Rosenkrantz|titre=A Companion to Metaphysics|éditeur=Wiley-Blackwell|année=2009|passage=414|citation=The name "metaphysics" goes back to the Aristotelian editors of the first century BC.}}</ref>. Le préfixe ''meta-'', qui signifie d'abord « après », fut ensuite compris au sens de « au-delà » : la métaphysique devint ainsi l'étude de ce qui se tient au-delà du monde sensible. Le grec autorise ce double sens, et le glissement de l'une à l'autre acception a durablement marqué la façon dont la discipline a été comprise.
Aristote lui-même désignait cette discipline de plusieurs manières : « sagesse » ({{lang|grc|σοφία}}), « philosophie première » ({{lang|grc|πρώτη φιλοσοφία}}), ou encore « théologie » ({{lang|grc|θεολογία}}). Ces appellations renvoient à trois tâches qu'il assignait à cette science : l'étude des premiers principes et des causes, la science de l'être en tant qu'être, et la connaissance du divin<ref>{{Ouvrage|auteur1=Kim, Sosa, Rosenkrantz|titre=A Companion to Metaphysics|passage=414|citation=Aristotle gives three different, not obviously equivalent accounts of the task of first philosophy. In Book A it is the science of first principles and causes, in Book Γ the science of being as being, and in Books Λ and E it is called theology.}}</ref>. Ces trois descriptions ne se recouvrent pas de façon évidente, et leur articulation a nourri d'innombrables commentaires. Le raisonnement d'Aristote peut se reconstituer ainsi : connaître une chose, c'est connaître ses causes ; la science la plus haute porte donc sur les causes premières de tout ce qui est, c'est-à-dire sur l'être considéré en tant qu'être ; or l'être au sens premier est la substance divine, immobile et séparée de la matière ; l'étude de l'être culmine par conséquent dans la connaissance du divin. La science des premiers principes, la science de l'être et la théologie apparaissent alors comme trois aspects d'une même enquête.
=== Définitions et tâches ===
Il n'existe pas de définition universellement acceptée de la métaphysique. Plusieurs caractérisations ont été proposées au cours de l'histoire de la philosophie.
La première en fait l'étude de l'être en tant qu'être. C'est la caractérisation aristotélicienne classique. Aristote estimait que le mot « être » ne se dit pas de la même façon de toutes choses : ce que signifie « exister » pour un cheval n'est pas ce que cela signifie pour un nombre, car ils n'ont pas le même mode d'être. La métaphysique, selon cette conception, cherche à dégager les différentes catégories ou genres de l'être<ref>{{Ouvrage|auteur1=Michael Rea|titre=Metaphysics: The Basics|éditeur=Routledge|année=2020|passage=3|citation=Aristotle's idea was that "being" and related words (like "is" or "exists") mean one thing when predicated of a horse or a cat and something else when predicated of a number.}}</ref>. Aristote éclaire cette idée par une comparaison : le mot « sain » n'a pas le même sens selon qu'on l'applique à un repas, à un teint ou à un être vivant. Dans un cas il désigne ce qui produit la santé, dans un autre ce qui en est le signe, dans un autre encore ce qui la possède. De même, « être » varie de sens selon le type d'entité auquel on l'attribue, sans pour autant se disperser en significations sans rapport : tous ces sens se rapportent à une réalité première, la [[Dictionnaire de philosophie/Substance|substance]].
Cette caractérisation a toutefois ses limites : la plupart des philosophes contemporains estiment que la métaphysique couvre bien davantage que l'étude de l'être en tant qu'être. Les questions sur la compatibilité de la [[Dictionnaire de philosophie/Liberté|liberté]] et du [[Dictionnaire de philosophie/Déterminisme|déterminisme]], sur la nature du lien causal ou sur l'existence d'objets abstraits passent pour métaphysiques, alors même qu'elles ne portent pas directement sur l'être comme tel<ref>{{Ouvrage|auteur1=Michael Rea|titre=Metaphysics: The Basics|passage=4}}.</ref>.
Une deuxième caractérisation présente la métaphysique comme l'étude de la réalité ultime, de ce qui existe « réellement ». Cette formulation soulève cependant une difficulté : pourquoi apprendrait-on ce qui existe vraiment en métaphysique plutôt qu'en botanique, en zoologie ou en physique théorique ? Et pourquoi tient-on pour métaphysique la question de savoir s'il existe des nombres ou des ensembles, mais non celle de savoir s'il existe des licornes<ref>{{Ouvrage|auteur1=Michael Rea|titre=Metaphysics: The Basics|passage=4-5}}.</ref> ? Ces objections montrent que l'idée de « réalité ultime » ne suffit pas à délimiter la discipline : il faut préciser ce qui distingue sa démarche de celle des sciences empiriques.
Une troisième caractérisation, proposée par Michael Rea, se veut plus englobante : la métaphysique serait l'étude non empirique de ce qui existe, de ce qui doit exister ou pourrait exister, et des natures ou essences des choses<ref>{{Ouvrage|auteur1=Michael Rea|titre=Metaphysics: The Basics|passage=10|citation=metaphysics is the non-empirical study of what there is, of what there must be, of what there could be, and of the natures or essences of things.}}</ref>. Cette définition retient quatre traits : la métaphysique procède de manière non empirique ; elle porte en partie sur ce qui existe ; elle porte aussi sur les essences ou natures des choses ; enfin, elle s'intéresse à ce qui est possible, nécessaire ou impossible. Le trait le plus important est sans doute le caractère non empirique : il sépare la métaphysique des sciences de la nature, qui répondent à leurs questions par l'observation et l'expérience.
=== Divisions historiques ===
Au fil des siècles, les philosophes ont cherché à organiser la métaphysique en branches ou sous-disciplines. Ces classifications aident à distinguer les types de questions que pose la métaphysique et montrent comment la discipline s'est structurée peu à peu.
==== L'héritage scolastique et la systématisation de Suárez ====
Avant d'en venir aux classifications modernes, il faut rappeler leur arrière-plan médiéval. Les philosophes scolastiques, en particulier [[Dictionnaire de philosophie/Thomas d'Aquin|Thomas d'Aquin]] (1225-1274) et [[Dictionnaire de philosophie/Jean Duns Scot|Jean Duns Scot]] (1266-1308), avaient développé des analyses métaphysiques élaborées dans le cadre de leurs commentaires d'Aristote. C'est toutefois le jésuite espagnol [[Dictionnaire de philosophie/Francisco Suárez|Francisco Suárez]] (1548-1617) qui, dans ses ''Disputationes Metaphysicae'' (1597), proposa le premier grand traité de métaphysique organisé par thèmes plutôt que par commentaire suivi des textes aristotéliciens. Suárez ordonna la matière autour de l'étude de l'être en général et de ses divisions, ouvrant la voie aux classifications ultérieures<ref>{{Ouvrage|auteur1=Jorge J. E. Gracia|titre=Francisco Suárez|lire en ligne=https://plato.stanford.edu/archives/spr2022/entries/suarez/|année=2019}}.</ref>.
==== La classification de Christian Wolff ====
[[Fichier:Portret van Christian Wolff, RP-P-1917-968.jpg|vignette|Christian Wolff, qui sépara la métaphysique générale, ou ontologie, des trois métaphysiques spéciales.]]
Le philosophe allemand [[Dictionnaire de philosophie/Christian Wolff|Christian Wolff]] (1679-1754), héritier de cette tradition, introduisit une division devenue classique, qui structura l'enseignement de la métaphysique dans les universités européennes pendant plus d'un siècle. Il distinguait la métaphysique générale de la métaphysique spéciale<ref>{{Ouvrage|auteur1=Kim, Sosa, Rosenkrantz|titre=A Companion to Metaphysics|passage=414}}.</ref>.
La métaphysique générale, que Wolff nomma « [[Dictionnaire de philosophie/Ontologie|ontologie]] », étudie l'être en tant qu'être. Elle s'interroge sur ce qui est commun à tout ce qui existe, indépendamment du type particulier de chose considéré : qu'est-ce qu'exister ? quelles sont les catégories fondamentales de l'être, comme la substance, la qualité ou la relation ? Ces questions sont dites générales parce qu'elles valent pour n'importe quelle entité, qu'il s'agisse d'une pierre, d'un nombre, d'une pensée ou de Dieu.
La métaphysique spéciale se divise quant à elle en trois branches, chacune consacrée à un domaine particulier de la réalité<ref>{{Ouvrage|auteur1=Michael Rea|titre=Metaphysics: The Basics|éditeur=Routledge|année=2020|passage=2-3}}.</ref>.
La théologie rationnelle, ou théologie naturelle, est la science de Dieu. Elle cherche à établir, par la seule raison et sans recourir à la révélation, si Dieu existe, quelle est sa nature et quels sont ses attributs (omniscience, toute-puissance, bonté). Les preuves classiques de l'existence de Dieu relèvent de cette branche : l'[[Dictionnaire de philosophie/Argument ontologique|argument ontologique]], qui prétend tirer l'existence de Dieu de son seul concept ; l'[[Dictionnaire de philosophie/Argument cosmologique|argument cosmologique]], qui remonte des effets observés à une cause première ; l'[[Dictionnaire de philosophie/Argument téléologique|argument téléologique]], qui infère un auteur intelligent à partir de l'ordre du monde<ref>{{Ouvrage|auteur1=Kim, Sosa, Rosenkrantz|titre=A Companion to Metaphysics|passage=205-210}}.</ref>.
La psychologie rationnelle est la science de l'âme. Elle porte sur la nature de l'esprit humain : l'âme est-elle une substance distincte du corps ? est-elle immortelle ? comment agit-elle sur le corps et en reçoit-elle des impressions ? Ces questions, bien qu'elles concernent l'esprit, ne relèvent pas de la psychologie scientifique, qui étudie le comportement et les processus mentaux par l'observation et l'expérience ; elles procèdent d'une réflexion ''a priori'', indépendante de l'expérience.
La cosmologie rationnelle est la science du monde pris comme totalité. Elle pose des questions sur l'univers dans son ensemble : le monde a-t-il commencé dans le temps ou existe-t-il de toute éternité ? est-il fini ou infini dans l'espace ? est-il composé de parties simples ou divisible à l'infini ? Ces questions se distinguent de celles de la physique ou de l'astronomie, car elles portent sur la structure la plus générale du cosmos, non sur ses propriétés mesurables.
Cette triple division, qui répartit la métaphysique spéciale entre Dieu, l'âme et le monde, reflète de grandes préoccupations de la philosophie occidentale depuis l'Antiquité. Elle traduit aussi l'ambition de la métaphysique classique : connaître par la seule raison des réalités qui échappent à l'expérience sensible.
==== La critique kantienne ====
[[Dictionnaire de philosophie/Emmanuel Kant|Emmanuel Kant]] (1724-1804) reprit cette structure, mais la transforma en profondeur au nom de sa philosophie critique. Dans la ''Critique de la raison pure'' (1781), il soutint que la métaphysique spéciale, lorsqu'elle prétend connaître des réalités situées au-delà de toute expérience possible, se heurte à des difficultés insurmontables<ref>{{Ouvrage|auteur1=Kim, Sosa, Rosenkrantz|titre=A Companion to Metaphysics|passage=414-415}}.</ref>.
Cette critique prend une forme différente pour chacune des trois branches. À la cosmologie rationnelle, Kant oppose des « antinomies » : des couples de propositions contradictoires, par exemple « le monde a un commencement » et « le monde n'a pas de commencement », qui semblent toutes deux démontrables par la raison pure. À la psychologie rationnelle, il reproche des « paralogismes » : des raisonnements trompeurs qui prétendent établir que l'âme est une substance simple et immortelle à partir du seul fait de penser. À la théologie rationnelle, il oppose une critique des preuves de l'existence de Dieu, soutenant qu'elles reposent toutes, en dernier ressort, sur l'argument ontologique, qu'il tient lui-même pour invalide<ref>{{Ouvrage|auteur1=Emmanuel Kant|titre=Critique de la raison pure|passage=Dialectique transcendantale|traducteur=Alain Renaut|éditeur=GF Flammarion|année=2006}}.</ref>.
Kant introduit une distinction importante entre métaphysique transcendante et métaphysique immanente. Est transcendante une prétention de connaissance qui porte sur ce qui dépasse toute expérience possible : Dieu, l'âme conçue comme substance simple, le monde pris comme totalité. Est immanente une connaissance qui demeure dans les limites de l'expérience possible. Pour Kant, seule une métaphysique immanente, entendue comme étude des conditions qui rendent l'expérience possible, peut prétendre à une connaissance légitime ; la métaphysique transcendante, elle, excède les pouvoirs de notre raison.
Cette critique a durablement marqué l'histoire de la discipline. Elle explique la défiance que beaucoup de philosophes ont nourrie envers la métaphysique traditionnelle au {{s-|XIX|e}} et au début du {{s-|XX|e}}. Elle a pourtant été contestée. [[Dictionnaire de philosophie/Hegel|Hegel]] a tenté de restaurer une métaphysique spéculative capable de surmonter les bornes que Kant lui avait fixées. Et au {{s-|XX|e}}, le retour de la métaphysique dans la tradition analytique, avec [[Dictionnaire de philosophie/Willard Van Orman Quine|Willard Van Orman Quine]], [[Dictionnaire de philosophie/Saul Kripke|Saul Kripke]] et [[Dictionnaire de philosophie/David Lewis|David Lewis]], a montré qu'une réflexion exigeante sur les structures fondamentales de la réalité restait possible et féconde, à condition de prendre des formes différentes de celles de la métaphysique wolffienne.
==== La distinction husserlienne ====
[[Fichier:Edmund Husserl 1900.jpg|vignette|Edmund Husserl, fondateur de la phénoménologie, distingua l'ontologie formelle des ontologies régionales.]]
Au {{s-|XX|e}}, [[Dictionnaire de philosophie/Edmund Husserl|Edmund Husserl]] (1859-1938), fondateur de la [[Dictionnaire de philosophie/Phénoménologie|phénoménologie]], proposa une autre manière d'ordonner les questions métaphysiques. Dans ses ''Idées directrices pour une phénoménologie'' (1913), il distingua l'ontologie formelle des ontologies matérielles, ou régionales<ref>{{Ouvrage|auteur1=Edmund Husserl|titre=Idées directrices pour une phénoménologie|passage=§§ 9-17|traducteur=Paul Ricœur|éditeur=Gallimard|année=1950}}.</ref>.
L'ontologie formelle relève de la logique pure. Elle étudie les structures qui s'appliquent à tout objet possible en tant qu'objet, quelle que soit sa nature : les notions d'objet en général, d'état de choses, de propriété, de relation, de partie et de tout, d'unité et de pluralité. Ces notions sont dites formelles parce qu'elles ne déterminent aucun contenu : elles valent aussi bien pour une table que pour un nombre, pour une émotion que pour une galaxie. Demander « qu'est-ce qu'une relation ? » ou « qu'est-ce qu'un tout par rapport à ses parties ? », c'est faire de l'ontologie formelle.
Les ontologies matérielles, ou régionales, étudient au contraire les concepts et les principes propres à chaque « région » de l'être. Une région, au sens de Husserl, n'est pas un simple domaine thématique : c'est un ensemble d'objets qui partagent une même essence générique, c'est-à-dire une même structure fondamentale. Husserl distingue plusieurs régions : la nature physique, caractérisée par l'étendue spatiale et la causalité ; la conscience, caractérisée par l'[[Dictionnaire de philosophie/Intentionnalité|intentionnalité]], c'est-à-dire par le fait d'être toujours dirigée vers un objet ; les objets mathématiques ; et d'autres encore. Chaque région appelle une ontologie spécifique : l'ontologie de la nature examine les concepts de matière, d'espace, de temps et de causalité physique ; l'ontologie de la conscience, ceux d'intentionnalité, de vécu et de temporalité immanente.
Cette distinction permet de concevoir la métaphysique comme un ensemble articulé de recherches, et non comme un bloc indivis. Elle a marqué l'organisation des travaux philosophiques contemporains : on parle aujourd'hui de philosophie de l'esprit, de philosophie de la physique ou de philosophie des mathématiques comme de domaines ayant chacun ses problèmes propres, tout en partageant des questions communes qui relèvent de l'ontologie formelle. L'approche husserlienne a néanmoins été critiquée, notamment pour le statut qu'elle accorde aux essences régionales, où certains ont vu le retour d'un platonisme contestable.
=== Métaphysique et ontologie ===
Les termes « métaphysique » et « ontologie » sont souvent employés l'un pour l'autre, ce qui prête à confusion. Il vaut donc la peine de préciser leur rapport, en tenant compte du fait que les usages varient selon les traditions.
==== Origine et sens du terme « ontologie » ====
Le mot « ontologie » vient du grec ''on, ontos'' (« être », « étant ») et ''logos'' (« discours », « étude ») : il désigne littéralement le discours ou l'étude de l'être. Le terme apparaît au début du {{s-|XVII|e}}, chez Rudolf Goclenius dans son ''Lexicon philosophicum'' (1613), et fut systématisé par Christian Wolff au siècle suivant<ref>{{Ouvrage|auteur1=Kim, Sosa, Rosenkrantz|titre=A Companion to Metaphysics|passage=414}}.</ref>. On en trouve toutefois un emploi un peu antérieur chez le philosophe allemand Jacob Lorhard, dès 1606.
L'ontologie, au sens classique, désigne l'étude de l'être en tant qu'être, c'est-à-dire l'étude de ce qui est commun à tout ce qui existe, indépendamment des traits particuliers de chaque type d'entité. Elle correspond à ce que Wolff appelait la métaphysique générale, par opposition aux métaphysiques spéciales (théologie, psychologie et cosmologie rationnelles).
==== Différentes manières de distinguer métaphysique et ontologie ====
Les distinctions proposées entre métaphysique et ontologie ne sont pas des théories rivales : ce sont plutôt des manières différentes de tracer une frontière entre deux termes qui se recoupent largement<ref>{{Ouvrage|auteur1=Michael Rea|titre=Metaphysics: The Basics|éditeur=Routledge|année=2020|passage=10-11}}.</ref>.
Une première manière fait de l'ontologie une partie de la métaphysique : celle qui traite des questions d'existence. Y a-t-il des [[Dictionnaire de philosophie/Universaux|universaux]], des nombres, des propositions, des événements ? La métaphysique, plus large, comprendrait aussi des questions qui ne portent pas seulement sur l'existence, comme celle de savoir si le temps « passe » réellement, ou en quoi consiste la causalité.
Une deuxième manière distingue deux questions complémentaires. L'ontologie demanderait « qu'y a-t-il ? » : elle dresse l'inventaire de ce qui existe. La métaphysique demanderait en outre « quelle est la nature de ce qui existe ? » : elle étudie la structure et les propriétés des entités recensées. La question « qu'y a-t-il ? » a été remise à l'honneur par Willard Van Orman Quine, puis reprise par [[Dictionnaire de philosophie/Peter van Inwagen|Peter van Inwagen]], qui en a fait le point de départ d'une réflexion sur la méthode propre de l'ontologie<ref>{{Ouvrage|auteur1=Peter van Inwagen|titre=Ontology, Identity, and Modality|éditeur=Cambridge University Press|année=2001|passage=chapitre « Meta-ontology »}}.</ref>.
Ces partages restent souples. Quelques exemples permettent de saisir l'idée, même si la frontière n'est jamais parfaitement nette. « Les nombres existent-ils ? » est une question proprement ontologique, puisqu'elle porte sur l'existence d'un type d'entités. « Qu'est-ce qu'un nombre ? » est une question métaphysique : elle a une part ontologique (de quel mode d'être relèvent les nombres ?), mais elle interroge aussi leur nature. « Le temps est-il réel ou illusoire ? » mêle les deux aspects, l'existence du temps et sa nature. « La liberté est-elle compatible avec le déterminisme ? » paraît moins directement ontologique, même si elle suppose des prises de position sur la nature des lois, de la causalité et de l'action humaine.
Une troisième manière, enfin, renonce à séparer nettement les deux termes. Dans la pratique contemporaine, en [[Dictionnaire de philosophie/Philosophie analytique|philosophie analytique]] surtout, « métaphysique » et « ontologie » s'emploient souvent comme des quasi-synonymes. Cet usage souple ne prive pas de valeur les distinctions précédentes ; il signale seulement que le contexte suffit d'ordinaire à lever les ambiguïtés.
==== La notion d'engagement ontologique ====
Une contribution importante à la clarification de l'ontologie revient à Willard Van Orman Quine. Dans son article « On What There Is » (1948), il propose un critère pour déterminer l'ontologie d'une théorie : une théorie est « engagée » envers les entités sur lesquelles portent ses quantifications, c'est-à-dire celles dont elle affirme l'existence<ref>{{Ouvrage|auteur1=Willard Van Orman Quine|titre=From a Logical Point of View|éditeur=Harvard University Press|année=1953|passage=1-19}}.</ref>.
Selon sa formule célèbre, « être, c'est être la valeur d'une variable liée » (''to be is to be the value of a bound variable''). Pour savoir à quoi une théorie s'engage, il faut donc examiner ce que ses énoncés, une fois mis en forme logique, affirment exister. Une théorie physique qui pose « il existe des électrons » s'engage envers les électrons ; une théorie mathématique qui pose « il existe des nombres premiers supérieurs à cent » s'engage envers les nombres.
Cette notion d'engagement ontologique (''ontological commitment'') est devenue centrale dans la métaphysique analytique. Elle permet de formuler la question ontologique avec précision : quelles entités faut-il admettre pour rendre compte de nos meilleures théories scientifiques et de notre discours ordinaire ?
==== Usages selon les traditions philosophiques ====
L'usage des deux termes varie sensiblement d'une tradition à l'autre.
En philosophie analytique, on emploie « métaphysique » comme terme générique pour l'ensemble des questions sur la structure fondamentale de la réalité, et l'on réserve souvent « ontologie » à des contextes plus techniques : l'ontologie d'une théorie désigne les entités dont cette théorie suppose l'existence. Un débat dit « méta-ontologique » porte aujourd'hui sur la portée des disputes ontologiques : sont-elles substantielles, ou seulement verbales ? [[Dictionnaire de philosophie/Eli Hirsch|Eli Hirsch]] défend une position « déflationniste », selon laquelle bien des désaccords ontologiques ne sont que des différences de langage ; [[Dictionnaire de philosophie/Theodore Sider|Theodore Sider]] soutient au contraire que ces questions ont des réponses objectives<ref>{{Ouvrage|auteur1=Theodore Sider|titre=Writing the Book of the World|éditeur=Oxford University Press|année=2011|passage=1-12}}.</ref>.
En [[Dictionnaire de philosophie/Philosophie continentale|philosophie continentale]], et particulièrement dans la tradition phénoménologique, « ontologie » garde souvent un sens plus technique et plus ambitieux. Dans ''Être et Temps'' (1927) et ses œuvres ultérieures, [[Dictionnaire de philosophie/Martin Heidegger|Martin Heidegger]] oppose l'ontologie, entendue comme la question du sens de l'être, à la métaphysique, à laquelle il reproche d'avoir « oublié » cette question pour s'attacher aux ''étants'', les choses qui sont. La métaphysique occidentale, d'Aristote à Nietzsche, aurait confondu l'être avec un étant suprême (Dieu, la substance, le sujet), et manqué ainsi la question proprement ontologique<ref>{{Ouvrage|auteur1=Martin Heidegger|titre=Être et Temps|traducteur=François Vezin|éditeur=Gallimard|année=1986|passage=Introduction}}.</ref>. Cette distinction a exercé une grande influence sur la philosophie continentale du {{s-|XX|e}}, tout en restant discutée et étrangère à l'usage dominant dans d'autres traditions.
D'autres penseurs de tradition continentale ont forgé leur propre conception de l'ontologie. Nicolai Hartmann a élaboré une « ontologie critique » distinguant des couches de l'être (matière, vie, psychisme, esprit). [[Dictionnaire de philosophie/Jean-Paul Sartre|Jean-Paul Sartre]], dans ''L'Être et le Néant'' (1943), a proposé une « ontologie phénoménologique » articulée autour de l'être-en-soi et de l'être-pour-soi.
En informatique et en intelligence artificielle, enfin, « ontologie » a pris un sens dérivé : une ontologie est la spécification formelle des concepts, des relations et des règles d'inférence d'un domaine donné. Une ontologie médicale, par exemple, définit les concepts de maladie, de symptôme, de traitement ou d'organe et leurs relations, afin qu'un système informatique puisse raisonner sur des données cliniques. Cet usage, apparu dans les années 1990, s'est éloigné du sens philosophique, dont il conserve cependant l'idée d'un classement systématique des types d'entités<ref>{{Ouvrage|auteur1=Thomas R. Gruber|titre=A Translation Approach to Portable Ontology Specifications|périodique=Knowledge Acquisition|volume=5|numéro=2|année=1993|passage=199-220}}.</ref>.
==== En résumé ====
On peut retenir quelques points simples. L'ontologie est l'étude de ce qui existe et des catégories fondamentales de l'être ; elle répond à la question « qu'y a-t-il ? ». La métaphysique est un terme plus large : elle englobe l'ontologie, mais traite aussi du temps, de la causalité, de la liberté, de l'esprit, des propriétés ou des relations. Dans la tradition analytique, les deux mots s'emploient souvent l'un pour l'autre, et la notion quinienne d'engagement ontologique y joue un rôle central. Dans la tradition continentale, leur distinction peut prendre un sens plus fort, notamment chez Heidegger. Le contexte suffit en général à indiquer dans quel sens ces termes sont employés.
=== Critiques historiques de la métaphysique ===
La métaphysique a été l'objet de critiques sévères tout au long de son histoire. Ces critiques ne sont pas de simples rejets : ce sont des interrogations sur la possibilité même d'une connaissance métaphysique. Les comprendre est indispensable pour saisir le statut actuel de la discipline.
==== La critique empiriste de David Hume ====
[[Fichier:David Hume Ramsay.jpg|vignette|David Hume, portrait par Allan Ramsay. Hume tenait la métaphysique d'école pour « sophismes et illusions ».]]
[[Dictionnaire de philosophie/David Hume|David Hume]] (1711-1776) compte parmi les critiques les plus incisifs de la métaphysique traditionnelle. À la fin de son ''Enquête sur l'entendement humain'' (1748), il propose une épreuve restée célèbre : prenant en main un ouvrage de théologie ou de métaphysique d'école, demandons-nous s'il contient des raisonnements abstraits sur la quantité et le nombre, ou des raisonnements expérimentaux sur des faits. Si la réponse est non dans les deux cas, l'ouvrage ne renferme, selon Hume, que « sophismes et illusions », et l'on peut le livrer aux flammes<ref>{{Ouvrage|auteur1=David Hume|titre=Enquête sur l'entendement humain|traducteur=André Leroy|éditeur=GF Flammarion|année=2006|passage=Section XII, partie 3}}.</ref>.
Pour Hume, il n'existe que deux sortes de connaissances légitimes : les « relations d'idées », comme celles des mathématiques et de la logique, certaines mais muettes sur le monde réel, et les « questions de fait », qui portent sur le monde mais dépendent entièrement de l'expérience. La métaphysique traditionnelle prétend connaître la nature ultime de la réalité par la seule raison, sans recours à l'expérience ; elle n'entre dans aucune de ces deux catégories, et se trouve par là disqualifiée.
Cette critique repose sur l'[[Dictionnaire de philosophie/Empirisme|empirisme]] : la thèse selon laquelle toute connaissance de fait dérive de l'expérience sensible. Faute d'expérience de Dieu, de l'âme immatérielle ou de la substance en soi, nous ne pourrions rien savoir de ces prétendues réalités.
==== La critique kantienne : les limites de la raison pure ====
[[Fichier:Immanuel Kant - Gemaelde 1.jpg|vignette|Emmanuel Kant chercha non à détruire la métaphysique, mais à en fixer les limites légitimes.]]
Emmanuel Kant (1724-1804), que la lecture de Hume avait, dit-il, « réveillé de son sommeil dogmatique », chercha non à détruire la métaphysique, mais à la refonder. Sa ''Critique de la raison pure'' (1781) examine les conditions et les limites de la connaissance humaine<ref>{{Ouvrage|auteur1=Emmanuel Kant|titre=Critique de la raison pure|traducteur=Alain Renaut|éditeur=GF Flammarion|année=2006|passage=Préface de la seconde édition}}.</ref>.
Kant croise deux distinctions. La première oppose la connaissance ''a priori'', indépendante de l'expérience (« 7 + 5 = 12 », ou « tout effet a une cause »), et la connaissance ''a posteriori'', qui en dépend (« l'eau bout à 100 °C », ou « Paris est la capitale de la France »). La seconde oppose les jugements analytiques, où le prédicat est déjà contenu dans le sujet (« tout célibataire est non marié »), et les jugements synthétiques, où le prédicat ajoute quelque chose au sujet (« tout corps est pesant »). Les jugements analytiques sont toujours ''a priori'' : nul besoin d'enquêter sur le monde pour savoir qu'un célibataire n'est pas marié. Les jugements synthétiques, eux, peuvent être ''a priori'' ou ''a posteriori''.
Kant pose alors une question qui commande tout le reste : des jugements synthétiques ''a priori'' sont-ils possibles ? Autrement dit, pouvons-nous connaître quelque chose du monde (synthétique) sans recourir à l'expérience (''a priori'') ? La métaphysique traditionnelle l'affirme : elle prétend connaître par la seule raison des vérités sur Dieu, l'âme et le monde.
La réponse de Kant est nuancée. Certains jugements synthétiques ''a priori'' sont bien possibles : ceux qui énoncent les conditions de possibilité de l'expérience elle-même, comme le principe que tout événement a une cause. Mais la métaphysique traditionnelle prétend connaître des réalités situées au-delà de toute expérience possible (Dieu, l'âme immortelle, le monde comme totalité) ; elle franchit alors les limites de la raison. Lancée hors de l'expérience possible, la raison tombe dans des contradictions insolubles, les antinomies, ou dans des raisonnements trompeurs, les paralogismes.
Kant ne récuse donc pas toute métaphysique. Il sépare une métaphysique légitime, qui étudie les conditions ''a priori'' de l'expérience, d'une métaphysique illégitime, qui prétend connaître des réalités transcendantes. La première est possible et même nécessaire ; la seconde est vouée à l'échec. Cette partition a elle-même été contestée : Hegel objectera que Kant a présupposé, avec sa « chose en soi », une métaphysique qu'il prétendait pourtant interdire.
==== La critique nietzschéenne : la métaphysique comme symptôme ====
[[Fichier:Nietzsche1882.jpg|vignette|Friedrich Nietzsche (photographie de 1882). Il voyait dans la métaphysique l'invention d'un « arrière-monde ».]]
[[Dictionnaire de philosophie/Friedrich Nietzsche|Friedrich Nietzsche]] (1844-1900) formule une critique d'un autre ordre, non plus épistémologique mais généalogique. La métaphysique n'est pas pour lui une simple erreur de raisonnement : elle est le symptôme d'une certaine attitude devant la vie, qu'il rattache au [[Dictionnaire de philosophie/Nihilisme|nihilisme]]<ref>{{Ouvrage|auteur1=Friedrich Nietzsche|titre=Crépuscule des idoles|traducteur=Henri Albert|éditeur=GF Flammarion|année=2017|passage=« La "raison" dans la philosophie »}}.</ref>.
La métaphysique traditionnelle, selon Nietzsche, dévalorise le monde sensible au profit d'un « arrière-monde » (''Hinterwelt'') tenu pour plus réel, plus vrai, plus stable. [[Dictionnaire de philosophie/Platon|Platon]] opposait le monde sensible, changeant et trompeur, au monde des Idées, éternel et seul véritablement réel ; le christianisme opposa la vallée de larmes d'ici-bas à la béatitude d'un au-delà. Ces arrière-mondes sont, aux yeux de Nietzsche, des fictions inventées par ceux qui ne supportent pas le devenir, la souffrance et la mort.
La critique nietzschéenne ne se borne pas à déclarer la métaphysique fausse ; elle demande pourquoi les hommes ont eu besoin de telles fictions, et quelle volonté s'y exprime. Nietzsche y discerne une « volonté de néant », le désir de fuir la vie réelle. Cette analyse a marqué la philosophie du {{s-|XX|e}}, et Heidegger en particulier.
==== La critique pragmatiste : le détachement de la pratique ====
Le [[Dictionnaire de philosophie/Pragmatisme|pragmatisme]] américain, illustré par [[Dictionnaire de philosophie/William James|William James]] (1842-1910) et [[Dictionnaire de philosophie/John Dewey|John Dewey]] (1859-1952), adresse à la métaphysique traditionnelle un autre reproche : son abstraction et son éloignement des conséquences pratiques<ref>{{Ouvrage|auteur1=William James|titre=Le Pragmatisme|traducteur=Nathalie Ferron|éditeur=Flammarion|année=2007|passage=Leçon II}}.</ref>.
Pour James, la valeur d'une idée tient à ses effets concrets : quelle différence cela fait-il, dans une vie, qu'une thèse soit vraie plutôt qu'une autre ? Or beaucoup de disputes métaphysiques, par exemple entre matérialisme et idéalisme, ou entre monisme et pluralisme, semblent ne rien changer à l'expérience. Lorsque deux théories rivales ont exactement les mêmes conséquences observables et pratiques, leur différence est, pour le pragmatiste, purement verbale.
Cette critique ne condamne pas toute métaphysique, mais elle demande que les questions métaphysiques soient reformulées en fonction de leurs conséquences. Une métaphysique pragmatiste reste possible, à condition de demeurer liée à l'expérience vécue et à l'action.
==== La critique positiviste : les énoncés métaphysiques sont dépourvus de sens ====
Au {{s-|XX|e}}, le [[Dictionnaire de philosophie/Positivisme logique|positivisme logique]], ou empirisme logique, élaboré par le [[Dictionnaire de philosophie/Cercle de Vienne|Cercle de Vienne]] dans les années 1920 et 1930, formule l'une des critiques les plus sévères de la métaphysique. [[Dictionnaire de philosophie/Rudolf Carnap|Rudolf Carnap]], Moritz Schlick et [[Dictionnaire de philosophie/Alfred Jules Ayer|Alfred Jules Ayer]] soutiennent que les énoncés métaphysiques ne sont pas seulement inconnaissables ou inutiles, mais littéralement dépourvus de sens cognitif<ref>{{Ouvrage|auteur1=Alfred Jules Ayer|titre=Langage, vérité et logique|traducteur=Joseph Ohana|éditeur=Flammarion|année=1956|passage=Chapitre I}}.</ref>.
Il faut bien entendre l'expression « dépourvu de sens ». Les positivistes ne disent pas que les énoncés métaphysiques sont faux, car les déclarer faux reviendrait encore à leur prêter un sens. Ils soutiennent que ces énoncés n'expriment ni vérité ni fausseté : ils n'affirment rien qui puisse être vrai ou faux. Ce sont des pseudo-énoncés, qui ont la forme grammaticale d'une affirmation sans en avoir le contenu.
L'argument repose sur le principe de vérification, ou critère de signification : un énoncé n'a de sens cognitif que s'il est analytique, vrai en vertu du sens des mots, ou empiriquement vérifiable, c'est-à-dire associé à des observations qui le confirmeraient ou l'infirmeraient. Ce principe a connu plusieurs formulations au fil des débats : vérifiabilité forte, exigeant une vérification concluante, ou vérifiabilité faible, se contentant d'une confirmation possible.
Soit l'énoncé « l'Absolu est au-delà du temps et du changement ». Il n'est pas analytique, et aucune observation ne saurait le confirmer ni l'infirmer : il échoue donc aux deux tests et compte, pour les positivistes, comme un pseudo-énoncé. Carnap applique cette analyse à une phrase de Heidegger, « le Néant néantise » (''das Nichts nichtet''), où il voit une confusion entre la fonction logique de la négation et un prétendu objet, le Néant<ref>{{Ouvrage|auteur1=Rudolf Carnap|titre=Überwindung der Metaphysik durch logische Analyse der Sprache|périodique=Erkenntnis|volume=2|année=1931|passage=219-241}}.</ref>. Les lecteurs de Heidegger ont répondu que l'objection présuppose précisément ce que Heidegger récuse : la réduction du langage philosophique à la logique formelle. L'expérience du néant, qu'il associe à l'angoisse devant la contingence de l'être, serait un phénomène que l'analyse logique ne peut atteindre.
==== L'objection d'auto-réfutation et les réponses positivistes ====
Le positivisme logique paraissait disqualifier la métaphysique sans retour. Mais il se heurta à une difficulté de principe : le critère de vérification est-il lui-même vérifiable ?
Le principe affirme qu'un énoncé n'a de sens cognitif que s'il est analytique ou empiriquement vérifiable. Or il n'est pas analytique, car il ne découle pas du sens des mots ; et il n'est pas vérifiable, car aucune observation ne saurait l'établir. Mesuré à ses propres exigences, il semble donc lui-même dépourvu de sens cognitif.
Plusieurs critiques ont formulé cette objection. [[Dictionnaire de philosophie/Karl Popper|Karl Popper]], qui partageait certaines préoccupations des positivistes, lui préféra un autre critère, la falsifiabilité plutôt que la vérifiabilité, afin d'échapper à cette difficulté<ref>{{Ouvrage|auteur1=Karl Popper|titre=La Logique de la découverte scientifique|traducteur=Nicole Thyssen-Rutten et Philippe Devaux|éditeur=Payot|année=1973|passage=Chapitre I}}.</ref>.
Les positivistes ont tenté diverses réponses. Certains ont soutenu que le principe de vérification n'est pas un énoncé de fait, mais une règle méthodologique, une recommandation sur l'usage du langage plutôt qu'une affirmation sur le monde. D'autres ont assoupli le critère en parlant de « confirmabilité » plutôt que de vérifiabilité stricte. Ces aménagements rencontrèrent à leur tour des difficultés, et le programme positiviste s'essouffla au cours des années 1950 et 1960<ref>{{Ouvrage|auteur1=Kim, Sosa, Rosenkrantz|titre=A Companion to Metaphysics|passage=414}}.</ref>.
==== La critique du « second Wittgenstein » ====
[[Fichier:Ludwig-Wittgenstein-portrait.jpg|vignette|Ludwig Wittgenstein, pour qui bien des problèmes métaphysiques naissent de méprises sur le langage.]]
[[Dictionnaire de philosophie/Ludwig Wittgenstein|Ludwig Wittgenstein]], dans sa philosophie tardive, et notamment dans les ''Recherches philosophiques'' (publiées en 1953), propose une autre critique. À la différence des positivistes, il ne juge pas les énoncés métaphysiques dépourvus de sens au regard d'un critère formel. Son diagnostic est plus fin : les problèmes métaphysiques naîtraient de méprises sur le fonctionnement de notre langage<ref>{{Ouvrage|auteur1=Ludwig Wittgenstein|titre=Recherches philosophiques|traducteur=Françoise Dastur et al.|éditeur=Gallimard|année=2004|passage=§ 38, § 109, § 116}}.</ref>.
Les philosophes, selon Wittgenstein, se laissent souvent envoûter par le langage et prennent des questions de grammaire pour des questions sur la réalité. Le mot « temps » fonctionne grammaticalement comme un nom, ce qui nous pousse à chercher l'objet qui lui correspondrait, le Temps conçu comme une entité mystérieuse ; mais cette quête est vaine, car elle méconnaît la grammaire de nos concepts. « Les problèmes philosophiques surgissent quand le langage part en vacances », écrit-il (§ 38).
Pour Wittgenstein, la philosophie n'a pas à résoudre les problèmes métaphysiques, mais à les dissoudre, en montrant qu'ils reposent sur des malentendus de langage. Cette lecture « thérapeutique » est cependant contestée : certains commentateurs voient dans la philosophie tardive de Wittgenstein des thèses positives sur le langage et l'esprit, et non la seule dissolution de faux problèmes.
==== La critique heideggérienne : la métaphysique comme onto-théologie ====
Martin Heidegger (1889-1976) développe une critique encore différente. La métaphysique occidentale, de Platon à Nietzsche, se caractériserait par un « oubli de l'être » (''Seinsvergessenheit''). Au lieu d'interroger le sens de l'être, c'est-à-dire ce que veut dire « être », elle s'est attachée aux étants, les choses qui sont, en quête d'un étant suprême qui fonderait tous les autres<ref>{{Ouvrage|auteur1=Martin Heidegger|titre=Introduction à la métaphysique|traducteur=Gilbert Kahn|éditeur=Gallimard|année=1967|passage=Chapitre I}}.</ref>.
Heidegger nomme cette structure « onto-théologie » : la métaphysique est à la fois ontologie, discours sur l'être en général, et théologie, discours sur l'étant suprême, qu'il s'agisse de Dieu, de l'Idée du Bien, du sujet ou de la volonté de puissance. Cette double orientation lui aurait fait manquer la question proprement ontologique, qui n'est pas « qu'est-ce qui existe ? » mais « que veut dire exister ? ».
La critique de Heidegger a inspiré nombre de penseurs continentaux, comme [[Dictionnaire de philosophie/Hans-Georg Gadamer|Hans-Georg Gadamer]], [[Dictionnaire de philosophie/Jacques Derrida|Jacques Derrida]] et [[Dictionnaire de philosophie/Emmanuel Levinas|Emmanuel Levinas]]. Elle demeure discutée : les uns y voient un apport majeur à l'intelligence de l'histoire de la philosophie, les autres une reconstruction historique fragile.
==== Le renouveau de la métaphysique au {{s-|XX|e}} ====
Malgré ces critiques, la métaphysique a connu un renouveau marqué dans la seconde moitié du {{s-|XX|e}}, surtout dans la tradition analytique de langue anglaise. Plusieurs raisons l'expliquent.
L'essoufflement du positivisme logique a d'abord libéré le terrain. Si le critère de vérification ne tient pas, la condamnation globale de la métaphysique perd son appui.
Des travaux de logique modale et de philosophie du langage ont ensuite ouvert des voies nouvelles. Dans « Two Dogmas of Empiricism » (1951), Willard Van Orman Quine récuse la distinction entre énoncés analytiques et synthétiques, et sape ainsi l'un des fondements du positivisme, tout en reformulant les questions ontologiques avec précision. Saul Kripke, dans ''La Logique des noms propres'' (1972), réhabilite les notions d'essence et de nécessité métaphysique, longtemps tenues pour suspectes. David Lewis bâtit une métaphysique des mondes possibles d'une grande exactitude technique<ref>{{Ouvrage|auteur1=Michael J. Loux|titre=Metaphysics: A Contemporary Introduction|éditeur=Routledge|année=2006|passage=1-10}}.</ref>.
La métaphysique contemporaine s'est enfin rapprochée des sciences de la nature. Les philosophes de l'esprit discutent de la conscience en lien avec les neurosciences, les philosophes du temps tiennent compte de la physique relativiste, les philosophes de la causalité s'inspirent des méthodes statistiques. Cette métaphysique « naturalisée » se garde des excès spéculatifs de la tradition tout en conservant des questions proprement philosophiques.
==== La situation actuelle : débats et critiques ====
La métaphysique est aujourd'hui une discipline active, pourvue de revues spécialisées, de colloques et de controverses nourries. Les critiques du passé n'ont pas été oubliées : elles ont rendu les métaphysiciens plus attentifs à la clarté des concepts, à la solidité des arguments et aux liens avec les sciences.
Le statut de la discipline reste pourtant disputé, jusque dans la tradition analytique. Eli Hirsch défend une position « déflationniste » : beaucoup de disputes métaphysiques seraient purement verbales, de simples désaccords sur la manière de parler plutôt que sur la structure du monde. Theodore Sider soutient au contraire que les questions métaphysiques admettent des réponses objectives, et que la métaphysique peut atteindre la structure fondamentale du réel. Ce débat « méta-métaphysique », portant sur le statut même de la discipline, est l'un des plus vivants de la philosophie contemporaine<ref>{{Ouvrage|auteur1=David Chalmers|auteur2=David Manley|auteur3=Ryan Wasserman|titre=Metametaphysics: New Essays on the Foundations of Ontology|éditeur=Oxford University Press|année=2009|passage=Introduction}}.</ref>.
Des objections plus sévères ont également été avancées. Dans ''Every Thing Must Go'' (2007), James Ladyman et Don Ross jugent que la métaphysique analytique est devenue une scolastique détachée de la science réelle, et qu'une métaphysique n'est légitime que « naturalisée », entièrement informée par la physique fondamentale<ref>{{Ouvrage|auteur1=James Ladyman|auteur2=Don Ross|titre=Every Thing Must Go: Metaphysics Naturalized|éditeur=Oxford University Press|année=2007|passage=Introduction}}.</ref>. Par certains côtés, cette position rappelle les objections positivistes ; elle ne rejette pourtant pas toute métaphysique, mais exige qu'elle reste en continuité avec les sciences.
D'autres critiques, venues de traditions différentes, méritent d'être signalées. La tradition marxiste a vu dans la métaphysique une « idéologie », une construction qui masque les conditions matérielles et sociales de sa propre production. Des critiques féministes ont mis en cause certains couples d'oppositions métaphysiques, comme esprit et corps, raison et émotion, culture et nature, où elles décèlent des partis pris liés au genre. Des critiques postcoloniales ont interrogé l'universalité que la métaphysique occidentale s'attribue. Marginales dans les manuels de métaphysique analytique, ces critiques n'en font pas moins partie du paysage intellectuel contemporain.
== Notes et références ==
{{références}}
== Bibliographie ==
=== Ouvrages généraux ===
* {{Ouvrage|auteur1=Michael J. Loux|titre=Metaphysics: A Contemporary Introduction|éditeur=Routledge|année=2006|isbn=978-0415140348}}
* {{Ouvrage|auteur1=Alyssa Ney|titre=Metaphysics: An Introduction|éditeur=Routledge|année=2014|isbn=9780815350491}}
* {{Ouvrage|auteur1=Michael Rea|titre=Metaphysics: The Basics|éditeur=Routledge|année=2020|isbn=9780367136086}}
=== Encyclopédies et ouvrages de référence ===
* {{Ouvrage|auteur1=Jaegwon Kim|auteur2=Ernest Sosa|auteur3=Gary S. Rosenkrantz|titre=A Companion to Metaphysics|éditeur=Wiley-Blackwell|année=2009|isbn=9780631199991}}
* {{Ouvrage|auteur1=Richard M. Gale|titre=The Blackwell Guide to Metaphysics|éditeur=Wiley-Blackwell|année=2002|isbn=9780631221203}}
* {{Ouvrage|auteur1=Helen Beebee|auteur2=Nikk Effingham|auteur3=Philip Goff|titre=Metaphysics: The Key Concepts|éditeur=Routledge|année=2014|isbn=978-0415559287}}
=== Ouvrages classiques ===
* {{Ouvrage|auteur1=Aristote|titre=Métaphysique|traducteur=Jules Tricot|éditeur=Vrin|année=1991}}
* {{Ouvrage|auteur1=René Descartes|titre=Méditations métaphysiques|éditeur=Flammarion|année=2009}}
* {{Ouvrage|auteur1=David Hume|titre=Traité de la nature humaine|traducteur=Philippe Saltel|éditeur=GF Flammarion|année=1999}}
* {{Ouvrage|auteur1=Emmanuel Kant|titre=Critique de la raison pure|traducteur=Alain Renaut|éditeur=GF Flammarion|année=2006}}
* {{Ouvrage|auteur1=Gottfried Wilhelm Leibniz|titre=Discours de métaphysique et Monadologie|éditeur=Gallimard|année=2004}}
* {{Ouvrage|auteur1=Baruch Spinoza|titre=Éthique|traducteur=Bernard Pautrat|éditeur=Seuil|année=1999}}
=== Ouvrages spécialisés contemporains ===
* {{Ouvrage|auteur1=David Lewis|titre=On the Plurality of Worlds|éditeur=Blackwell|année=1986}}
* {{Ouvrage|auteur1=David Chalmers|titre=The Conscious Mind: In Search of a Fundamental Theory|éditeur=Oxford University Press|année=1996}}
* {{Ouvrage|auteur1=Derek Parfit|titre=Reasons and Persons|éditeur=Oxford University Press|année=1984}}
* {{Ouvrage|auteur1=Saul Kripke|titre=La Logique des noms propres|traducteur=Pierre Jacob et François Recanati|éditeur=Minuit|année=1982}}
* {{Ouvrage|auteur1=James Woodward|titre=Making Things Happen: A Theory of Causal Explanation|éditeur=Oxford University Press|année=2003}}
* {{Ouvrage|auteur1=Peter van Inwagen|titre=Material Beings|éditeur=Cornell University Press|année=1990}}
* {{Ouvrage|auteur1=Theodore Sider|titre=Four-Dimensionalism: An Ontology of Persistence and Time|éditeur=Oxford University Press|année=2001}}
* {{Ouvrage|auteur1=D. M. Armstrong|titre=Universals: An Opinionated Introduction|éditeur=Westview Press|année=1989}}
== Voir aussi ==
=== Textes en ligne sur Wikisource ===
* [[s:La Métaphysique d’Aristote|Aristote, ''La Métaphysique'']]
* [[s:Méditations métaphysiques|René Descartes, ''Méditations métaphysiques'']]
* [[s:Éthique (Saisset, 1861)|Baruch Spinoza, ''Éthique'']] (traduction Émile Saisset)
* [[s:Monadologie|Gottfried Wilhelm Leibniz, ''La Monadologie'']]
* [[s:Critique de la raison pure (trad. Barni)|Emmanuel Kant, ''Critique de la raison pure'']] (traduction Jules Barni)
* [[s:Le Crépuscule des Idoles|Friedrich Nietzsche, ''Le Crépuscule des idoles'']] (traduction Henri Albert)
=== Liens externes ===
* {{lien web|url=https://plato.stanford.edu/entries/metaphysics/|titre=Metaphysics|site=Stanford Encyclopedia of Philosophy|langue=en}}
* {{lien web|url=https://iep.utm.edu/metaphys/|titre=Metaphysics|site=Internet Encyclopedia of Philosophy|langue=en}}
* {{lien web|url=https://www.britannica.com/topic/metaphysics|titre=Metaphysics|site=Encyclopædia Britannica|langue=en}}
{{autocat}}
[[Catégorie:Métaphysique]]
180v2pa0x7jonis1gytugrgkprta1el
Feuille test orpheline
0
83645
768708
761436
2026-06-26T09:18:11Z
Xhungab
23827
768708
wikitext
text/x-wiki
{{ébauche}}
* Je propose de créer un livre "feuille test orpheline" pour sortir ces feuilles des pages orphelines. Ces feuilles de test orphelines créées pour tester wikibook ont été abandonnées sans remords.
* 01) Feuilles Sans Contenues
* 02) Feuilles Sans Contenues Pertinents
* 03) La qualité du contenu de la feuille ne correspond pas à celle du livre
* 04) Feuilles Tests Orphelines
== 01) Feuilles Sans Contenues ==
* [[Fonctionnement d'un ordinateur/Version imprimable|Fonctionnement d'un ordinateur/Version imprimable]] La nouvelle version [[Fonctionnement d'un ordinateur/Version imprimable 2|Version imprimable 2]]
* [[Photographie/Thèmes/La femme|La femme]] (Vide depuis 2008)
* [[Photographie/Thèmes/Les fleurs|Les fleurs]] (Vide depuis 2008)
== 02) Feuilles Sans Contenues Pertinents ==
* [[Photographie/Posemètres indépendants|Posemètres indépendants]]
* [[Renaults d'avant guerre/Sommaire|Renaults d'avant guerre Sommaire]]
* [[Sur le chemin de Messon, un modèle photo|Sur le chemin de Messon, un modèle photo]]
* [[Chimie médicinale|Chimie médicinale]]
* [[Chimie physique/Fonction d'état|Chimie physique/Fonction d'état]]
* [[Chimie physique/Introduction à la thermodynamique classique|Chimie physique/Introduction à la thermodynamique classique]]
== 03) La qualité du contenu de la feuille ne correspond pas à celle du livre ==
Page du livre Plantes messicoles de l'Avesnois :
* [[Plantes messicoles de l'Avesnois/Atlas des plantes messicoles/Présentation des espèces/Anchusa arvensis (L.) M.Bieb.|Anchusa arvensis (L.) M.Bieb.]]
Pages orphelines du livre Plantes messicoles de l'Avesnois :
* [[Plantes messicoles de l'Avesnois/Atlas des plantes messicoles/Présentation des espèces/Chondrilla juncea L.|Chondrilla juncea L.]] 3 février 2012 '''Les autres pages du livres sont en couleurs.'''
* [[Plantes messicoles de l'Avesnois/Plantes messicoles/Présentation des espèces/Scleranthus annuus L. subsp. annuus|Scleranthus annuus L. subsp. annuus]] 3 février 2012 '''Les autres pages du livres sont en couleurs.'''
Page du livre Wikijunior:Les félins :
* [[Wikijunior:Les félins/Les tigres|Les tigres]]
Pages orphelines du livre Wikijunior:Les félins :
* [[Wikijunior/Le mouflon|Le mouflon]] 7 avril 2016
* [[Wikijunior/Le renard roux|Le renard roux]] 7 avril 2016
== 04 Feuilles Tests Orphelines ==
* [[Économie|Économie]]
* [[Hydraulique|Hydraulique]] 18 juin 2008
[[Catégorie:Feuilles volantes|*]]
7drjl4cmo2zhbiiy5ufkwhzdxzl5b84
768709
768708
2026-06-26T09:21:24Z
Xhungab
23827
768709
wikitext
text/x-wiki
{{ébauche}}
* Je propose de créer un livre "feuille test orpheline" pour sortir ces feuilles des pages orphelines. Ces feuilles de test orphelines créées pour tester wikibook ont été abandonnées sans remords.
* 01) Feuilles Sans Contenues
* 02) Feuilles Sans Contenues Pertinents
* 03) La qualité du contenu de la feuille ne correspond pas à celle du livre
* 04) Feuilles Tests Orphelines
== 01) Feuilles Sans Contenues ==
* [[Fonctionnement d'un ordinateur/Version imprimable|Fonctionnement d'un ordinateur/Version imprimable]] La nouvelle version [[Fonctionnement d'un ordinateur/Version imprimable 2|Version imprimable 2]]
* [[Photographie/Thèmes/La femme|La femme]] (Vide depuis 2008)
* [[Photographie/Thèmes/Les fleurs|Les fleurs]] (Vide depuis 2008)
== 02) Feuilles Sans Contenues Pertinents ==
* [[Photographie/Posemètres indépendants|Posemètres indépendants]]
* [[Renaults d'avant guerre/Sommaire|Renaults d'avant guerre Sommaire]]
* [[Sur le chemin de Messon, un modèle photo|Sur le chemin de Messon, un modèle photo]]
* [[Chimie médicinale|Chimie médicinale]]
* [[Chimie physique/Fonction d'état|Chimie physique/Fonction d'état]]
* [[Chimie physique/Introduction à la thermodynamique classique|Chimie physique/Introduction à la thermodynamique classique]]
* [[Wikijunior/Le mouflon|Le mouflon]]
* [[Wikijunior/Le renard roux|Le renard roux]]
== 03) La qualité du contenu de la feuille ne correspond pas à celle du livre ==
Page du livre Plantes messicoles de l'Avesnois :
* [[Plantes messicoles de l'Avesnois/Atlas des plantes messicoles/Présentation des espèces/Anchusa arvensis (L.) M.Bieb.|Anchusa arvensis (L.) M.Bieb.]]
Pages orphelines du livre Plantes messicoles de l'Avesnois :
* [[Plantes messicoles de l'Avesnois/Atlas des plantes messicoles/Présentation des espèces/Chondrilla juncea L.|Chondrilla juncea L.]] 3 février 2012 '''Les autres pages du livres sont en couleurs.'''
* [[Plantes messicoles de l'Avesnois/Plantes messicoles/Présentation des espèces/Scleranthus annuus L. subsp. annuus|Scleranthus annuus L. subsp. annuus]] 3 février 2012 '''Les autres pages du livres sont en couleurs.'''
== 04 Feuilles Tests Orphelines ==
* [[Économie|Économie]]
* [[Hydraulique|Hydraulique]] 18 juin 2008
[[Catégorie:Feuilles volantes|*]]
49rez630stb2zsxq1dkhn8d62p9le41
768710
768709
2026-06-26T09:24:36Z
Xhungab
23827
768710
wikitext
text/x-wiki
{{ébauche}}
* Je propose de créer un livre "feuille test orpheline" pour sortir ces feuilles des pages orphelines. Ces feuilles de test orphelines créées pour tester wikibook ont été abandonnées sans remords.
* 01) Feuilles Sans Contenues
* 02) Feuilles Sans Contenues Pertinents
* 03) La qualité du contenu de la feuille ne correspond pas à celle du livre
* 04) Feuilles Tests Orphelines
== 01) Feuilles Sans Contenues ==
* [[Fonctionnement d'un ordinateur/Version imprimable|Fonctionnement d'un ordinateur/Version imprimable]] La nouvelle version [[Fonctionnement d'un ordinateur/Version imprimable 2|Version imprimable 2]]
* [[Photographie/Thèmes/La femme|La femme]] (Vide depuis 2008)
* [[Photographie/Thèmes/Les fleurs|Les fleurs]] (Vide depuis 2008)
== 02) Feuilles Sans Contenues Pertinents ==
* [[Photographie/Posemètres indépendants|Posemètres indépendants]]
* [[Renaults d'avant guerre/Sommaire|Renaults d'avant guerre Sommaire]]
* [[Sur le chemin de Messon, un modèle photo|Sur le chemin de Messon, un modèle photo]]
* [[Chimie médicinale|Chimie médicinale]]
* [[Chimie physique/Fonction d'état|Chimie physique/Fonction d'état]]
* [[Chimie physique/Introduction à la thermodynamique classique|Chimie physique/Introduction à la thermodynamique classique]]
* [[Wikijunior/Le mouflon|Le mouflon]]
* [[Wikijunior/Le renard roux|Le renard roux]]
== 03) La qualité du contenu de la feuille ne correspond pas à celle du livre ==
Page du livre Plantes messicoles de l'Avesnois :
* [[Plantes messicoles de l'Avesnois/Atlas des plantes messicoles/Présentation des espèces/Anchusa arvensis (L.) M.Bieb.|Anchusa arvensis (L.) M.Bieb.]]
Pages orphelines du livre Plantes messicoles de l'Avesnois :
* [[Plantes messicoles de l'Avesnois/Atlas des plantes messicoles/Présentation des espèces/Chondrilla juncea L.|Chondrilla juncea L.]] 3 février 2012 '''Les autres pages du livres sont en couleurs.'''
* [[Plantes messicoles de l'Avesnois/Plantes messicoles/Présentation des espèces/Scleranthus annuus L. subsp. annuus|Scleranthus annuus L. subsp. annuus]] 3 février 2012 '''Les autres pages du livres sont en couleurs.'''
== 04 Feuilles Tests Orphelines ==
* [[Économie|Économie]]
* [[Économie/Économie réelle|Économie réelle]]
* [[Hydraulique|Hydraulique]] 18 juin 2008
[[Catégorie:Feuilles volantes|*]]
bp5sty9ix52j5p1sfoxylrc4iz8blju
768711
768710
2026-06-26T09:25:56Z
Xhungab
23827
768711
wikitext
text/x-wiki
{{ébauche}}
* Je propose de créer un livre "feuille test orpheline" pour sortir ces feuilles des pages orphelines. Ces feuilles de test orphelines créées pour tester wikibook ont été abandonnées sans remords.
* 01) Feuilles Sans Contenues
* 02) Feuilles Sans Contenues Pertinents
* 03) La qualité du contenu de la feuille ne correspond pas à celle du livre
* 04) Feuilles Tests Orphelines
== 01) Feuilles Sans Contenues ==
* [[Fonctionnement d'un ordinateur/Version imprimable|Fonctionnement d'un ordinateur/Version imprimable]] La nouvelle version [[Fonctionnement d'un ordinateur/Version imprimable 2|Version imprimable 2]]
* [[Photographie/Thèmes/La femme|La femme]] (Vide depuis 2008)
* [[Photographie/Thèmes/Les fleurs|Les fleurs]] (Vide depuis 2008)
== 02) Feuilles Sans Contenues Pertinents ==
* [[Photographie/Posemètres indépendants|Posemètres indépendants]]
* [[Renaults d'avant guerre/Sommaire|Renaults d'avant guerre Sommaire]]
* [[Sur le chemin de Messon, un modèle photo|Sur le chemin de Messon, un modèle photo]]
* [[Chimie médicinale|Chimie médicinale]]
* [[Chimie physique/Fonction d'état|Chimie physique/Fonction d'état]]
* [[Chimie physique/Introduction à la thermodynamique classique|Chimie physique/Introduction à la thermodynamique classique]]
* [[Wikijunior/Le mouflon|Le mouflon]]
* [[Wikijunior/Le renard roux|Le renard roux]]
== 03) La qualité du contenu de la feuille ne correspond pas à celle du livre ==
Page du livre Plantes messicoles de l'Avesnois :
* [[Plantes messicoles de l'Avesnois/Atlas des plantes messicoles/Présentation des espèces/Anchusa arvensis (L.) M.Bieb.|Anchusa arvensis (L.) M.Bieb.]]
Pages orphelines du livre Plantes messicoles de l'Avesnois :
* [[Plantes messicoles de l'Avesnois/Atlas des plantes messicoles/Présentation des espèces/Chondrilla juncea L.|Chondrilla juncea L.]] 3 février 2012 '''Les autres pages du livres sont en couleurs.'''
* [[Plantes messicoles de l'Avesnois/Plantes messicoles/Présentation des espèces/Scleranthus annuus L. subsp. annuus|Scleranthus annuus L. subsp. annuus]] 3 février 2012 '''Les autres pages du livres sont en couleurs.'''
== 04 Feuilles Tests Orphelines ==
* [[Économie|Économie]]
* [[Économie/Économie réelle|Économie réelle]]
* [[Économie/Économie financière|Économie/Économie financière]]
* [[Hydraulique|Hydraulique]] 18 juin 2008
[[Catégorie:Feuilles volantes|*]]
5euhyubkpzud3m6t5gh1qmx1oqrte0e
768712
768711
2026-06-26T09:28:29Z
Xhungab
23827
768712
wikitext
text/x-wiki
{{ébauche}}
* Je propose de créer un livre "feuille test orpheline" pour sortir ces feuilles des pages orphelines. Ces feuilles de test orphelines créées pour tester wikibook ont été abandonnées sans remords.
* 01) Feuilles Sans Contenues
* 02) Feuilles Sans Contenues Pertinents
* 03) La qualité du contenu de la feuille ne correspond pas à celle du livre
* 04) Feuilles Tests Orphelines
== 01) Feuilles Sans Contenues ==
* [[Fonctionnement d'un ordinateur/Version imprimable|Fonctionnement d'un ordinateur/Version imprimable]] La nouvelle version [[Fonctionnement d'un ordinateur/Version imprimable 2|Version imprimable 2]]
* [[Photographie/Thèmes/La femme|La femme]] (Vide depuis 2008)
* [[Photographie/Thèmes/Les fleurs|Les fleurs]] (Vide depuis 2008)
== 02) Feuilles Sans Contenues Pertinents ==
* [[Photographie/Posemètres indépendants|Posemètres indépendants]]
* [[Renaults d'avant guerre/Sommaire|Renaults d'avant guerre Sommaire]]
* [[Sur le chemin de Messon, un modèle photo|Sur le chemin de Messon, un modèle photo]]
* [[Chimie médicinale|Chimie médicinale]]
* [[Chimie physique/Fonction d'état|Chimie physique/Fonction d'état]]
* [[Chimie physique/Introduction à la thermodynamique classique|Chimie physique/Introduction à la thermodynamique classique]]
* [[Wikijunior/Le mouflon|Le mouflon]]
* [[Wikijunior/Le renard roux|Le renard roux]]
* [[Hydrostatique|Hydrostatique]]
== 03) La qualité du contenu de la feuille ne correspond pas à celle du livre ==
Page du livre Plantes messicoles de l'Avesnois :
* [[Plantes messicoles de l'Avesnois/Atlas des plantes messicoles/Présentation des espèces/Anchusa arvensis (L.) M.Bieb.|Anchusa arvensis (L.) M.Bieb.]]
Pages orphelines du livre Plantes messicoles de l'Avesnois :
* [[Plantes messicoles de l'Avesnois/Atlas des plantes messicoles/Présentation des espèces/Chondrilla juncea L.|Chondrilla juncea L.]] 3 février 2012 '''Les autres pages du livres sont en couleurs.'''
* [[Plantes messicoles de l'Avesnois/Plantes messicoles/Présentation des espèces/Scleranthus annuus L. subsp. annuus|Scleranthus annuus L. subsp. annuus]] 3 février 2012 '''Les autres pages du livres sont en couleurs.'''
== 04 Feuilles Tests Orphelines ==
* [[Économie|Économie]]
* [[Économie/Économie réelle|Économie réelle]]
* [[Économie/Économie financière|Économie/Économie financière]]
* [[Hydraulique|Hydraulique]] 18 juin 2008
[[Catégorie:Feuilles volantes|*]]
6u9nzceq941j061wcqbfiim143tepo9
768714
768712
2026-06-26T09:34:57Z
Xhungab
23827
768714
wikitext
text/x-wiki
{{ébauche}}
* Je propose de créer un livre "feuille test orpheline" pour sortir ces feuilles des pages orphelines. Ces feuilles de test orphelines créées pour tester wikibook ont été abandonnées sans remords.
* 01) Feuilles Sans Contenues
* 02) Feuilles Sans Contenues Pertinents
* 03) La qualité du contenu de la feuille ne correspond pas à celle du livre
* 04) Feuilles Tests Orphelines
== 01) Feuilles Sans Contenues ==
* [[Fonctionnement d'un ordinateur/Version imprimable|Fonctionnement d'un ordinateur/Version imprimable]] La nouvelle version [[Fonctionnement d'un ordinateur/Version imprimable 2|Version imprimable 2]]
* [[Photographie/Thèmes/La femme|La femme]] (Vide depuis 2008)
* [[Photographie/Thèmes/Les fleurs|Les fleurs]] (Vide depuis 2008)
== 02) Feuilles Sans Contenues Pertinents ==
* [[Photographie/Posemètres indépendants|Posemètres indépendants]]
* [[Renaults d'avant guerre/Sommaire|Renaults d'avant guerre Sommaire]]
* [[Sur le chemin de Messon, un modèle photo|Sur le chemin de Messon, un modèle photo]]
* [[Chimie médicinale|Chimie médicinale]]
* [[Chimie physique/Fonction d'état|Chimie physique/Fonction d'état]]
* [[Chimie physique/Introduction à la thermodynamique classique|Chimie physique/Introduction à la thermodynamique classique]]
* [[Wikijunior/Le mouflon|Le mouflon]]
* [[Wikijunior/Le renard roux|Le renard roux]]
* [[Hydrostatique|Hydrostatique]]
== 03) La qualité du contenu de la feuille ne correspond pas à celle du livre ==
Page du livre Plantes messicoles de l'Avesnois :
* [[Plantes messicoles de l'Avesnois/Atlas des plantes messicoles/Présentation des espèces/Anchusa arvensis (L.) M.Bieb.|Anchusa arvensis (L.) M.Bieb.]]
Pages orphelines du livre Plantes messicoles de l'Avesnois :
* [[Plantes messicoles de l'Avesnois/Atlas des plantes messicoles/Présentation des espèces/Chondrilla juncea L.|Chondrilla juncea L.]] 3 février 2012 '''Les autres pages du livres sont en couleurs.'''
* [[Plantes messicoles de l'Avesnois/Plantes messicoles/Présentation des espèces/Scleranthus annuus L. subsp. annuus|Scleranthus annuus L. subsp. annuus]] 3 février 2012 '''Les autres pages du livres sont en couleurs.'''
== 04 Feuilles Tests Orphelines ==
* [[Économie|Économie]]
* [[Économie/Économie réelle|Économie réelle]]
* [[Économie/Économie financière|Économie/Économie financière]]
[[Catégorie:Feuilles volantes|*]]
gqg52eex1ba2ycxgwxoiby06ldz7gv6
768715
768714
2026-06-26T09:35:46Z
Xhungab
23827
768715
wikitext
text/x-wiki
{{ébauche}}
* Je propose de créer un livre "feuille test orpheline" pour sortir ces feuilles des pages orphelines. Ces feuilles de test orphelines créées pour tester wikibook ont été abandonnées sans remords.
* 01) Feuilles Sans Contenues
* 02) Feuilles Sans Contenues Pertinents
* 03) La qualité du contenu de la feuille ne correspond pas à celle du livre
* 04) Feuilles Tests Orphelines
== 01) Feuilles Sans Contenues ==
* [[Fonctionnement d'un ordinateur/Version imprimable|Fonctionnement d'un ordinateur/Version imprimable]] La nouvelle version [[Fonctionnement d'un ordinateur/Version imprimable 2|Version imprimable 2]]
* [[Photographie/Thèmes/La femme|La femme]] (Vide depuis 2008)
* [[Photographie/Thèmes/Les fleurs|Les fleurs]] (Vide depuis 2008)
== 02) Feuilles Sans Contenues Pertinents ==
* [[Photographie/Posemètres indépendants|Posemètres indépendants]]
* [[Renaults d'avant guerre/Sommaire|Renaults d'avant guerre Sommaire]]
* [[Sur le chemin de Messon, un modèle photo|Sur le chemin de Messon, un modèle photo]]
* [[Chimie médicinale|Chimie médicinale]]
* [[Chimie physique/Fonction d'état|Chimie physique/Fonction d'état]]
* [[Chimie physique/Introduction à la thermodynamique classique|Chimie physique/Introduction à la thermodynamique classique]]
* [[Wikijunior/Le mouflon|Le mouflon]]
* [[Wikijunior/Le renard roux|Le renard roux]]
* [[Hydrostatique|Hydrostatique]]
* [[Économie|Économie]]
* [[Économie/Économie réelle|Économie réelle]]
* [[Économie/Économie financière|Économie/Économie financière]]
== 03) La qualité du contenu de la feuille ne correspond pas à celle du livre ==
Page du livre Plantes messicoles de l'Avesnois :
* [[Plantes messicoles de l'Avesnois/Atlas des plantes messicoles/Présentation des espèces/Anchusa arvensis (L.) M.Bieb.|Anchusa arvensis (L.) M.Bieb.]]
Pages orphelines du livre Plantes messicoles de l'Avesnois :
* [[Plantes messicoles de l'Avesnois/Atlas des plantes messicoles/Présentation des espèces/Chondrilla juncea L.|Chondrilla juncea L.]] 3 février 2012 '''Les autres pages du livres sont en couleurs.'''
* [[Plantes messicoles de l'Avesnois/Plantes messicoles/Présentation des espèces/Scleranthus annuus L. subsp. annuus|Scleranthus annuus L. subsp. annuus]] 3 février 2012 '''Les autres pages du livres sont en couleurs.'''
== 04 Feuilles Tests Orphelines ==
[[Catégorie:Feuilles volantes|*]]
lxoxqagdsiq474iqns2zgxfy5a1ql50
Dictionnaire de philosophie/Karl Marx
0
83744
768685
764802
2026-06-26T04:31:31Z
PandaMystique
119061
Bot : ajout du paramètre lecture=oui au modèle {{DicoPhilo}}
768685
wikitext
text/x-wiki
{{DicoPhilo|Karl Marx|lecture=oui}}
== Formation intellectuelle et itinéraire ==
Karl Marx naît le 5 mai 1818 à Trèves, en Rhénanie, dans une famille de la bourgeoisie cultivée. Son père, Heinrich Marx, avocat, s'est converti du judaïsme au protestantisme vers 1817, moins par conviction religieuse que pour satisfaire aux contraintes civiques et juridiques que la législation prussienne faisait peser sur les Juifs, auxquels l'accès aux professions libérales et à la fonction publique était sévèrement restreint. Imprégné des Lumières françaises, lecteur de Voltaire et de Rousseau, Heinrich Marx transmet à son fils un rationalisme dont l'empreinte se retrouve dans la sensibilité du philosophe aux questions d'émancipation et de liberté civique.
En 1835, Marx s'inscrit à l'université de Bonn pour y étudier le droit, avant de rejoindre l'année suivante l'université de Berlin. C'est là qu'il entre en contact avec la philosophie de Hegel, alors dominante dans l'Allemagne intellectuelle, et qu'il fréquente le cercle des Jeunes Hégéliens de Berlin — le ''Doktorklub'' de Bruno Bauer et Karl Friedrich Koppen — avant de nouer des liens avec Arnold Ruge et Moses Hess, qui participent au même mouvement de critique philosophique appliqué à la religion et à l'État prussien. La thèse de doctorat que Marx soumet en 1841 à l'université d'Iéna, consacrée à la ''Différence de la philosophie de la nature chez Démocrite et chez Épicure''<ref>K. Marx, Differenz der demokritischen und epikureischen Naturphilosophie, 1841 ; trad. fr. in Œuvres, éd. M. Rubel, Paris, Gallimard, « Bibliothèque de la Pléiade », t. III, 1982.</ref>, témoigne déjà d'un intérêt pour le matérialisme antique et pour la question de la liberté individuelle face au déterminisme. La carrière universitaire lui étant fermée par le climat politique, Marx se tourne vers le journalisme, collabore à la ''Rheinische Zeitung'' à partir de 1842 et en devient le rédacteur en chef le 15 octobre de la même année. C'est dans cette fonction qu'il se confronte pour la première fois à des questions économiques et sociales concrètes — les débats sur le vol de bois, la misère des vignerons mosellans — qui le convainquent de l'insuffisance d'une critique purement philosophique.
L'interdiction du journal par la censure prussienne pousse Marx à l'exil. Il s'installe d'abord à Paris en 1843, où il fréquente les milieux socialistes français et les émigrés allemands, rencontre Friedrich Engels — qui deviendra son collaborateur de toute une vie — et rédige les ''Manuscrits économico-philosophiques de 1844'', texte resté inédit de son vivant mais devenu central pour la compréhension de sa pensée de jeunesse. Expulsé de France en 1845 à la demande du gouvernement prussien, Marx se réfugie à Bruxelles, où il écrit avec Engels ''L'Idéologie allemande'' (1845-1846, publiée posthumément) et le ''Manifeste du parti communiste'' (1848). Après les échecs des révolutions de 1848, il s'établit définitivement à Londres en 1849. C'est là, dans des conditions matérielles souvent précaires, qu'il consacre l'essentiel de son activité à l'élaboration de sa critique de l'économie politique, dont le premier volume du ''Capital'' paraît en 1867. Les deux volumes suivants seront publiés après sa mort par Engels. Marx meurt à Londres le 14 mars 1883.
== La critique de Hegel et le renversement de la dialectique ==
Le rapport de Marx à Hegel relève à la fois de l'héritage et de la rupture. De Hegel, Marx retient la méthode dialectique — l'idée que la réalité se développe à travers des contradictions qui se surmontent — mais il en conteste le fondement idéaliste. Pour Hegel, le mouvement de l'histoire est celui de l'Esprit qui se réalise progressivement à travers les institutions, les cultures et les États ; le réel est en dernière instance l'expression d'une rationalité qui se déploie et se reconnaît elle-même. Marx considère que cette construction, aussi puissante soit-elle, renverse les rapports effectifs : ce n'est pas la conscience qui détermine l'être social, mais l'être social qui détermine la conscience.
La première confrontation systématique se trouve dans la ''Critique du droit politique hégélien'' (1843)<ref>K. Marx, Zur Kritik der Hegelschen Rechtsphilosophie, 1843 ; trad. fr. in Œuvres, éd. cit., t. III.</ref>. Marx y montre que Hegel, en faisant de l'État la réalisation de l'Idée éthique, transforme les rapports réels entre la société civile et l'État en des moments logiques d'un développement conceptuel. Au lieu de partir de la famille et de la société civile comme réalités empiriques pour comprendre comment l'État s'en nourrit et s'y enracine, Hegel fait de l'État le sujet et de la société civile le prédicat. C'est ce que Marx appelle un renversement mystificateur : les rapports réels sont traités comme des manifestations de l'Idée, alors que c'est l'Idée qui devrait être comprise comme le reflet des rapports réels. À ce stade, la critique porte moins sur la dialectique elle-même que sur l'usage spéculatif qu'en fait Hegel dans le domaine politique. Ce n'est que trente ans plus tard, dans la postface à la seconde édition du ''Capital'' (1873)<ref name="n1">K. Marx, Postface à la seconde édition allemande du Capital, 1873, MEW, t. XXIII, p. 27 ; trad. fr. J.-P. Lefebvre, Paris, PUF, « Quadrige », 1993, p. 17.</ref>, que Marx formulera explicitement le projet de « remettre la dialectique sur ses pieds » en la retournant pour en extraire le noyau rationnel de son enveloppe mystique — donnant ainsi une portée méthodologique générale à ce qui avait d'abord été une critique politique de la philosophie de l'État.
Ce renversement a des conséquences profondes. La philosophie ne peut plus être une contemplation du mouvement de l'Esprit ; elle doit devenir une analyse des conditions matérielles de l'existence humaine. La célèbre onzième thèse sur Feuerbach<ref>K. Marx, « Thesen über Feuerbach » (1845), n° 11, MEW, t. III, p. 7 ; trad. fr. in L'Idéologie allemande, Paris, Éditions sociales, 2012, p. 1.</ref> — « Les philosophes n'ont fait qu'interpréter le monde de diverses manières ; ce qui importe, c'est de le transformer » — condense cette exigence. La philosophie est ainsi appelée à se dépasser elle-même en devenant une pratique critique orientée vers la transformation des rapports sociaux. Ce passage de la spéculation à la praxis constitue l'un des gestes fondateurs de la pensée marxienne.
== Feuerbach, la critique de la religion et le concept d'aliénation ==
La rupture avec l'idéalisme hégélien doit beaucoup à Ludwig Feuerbach, dont ''L'Essence du christianisme'' (1841) exerce sur le jeune Marx une influence considérable. Feuerbach soutient que la religion n'est pas la révélation d'un être transcendant, mais la projection par l'homme de ses propres qualités — raison, amour, volonté — dans un être imaginaire qu'il appelle Dieu. En adorant Dieu, l'homme adore en réalité sa propre essence, mais sous une forme aliénée : il s'appauvrit d'autant qu'il enrichit cet être fictif. La théologie doit donc être ramenée à l'anthropologie.
Marx accueille cette critique avec enthousiasme, mais il en perçoit rapidement les limites. Si Feuerbach a raison de dissoudre l'essence religieuse dans l'essence humaine, il laisse sans réponse la question de savoir pourquoi l'homme a besoin de cette projection. Autrement dit, Feuerbach décrit le mécanisme de l'aliénation religieuse sans en expliquer les causes. Or, pour Marx, l'aliénation religieuse a ses racines dans l'aliénation sociale et économique. La religion est « le soupir de la créature opprimée, le cœur d'un monde sans cœur, l'esprit de conditions dépourvues d'esprit »<ref>K. Marx, « Zur Kritik der Hegelschen Rechtsphilosophie. Einleitung », in Deutsch-Französische Jahrbücher, 1844, MEW, t. I, p. 378 ; trad. fr. in Œuvres, éd. cit., t. III, p. 382.</ref>. Elle est à la fois l'expression d'une détresse réelle et la protestation contre cette détresse. La critique de la religion débouche donc nécessairement sur la critique des conditions sociales qui la rendent nécessaire.
Les ''Thèses sur Feuerbach'' (1845), texte bref mais dense, précisent la nature de cette insuffisance. Feuerbach conçoit l'homme comme un être générique, abstrait, défini par des propriétés naturelles invariables. Il ignore que l'essence humaine n'est pas une abstraction résidant dans l'individu isolé, mais « l'ensemble des rapports sociaux »<ref>K. Marx, « Thesen über Feuerbach », n° 6, MEW, t. III, p. 6.</ref>. C'est cette conception relationnelle et historique de l'être humain que Marx oppose au naturalisme de Feuerbach. L'homme n'est pas une nature donnée une fois pour toutes ; il se constitue à travers son activité productive et ses rapports avec les autres dans des conditions historiques déterminées. Le concept d'aliénation est ainsi retravaillé : il ne désigne plus seulement la projection illusoire dans la religion, mais un ensemble de processus par lesquels l'activité humaine, ses produits et ses rapports sociaux prennent une forme étrangère et hostile à ceux qui les produisent.
== La conception matérialiste de l'histoire ==
=== Les fondements ===
La conception matérialiste de l'histoire, que Marx et Engels élaborent principalement dans ''L'Idéologie allemande'' et que Marx résume dans la célèbre préface à la ''Contribution à la critique de l'économie politique'' (1859)<ref>K. Marx, Zur Kritik der politischen Ökonomie. Vorwort, 1859, MEW, t. XIII, p. 8-9 ; trad. fr. in Œuvres, éd. cit., t. I, 1963, p. 272-275.</ref>, constitue le cadre général dans lequel s'inscrit l'ensemble de la pensée marxienne. Son principe peut se formuler ainsi : ce ne sont pas les idées, les représentations ou les formes de conscience qui déterminent l'existence sociale des hommes, mais, à l'inverse, leur existence sociale — c'est-à-dire la manière dont ils produisent et reproduisent leur vie matérielle — qui détermine leurs formes de conscience.
Le point de départ est le constat que les hommes, pour vivre, doivent produire les moyens de leur subsistance. Cette production n'est pas un acte isolé mais un processus social qui engage des rapports déterminés entre les individus. À chaque stade du développement historique correspondent des forces productives — l'ensemble des moyens techniques, des savoirs et des capacités humaines mobilisés dans la production — et des rapports de production — les relations sociales dans lesquelles les hommes entrent pour produire, et qui déterminent notamment les formes de propriété. L'ensemble formé par les forces productives et les rapports de production constitue ce que Marx appelle le mode de production, qui est la base réelle sur laquelle s'élève une superstructure juridique et politique, à laquelle correspondent des formes de conscience sociale déterminées.
Le dynamisme de l'histoire tient à la tension entre forces productives et rapports de production. À un certain moment du développement, les rapports de production, qui avaient favorisé le déploiement des forces productives, deviennent une entrave à leur développement ultérieur. S'ouvre alors, selon Marx, une époque de transformation sociale au cours de laquelle les formes juridiques, politiques, religieuses et philosophiques — en un mot, les formes idéologiques — à travers lesquelles les hommes prennent conscience de ce conflit et le mènent jusqu'au bout, se trouvent bouleversées. La succession des modes de production — Marx mentionne, dans la préface de 1859, les formes « asiatique, antique, féodale et bourgeoise moderne » — dessine ainsi les grandes époques de la formation sociale. Il convient toutefois de ne pas lire cette énumération comme une loi universelle de développement, applicable mécaniquement à toutes les sociétés : Marx lui-même la présente comme une esquisse portant sur l'Europe occidentale, et la catégorie de « mode de production asiatique » a fait l'objet de débats considérables, aussi bien du vivant de Marx que dans la tradition ultérieure. On reviendra plus bas sur les mises en garde que Marx formule explicitement à cet égard.
=== Mode de production et formation sociale ===
La notion de mode de production ne désigne pas simplement une technique ou un type d'économie, mais une totalité articulée de rapports sociaux. Le mode de production capitaliste, qui occupe l'essentiel de l'analyse de Marx, se caractérise par la séparation entre les propriétaires des moyens de production (la classe capitaliste) et les travailleurs qui, ne possédant que leur force de travail, sont contraints de la vendre pour subsister. Cette séparation, qui est le résultat d'un long processus historique que Marx décrit sous le nom d'« accumulation primitive », constitue la condition structurelle du rapport d'exploitation capitaliste.
L'un des apports les plus remarquables de l'analyse marxienne est la reconstitution historique de ce processus de séparation, que Marx nomme l'« accumulation primitive » et à laquelle il consacre la dernière partie du livre I du ''Capital''. Il y montre que le capitalisme ne naît pas d'un processus pacifique d'épargne et d'échange, comme le veut le récit des économistes classiques, mais d'un mouvement violent de dépossession des producteurs directs — paysans chassés de leurs terres par les enclosures en Angleterre, dissolution des rapports féodaux, colonisation, traite négrière, législation sanguinaire contre le vagabondage. L'accumulation primitive n'est pas un épisode pré-capitaliste dont le système marchand serait ensuite le développement naturel ; elle est la condition historique de possibilité du capitalisme, et Marx souligne que ses méthodes sont tout sauf idylliques. Ce chapitre du ''Capital'' est aussi l'un de ceux où Marx déploie avec le plus de force ses talents d'historien et de polémiste, en documentant avec précision les législations, les expropriations et les résistances populaires qui accompagnent la naissance du salariat moderne.
Il importe de noter que Marx ne conçoit pas le rapport entre la base économique et la superstructure de manière mécanique. Les formes juridiques, politiques et idéologiques ne sont pas le simple reflet passif des rapports de production ; elles possèdent une consistance propre, exercent en retour une influence sur la base économique et peuvent, dans certaines conditions, jouer un rôle actif dans les processus de transformation sociale. La correspondance d'Engels avec différents interlocuteurs dans les années 1890 insiste sur ce point : le matérialisme historique ne prétend pas que le facteur économique est le seul facteur déterminant, mais qu'il est déterminant « en dernière instance »<ref>F. Engels, lettre à J. Bloch du 21 septembre 1890, MEW, t. XXXVII, p. 463. La formule « en dernière instance » (in letzter Instanz) est d'Engels, non de Marx.</ref>. Les formes superstructurelles disposent d'une autonomie relative et leur développement obéit en partie à une logique interne propre.
Cette précision est importante pour comprendre le statut de l'analyse marxienne. Il ne s'agit pas d'un déterminisme économique simpliste qui réduirait l'histoire à l'action d'un seul facteur, mais d'une tentative pour penser l'articulation complexe entre les différentes dimensions de la vie sociale — production, droit, politique, religion, art, philosophie — en rapportant cette articulation aux conditions matérielles de l'existence. C'est ce que Marx appelle la « méthode d'économie politique » dans l'introduction aux ''Grundrisse'' (1857-1858)<ref name="n2">K. Marx, Einleitung zur Kritik der politischen Ökonomie (1857), MEW, t. XIII, p. 631-642 ; trad. fr. in Grundrisse, Paris, Éditions sociales, 2011, t. I.</ref> : partir non de catégories abstraites, mais des rapports réels dans leur interdépendance concrète.
== L'aliénation ==
Le concept d'aliénation traverse l'œuvre de Marx, depuis les écrits de jeunesse jusqu'au ''Capital'', même si la terminologie et l'usage qu'il en fait évoluent. Dans les ''Manuscrits de 1844'', Marx distingue quatre dimensions de l'aliénation du travail dans le mode de production capitaliste.
La première concerne le rapport du travailleur au produit de son travail. Le produit apparaît comme un objet étranger, comme une puissance indépendante qui se dresse face à celui qui l'a créé. Plus le travailleur produit, plus il se trouve dépossédé, car le produit de son travail appartient à un autre et s'accumule comme capital qui renforce la domination exercée sur lui. La deuxième dimension touche l'activité productive elle-même : le travail n'est pas pour le travailleur l'expression de ses capacités et de sa personnalité, mais une activité subie, contrainte, dans laquelle il ne s'appartient pas. Le travailleur ne se sent pas chez lui dans son travail ; il ne se sent lui-même qu'en dehors du travail. Son travail n'est pas la satisfaction d'un besoin, mais seulement un moyen de satisfaire des besoins extérieurs à lui.
La troisième dimension est l'aliénation de l'essence humaine ou « être générique ». Marx, reprenant de manière originale un concept de Feuerbach, considère que ce qui distingue l'activité humaine de l'activité animale est son caractère conscient, universel et libre : l'homme ne produit pas seulement sous l'emprise du besoin immédiat, mais peut produire selon les lois de la beauté, se rapporter à l'ensemble de la nature comme à son corps inorganique, et faire de son activité vitale un objet de sa volonté et de sa conscience. Le travail aliéné renverse ce rapport : l'activité vitale, la vie productive, n'est plus qu'un moyen d'assurer l'existence physique. L'homme est ainsi aliéné de ce qui constitue sa spécificité. La quatrième dimension découle des précédentes : l'aliénation de l'homme par rapport à l'homme. Dans le travail aliéné, le rapport de l'individu à l'autre est lui-même un rapport d'étrangeté et de domination ; le produit du travail ne lui appartient pas parce qu'il appartient à un autre homme qui ne travaille pas — le capitaliste.
La question de savoir si le concept d'aliénation disparaît dans les œuvres de maturité ou s'il y persiste sous d'autres formes a fait l'objet de controverses interprétatives importantes. Louis Althusser a défendu la thèse d'une « coupure épistémologique » séparant un jeune Marx encore pris dans une problématique philosophique humaniste d'un Marx scientifique, auteur du ''Capital'', qui aurait abandonné le concept d'aliénation au profit de catégories strictement économiques. D'autres interprètes font valoir que le concept de fétichisme de la marchandise, central dans le ''Capital'', prolonge et approfondit l'analyse de l'aliénation sous une forme plus rigoureuse. Sans trancher ici ce débat, on peut observer que la préoccupation pour les processus par lesquels les rapports sociaux prennent une forme réifiée et se retournent contre les individus qui les constituent demeure présente d'un bout à l'autre de l'œuvre.
== La critique de l'idéologie ==
On regroupe souvent sous le terme de « théorie marxienne de l'idéologie » un ensemble de formulations dont il faut d'emblée souligner le caractère dispersé. Marx n'a pas laissé de traité systématique de l'idéologie : les développements les plus étendus se trouvent dans ''L'Idéologie allemande'', mais des formulations distinctes apparaissent dans la préface de 1859, dans le ''Capital'' et dans les écrits politiques. Le statut exact de ces formulations — leur continuité, leur rapport au concept de fétichisme, le degré auquel elles constituent une « théorie » cohérente — fait l'objet de discussions savantes, notamment entre les lectures d'inspiration althussérienne, qui tendent à distinguer nettement idéologie et fétichisme, et celles qui, à la suite de Lukács, les articulent dans une théorie unitaire de la réification. Sous ces réserves, on peut dégager les lignes de force suivantes.
Par idéologie, Marx entend un ensemble de représentations, d'idées et de valeurs qui, tout en se présentant comme universelles et autonomes, expriment en réalité les intérêts particuliers de la classe dominante et contribuent à les légitimer. L'idéologie n'est pas un simple mensonge ni une erreur individuelle ; elle est une forme de conscience socialement nécessaire, produite par les conditions mêmes de l'existence dans une société divisée en classes. La métaphore de la ''camera obscura'', que Marx emploie dans ''L'Idéologie allemande'', illustre ce renversement : de même que, dans la chambre noire, les objets apparaissent inversés, de même, dans l'idéologie, les rapports réels entre les hommes apparaissent à l'envers. Les idées semblent gouverner le monde alors qu'elles ne sont que l'expression intellectuelle des rapports matériels dominants. Les pensées dominantes d'une époque ne sont jamais que les pensées de la classe dominante, c'est-à-dire de la classe qui dispose des moyens de production matérielle et, par là, des moyens de production intellectuelle<ref name="n3">K. Marx et F. Engels, Die deutsche Ideologie (1845-1846), MEW, t. III, p. 33 ; trad. fr. Paris, Éditions sociales, 2012, p. 30.</ref>.
La critique de l'idéologie ne conduit pas cependant à un pur relativisme. Marx ne soutient pas que toute pensée est une simple illusion de classe. La science — et notamment la critique de l'économie politique qu'il entreprend — prétend accéder à une connaissance objective des rapports sociaux, précisément en dévoilant les conditions matérielles que l'idéologie masque. Le dépassement de l'idéologie suppose à la fois un travail théorique de démystification et une transformation pratique des conditions sociales qui la produisent. C'est en ce sens que la critique de l'idéologie est indissociable de la critique de l'économie politique et de la perspective d'une transformation sociale : tant que subsistent les rapports de domination de classe, les formes idéologiques qui les expriment et les légitiment se reproduisent nécessairement.
Il faut souligner que la critique de l'idéologie s'exerce d'abord et en priorité à l'encontre de l'économie politique elle-même. Marx reconnaît à la tradition de Smith et de Ricardo le mérite d'avoir identifié le travail comme source de la richesse, mais il leur reproche de naturaliser les catégories du capitalisme — propriété privée, salaire, profit, rente — en les traitant comme des données éternelles au lieu de les comprendre comme des formes historiques déterminées. L'économie politique « classique » décrit le capitalisme, mais en le présentant comme l'horizon indépassable de toute société humaine, elle assume une fonction idéologique. La critique de Marx consiste à montrer que ces catégories, loin d'être naturelles, sont les produits de rapports sociaux historiquement situés et, à ce titre, transitoires.
== La critique de l'économie politique ==
=== La marchandise et la valeur ===
Le ''Capital'' s'ouvre par l'analyse de la marchandise, que Marx présente comme la « cellule » de la société bourgeoise. Ce choix n'est pas arbitraire : la marchandise est la forme élémentaire dans laquelle se manifeste la richesse dans les sociétés où domine le mode de production capitaliste. Toute marchandise possède un double caractère : elle est à la fois valeur d'usage — elle satisfait un besoin humain déterminé — et valeur d'échange — elle est échangeable contre d'autres marchandises dans des proportions déterminées.
La question que Marx pose est celle du fondement de la valeur d'échange : qu'est-ce qui fait que des marchandises qualitativement différentes — une table, du linge, du blé — sont commensurables et peuvent s'échanger dans des proportions définies ? Si l'on fait abstraction des qualités sensibles qui font de chaque marchandise une valeur d'usage particulière, il ne leur reste plus, selon Marx, qu'une propriété commune : celle d'être des produits du travail humain. Mais pas du travail concret — celui du menuisier, du tisserand, du cultivateur — qui produit des valeurs d'usage déterminées. Il faut faire abstraction du caractère concret du travail pour ne retenir que ce que tous ces travaux ont en commun : la dépense de force humaine de travail en général, ce que Marx appelle le travail abstrait. La valeur d'une marchandise est alors déterminée par la quantité de travail socialement nécessaire à sa production, c'est-à-dire le temps de travail requis dans les conditions moyennes de production, avec le degré moyen d'habileté et d'intensité du travail en vigueur dans une société donnée.
Ce double caractère de la marchandise — valeur d'usage et valeur — reflète le double caractère du travail qui la produit : travail concret, utile, créateur de valeurs d'usage, et travail abstrait, créateur de valeur. Marx considère cette distinction comme l'un des apports les plus importants de son analyse. Elle permet de comprendre que la valeur n'est pas une propriété naturelle des choses, mais un rapport social qui prend la forme d'une propriété des choses. Dans une société marchande, les rapports entre les producteurs ne se manifestent pas directement comme rapports sociaux entre personnes, mais comme rapports entre les produits de leur travail. C'est cette inversion que Marx nomme le « fétichisme de la marchandise ».
=== Le fétichisme de la marchandise ===
L'analyse du fétichisme de la marchandise, développée dans le premier chapitre du ''Capital''<ref>K. Marx, Das Kapital, livre I, chap. 1, section 4 : « Der Fetischcharakter der Ware und sein Geheimnis », MEW, t. XXIII, p. 85-98 ; trad. fr. Lefebvre, p. 81-95.</ref>, constitue l'un des passages les plus commentés de l'œuvre. Marx y montre que dans une économie marchande, les rapports sociaux entre les producteurs prennent nécessairement la forme de rapports entre les produits du travail. Les marchandises semblent posséder une valeur intrinsèque, comme si celle-ci était une propriété naturelle des objets, alors qu'elle n'est que l'expression d'un rapport social déterminé. Il y a fétichisme au sens où les hommes attribuent aux choses — et singulièrement à l'argent — des propriétés qui ne leur appartiennent pas en propre mais qui ne sont que le reflet des rapports entre les producteurs.
Ce processus n'est pas une illusion qu'il suffirait de dissiper par la pensée ; il est enraciné dans la structure même de la production marchande. Tant que les travaux privés des producteurs indépendants ne se valident socialement qu'à travers l'échange de leurs produits sur le marché, les rapports sociaux revêtent nécessairement cette forme réifiée. Le fétichisme de la marchandise n'est donc ni une erreur de perception ni une mystification délibérée, mais une apparence objectivement produite par les rapports sociaux eux-mêmes. On mesure ici la distance qui sépare l'analyse marxienne d'une simple « dénonciation » de l'idéologie : il ne s'agit pas de critiquer de fausses croyances, mais de comprendre pourquoi les rapports sociaux prennent nécessairement une forme qui en dissimule la nature.
Le fétichisme atteint son expression la plus achevée dans la forme monétaire. L'argent, qui n'est d'abord que l'équivalent général facilitant les échanges, semble posséder par nature le pouvoir de commander le travail et d'acquérir toute marchandise. Marx démonte cette apparence en montrant que l'argent n'est que la cristallisation du travail social abstrait, la forme dans laquelle la société reconnaît et valide les travaux privés. Mais cette forme acquiert une autonomie qui occulte sa genèse : l'argent devient un objet de désir en lui-même, et la logique de l'accumulation monétaire — acheter pour vendre plus cher, transformer l'argent en plus d'argent — constitue le ressort du capitalisme.
=== La force de travail et la plus-value ===
L'une des questions centrales que pose Marx est celle de l'origine du profit. Dans le cadre de sa théorie de la valeur-travail, si les marchandises s'échangent conformément au temps de travail socialement nécessaire à leur production, comment le capitaliste peut-il tirer un surplus de l'échange ? La réponse, selon l'analyse du livre I du ''Capital'', passe par la découverte d'une marchandise particulière dont la consommation est elle-même source de valeur : la force de travail.
La force de travail est, pour Marx, la capacité de travail que le travailleur vend au capitaliste sur le marché du travail. Comme toute marchandise, elle a une valeur, déterminée par le temps de travail nécessaire à sa production et à sa reproduction — c'est-à-dire par la valeur des moyens de subsistance (nourriture, logement, vêtement, éducation) nécessaires à l'entretien du travailleur et de sa famille. Mais la force de travail a cette propriété singulière que son usage — le travail effectif — crée plus de valeur que sa propre valeur. Le capitaliste achète la force de travail à sa valeur, mais il fait travailler l'ouvrier au-delà du temps nécessaire pour reproduire cette valeur. La différence entre la valeur produite par le travailleur et la valeur de sa force de travail constitue ce que Marx appelle la plus-value, qui est, dans son analyse, la source du profit capitaliste.
Marx distingue deux formes de plus-value. La plus-value absolue résulte de l'allongement de la journée de travail<ref>K. Marx, Das Kapital, livre I, chap. 8 : « Der Arbeitstag », MEW, t. XXIII, p. 245-320.</ref> au-delà du temps de travail nécessaire : plus le capitaliste prolonge la durée du travail, plus il extrait de surtravail. Cette forme trouve sa limite dans les résistances physiques et sociales des travailleurs, d'où les luttes historiques pour la limitation de la journée de travail, auxquelles Marx consacre un chapitre du ''Capital'' richement documenté. La plus-value relative résulte de la diminution du temps de travail nécessaire par l'augmentation de la productivité du travail dans les branches qui produisent les moyens de subsistance. L'introduction de machines et de procédés plus efficaces permet de réduire la valeur de la force de travail sans abaisser le niveau de vie réel, augmentant ainsi la part de surtravail. C'est cette forme qui prédomine à mesure que le capitalisme se développe et que la production s'industrialise.
=== L'accumulation du capital et ses contradictions ===
La plus-value n'est pas simplement consommée par le capitaliste ; elle est en grande partie réinvestie dans la production sous forme de capital additionnel. C'est le processus d'accumulation du capital, que Marx analyse comme le ressort fondamental du développement capitaliste. La concurrence entre capitalistes les contraint à réinvestir continuellement leurs profits pour accroître la productivité du travail, sous peine d'être éliminés par des concurrents plus efficaces. Ce processus engendre une concentration et une centralisation croissantes du capital : les entreprises les plus grandes absorbent ou éliminent les plus petites, et le capital se concentre entre un nombre toujours plus restreint de mains.
Marx analyse la composition du capital en distinguant le capital constant — la valeur des moyens de production (machines, matières premières, bâtiments) — et le capital variable — la valeur de la force de travail achetée. Seul le capital variable, c'est-à-dire le travail vivant, crée de la valeur nouvelle et donc de la plus-value. Or, le développement capitaliste se caractérise par une hausse tendancielle de la composition organique du capital, c'est-à-dire du rapport entre capital constant et capital variable : à mesure que la production se mécanise, la part du capital investi en machines et en matières premières augmente par rapport à celle investie en force de travail. Si le taux de plus-value reste constant, cette évolution entraîne une tendance à la baisse du taux de profit — le rapport entre la plus-value et le capital total investi<ref>K. Marx, Das Kapital, livre III, 3e section, MEW, t. XXV, p. 221-277 ; trad. fr. Éditions sociales, 1977, t. VI.</ref>.
Marx présente cette loi de la baisse tendancielle du taux de profit comme « la loi la plus importante de l'économie politique moderne ». Elle ne décrit pas une chute mécanique et continue, mais une tendance contrecarrée par un ensemble de facteurs — intensification de l'exploitation, abaissement du prix des éléments du capital constant, commerce extérieur, expansion vers de nouveaux marchés — qui ralentissent ou compensent temporairement la baisse. Il faut préciser que cette loi ne constitue pas la seule explication marxienne des crises capitalistes : Marx avance aussi des analyses portant sur les crises de surproduction, les disproportions entre secteurs et les contradictions liées à la réalisation de la valeur sur le marché. C'est l'articulation de ces différentes lignes d'analyse, plutôt qu'un facteur unique, qui rend compte, dans la théorie marxienne, du caractère cyclique et des crises récurrentes du capitalisme. Il faut ajouter que le passage des valeurs aux prix de production, tel que Marx le propose dans le livre III du ''Capital'', soulève un problème théorique considérable — connu sous le nom de « problème de la transformation » — dont la résolution a fait l'objet de débats prolongés parmi les économistes, aussi bien partisans que critiques de la théorie de la valeur-travail.
Les crises, pour Marx, ne sont pas des accidents extérieurs mais des moments nécessaires du processus d'accumulation, par lesquels le système restaure temporairement les conditions de sa propre reproduction — par la destruction de capital, la dévalorisation des marchandises et la reconstitution d'une « armée industrielle de réserve » de travailleurs sans emploi qui fait pression à la baisse sur les salaires. L'analyse des crises est prolongée par celle de ce que Marx appelle la « loi générale de l'accumulation capitaliste ». À mesure que le capital s'accumule, la classe ouvrière subit un double mouvement : d'un côté, la demande de travail croît avec l'accumulation ; de l'autre, la mécanisation rend une partie des travailleurs surnuméraires, constituant cette armée industrielle de réserve dont la taille fluctue au rythme des cycles économiques. La tendance de long terme est à la polarisation de la société entre une richesse accumulée à un pôle et une masse croissante de travailleurs dont l'existence dépend entièrement des aléas du marché. Marx voit dans cette polarisation non pas une fatalité, mais le produit d'un rapport social déterminé, historiquement constitué et, en principe, transformable.
== Les classes sociales et la lutte des classes ==
L'affirmation selon laquelle « l'histoire de toute société jusqu'à nos jours n'a été que l'histoire de luttes de classes »<ref name="n4">K. Marx et F. Engels, Manifest der Kommunistischen Partei, 1848, MEW, t. IV, p. 462 ; trad. fr. in Œuvres, éd. cit., t. I, p. 161.</ref>, qui ouvre le ''Manifeste du parti communiste'', condense l'une des thèses les plus connues de Marx. La lutte des classes n'est pas pour lui un phénomène accidentel ou secondaire, mais le principe moteur de la transformation historique. Dans chaque mode de production, une classe dominante — qui possède ou contrôle les moyens de production — s'oppose à une ou plusieurs classes dominées dont le travail est la source de la richesse sociale.
Marx ne donne nulle part une théorie systématique et achevée des classes sociales — le chapitre du livre III du ''Capital'' qui devait y être consacré s'interrompt après quelques paragraphes. Néanmoins, les éléments dispersés dans l'ensemble de l'œuvre permettent de reconstituer sa conception. Une classe se définit d'abord par sa position dans les rapports de production : propriétaire ou non-propriétaire des moyens de production. Mais cette détermination objective ne suffit pas. Dans la ''Misère de la philosophie'' (1847), Marx décrit le passage du prolétariat d'une masse dispersée, partageant objectivement les mêmes conditions, à une classe organisée et consciente de ses intérêts communs — un passage que les commentateurs ont formulé dans les termes hégéliens de « classe en soi » et de « classe pour soi ». Cette transformation n'est pas automatique ; elle dépend d'un ensemble de conditions historiques, culturelles et politiques.
L'analyse que Marx donne des classes dans la société capitaliste est plus complexe que le schéma binaire capitalistes/prolétaires. Il identifie de nombreuses catégories intermédiaires — petite bourgeoisie, paysannerie, Lumpenproletariat — et accorde une attention soutenue aux fractions de classe et à leurs rapports. Les écrits politiques et historiques, en particulier ''Le 18 Brumaire de Louis Bonaparte'' (1852)<ref>K. Marx, Der achtzehnte Brumaire des Louis Bonaparte, 1852, MEW, t. VIII ; trad. fr. in Œuvres, éd. cit., t. IV, 1994.</ref> et ''Les Luttes de classes en France'' (1850), offrent des analyses remarquablement nuancées de la dynamique des alliances et des conflits entre les différentes couches sociales. Marx y montre que la classe paysanne, parcellaire et dispersée, incapable de s'organiser politiquement par elle-même, finit par déléguer sa représentation à un pouvoir autoritaire — en l'occurrence Louis-Napoléon Bonaparte. Il y montre aussi que la bourgeoisie elle-même est traversée de tensions entre ses différentes fractions — bourgeoisie foncière, bourgeoisie industrielle, bourgeoisie financière — dont les intérêts ne convergent pas toujours.
La lutte des classes dans le capitalisme oppose fondamentalement la bourgeoisie, qui détient les moyens de production, et le prolétariat, qui n'a d'autre ressource que la vente de sa force de travail. Mais cette opposition ne se réduit pas à un conflit d'intérêts économiques ; elle structure l'ensemble de la vie sociale, politique et culturelle. La lutte pour la limitation de la journée de travail, que Marx analyse en détail dans le ''Capital'', illustre cette dimension : elle met en jeu non seulement des intérêts matériels immédiats, mais des conceptions antagonistes du droit, de la liberté et de la dignité humaine. Le capitaliste invoque son droit de propriétaire à disposer de la marchandise qu'il a achetée — la force de travail — comme il l'entend ; le travailleur invoque son droit à ne pas être usé jusqu'à la mort. C'est la lutte sociale qui tranche entre ces droits également fondés du point de vue de la logique marchande.
== L'État et la question de la révolution ==
La conception marxienne de l'État s'inscrit dans le prolongement de l'analyse des classes sociales. L'État n'est pas pour Marx une institution neutre, arbitre impartial des conflits sociaux, mais un instrument de domination de classe. Dans le ''Manifeste'', il est défini comme un « comité qui gère les affaires communes de la classe bourgeoise tout entière ». Cette formule, souvent jugée trop schématique, doit être mise en regard des analyses plus nuancées que Marx développe dans ses écrits historiques.
Le ''18 Brumaire'' montre en effet que l'État peut acquérir, dans certaines conjonctures, une autonomie relative par rapport aux classes en présence. Le bonapartisme représente une situation où l'État, profitant de l'équilibre des forces entre les classes, semble s'élever au-dessus de la société et gouverner en son nom propre. Mais cette autonomie reste en dernière instance subordonnée aux intérêts de la propriété : l'État bonapartiste protège la structure sociale existante tout en paraissant se situer au-dessus des conflits de classe. Le pouvoir étatique, avec son appareil bureaucratique, militaire et policier, est un héritage que chaque classe dominante s'empresse de perfectionner au lieu de le briser — c'est cette observation qui conduit Marx à distinguer entre la simple conquête de l'appareil d'État et sa destruction.
La Commune de Paris (1871) représente pour Marx le premier exemple d'un pouvoir politique de type nouveau. Dans ''La Guerre civile en France''<ref>K. Marx, Der Bürgerkrieg in Frankreich, 1871, MEW, t. XVII, p. 336-362 ; trad. fr. in Œuvres, éd. cit., t. IV.</ref>, il analyse les mesures prises par la Commune — élection et révocabilité des fonctionnaires, suppression de l'armée permanente remplacée par le peuple en armes, rémunération des responsables publics au niveau du salaire ouvrier — comme les formes politiques enfin trouvées de l'émancipation du travail. Marx tend à penser la Commune comme une forme politique de transition qui rompt avec l'État bourgeois traditionnel : il y voit le type d'un pouvoir ouvrier qui commence à défaire la machine étatique séparée. Il faut toutefois noter que cette lecture est formulée dans le feu de l'événement, et que son statut — description de ce que la Commune a effectivement été ou projection de ce qu'elle aurait pu devenir — reste discuté.
La question de la transition entre le capitalisme et le communisme se pose alors dans les termes suivants. Marx distingue, dans la ''Critique du programme de Gotha'' (1875)<ref>K. Marx, Kritik des Gothaer Programms, 1875, MEW, t. XIX, p. 13-32 ; trad. fr. in Œuvres, éd. cit., t. I, p. 1413-1434.</ref>, deux phases de la société communiste. La première phase, souvent appelée ultérieurement « socialisme », porte encore les marques de l'ancienne société dont elle est issue : la répartition y obéit au principe « à chacun selon son travail », ce qui maintient une forme d'inégalité puisque les individus ont des capacités inégales. La seconde phase, le communisme proprement dit, suppose un développement des forces productives tel que la société puisse inscrire sur ses drapeaux le principe « de chacun selon ses capacités, à chacun selon ses besoins ». Entre la société capitaliste et la société communiste se place une période de transformation au cours de laquelle l'État revêt la forme d'une « dictature révolutionnaire du prolétariat ». Il convient de noter que cette formule, devenue centrale dans le marxisme postérieur — en particulier chez Lénine —, n'apparaît que de manière occasionnelle dans les textes de Marx lui-même, qui n'en précise guère le contenu institutionnel, au-delà de la référence à la Commune de Paris.
La pratique politique de Marx est indissociable de sa réflexion théorique. Son engagement dans l'Association internationale des travailleurs (la Première Internationale, fondée en 1864) illustre sa conviction que l'émancipation des travailleurs doit être l'œuvre des travailleurs eux-mêmes, et que la lutte de classe dépasse les frontières nationales. Au sein de l'Internationale, Marx s'efforce de concilier des tendances très diverses — syndicalistes anglais, mutualistes proudhoniens, républicains mazziniens, anarchistes bakouniniens — tout en défendant la nécessité d'une organisation politique autonome de la classe ouvrière. Les polémiques avec Proudhon — auquel ''La Misère de la philosophie'' (1847) oppose une critique en règle —, puis avec Bakounine, qui reproche à Marx son centralisme et son autoritarisme, éclairent les tensions constitutives du mouvement socialiste entre la libération sociale et l'organisation politique, entre la spontanéité des masses et la direction du mouvement. Ces débats conduiront à la scission de l'Internationale au congrès de La Haye en 1872, puis à sa dissolution formelle en 1876 ; ils restent instructifs sur les difficultés pratiques que pose la traduction d'une théorie de l'émancipation en stratégie politique effective.
La ''Critique du programme de Gotha'' est aussi l'occasion pour Marx de préciser sa conception du rapport entre le parti ouvrier, la transformation sociale et les revendications concrètes. Il y conteste un programme qu'il juge trop influencé par les thèses de Ferdinand Lassalle — notamment l'idée d'un « État libre » et d'une « répartition équitable du produit du travail » — en montrant que ces formules restent prisonnières de catégories bourgeoises. La distribution du produit social, rappelle Marx, ne peut être pensée indépendamment des rapports de production qui la déterminent ; parler de « répartition équitable » sans remettre en cause les rapports capitalistes, c'est rester à la surface des choses. Cet écrit, d'une grande densité argumentative, illustre la manière dont Marx articule constamment l'analyse théorique et l'intervention politique concrète.
== La méthode : dialectique et critique ==
La question de la méthode est omniprésente dans l'œuvre de Marx, sans qu'il en ait jamais livré un exposé séparé et complet. Les indications les plus précieuses se trouvent dans la postface à la seconde édition du ''Capital'' (1873)<ref name="n1" />, dans l'introduction aux ''Grundrisse'' (1857) et dans les remarques méthodologiques dispersées dans l'ensemble de l'œuvre. Trois traits principaux caractérisent la démarche marxienne.
Le premier est l'usage de la dialectique. Marx reprend à Hegel l'idée que la réalité ne peut être saisie que dans son mouvement, à travers ses contradictions et ses transformations, et non comme un ensemble de faits fixes et séparés. Mais la dialectique marxienne se distingue de la dialectique hégélienne en ce qu'elle prend pour objet non le mouvement de l'Idée mais le mouvement réel des rapports sociaux. Chez Hegel, explique Marx dans la postface au ''Capital'', la dialectique « marche sur la tête » ; il faut « la retourner pour découvrir le noyau rationnel sous l'enveloppe mystique ». Car pour Marx, l'idéal n'est rien d'autre que le matériel transposé et traduit dans la tête de l'homme. Ce renversement ne signifie pas un simple remplacement de l'Esprit par la Matière : la dialectique matérialiste vise à saisir le mouvement effectif de la réalité sociale, avec ses contradictions, ses ruptures et ses transformations.
Le deuxième trait est le mouvement de l'abstrait au concret. Dans l'introduction aux ''Grundrisse'', Marx décrit la méthode de l'économie politique comme un parcours qui va des déterminations abstraites les plus simples — la marchandise, la valeur, le travail — vers la reconstitution d'une « totalité concrète », c'est-à-dire la compréhension du mode de production capitaliste dans l'ensemble de ses déterminations et de ses articulations. Le concret n'est pas le point de départ immédiat de la connaissance, mais son résultat : il est « concret parce qu'il est la synthèse de multiples déterminations, donc unité de la diversité »<ref name="n2" />. Cette méthode n'a rien à voir avec une déduction logique à partir de principes ; elle suppose au contraire un va-et-vient constant entre l'analyse des formes abstraites et l'étude des processus historiques réels.
Le troisième trait est la dimension critique. La critique de l'économie politique n'est pas une simple correction des erreurs des économistes classiques, ni la substitution d'un système théorique à un autre. Elle consiste à montrer que les catégories de l'économie politique — valeur, capital, salaire, profit — ne sont pas des formes naturelles et éternelles de la vie économique, mais des formes sociales historiquement déterminées qui expriment des rapports sociaux spécifiques. La critique est donc à la fois une analyse de la réalité capitaliste et un dévoilement du caractère historique et transitoire de cette réalité. En montrant que le capitalisme est un mode de production parmi d'autres, qu'il est né dans des conditions historiques déterminées et qu'il porte en lui des contradictions qui rendent possible son dépassement, la critique ne se contente pas de décrire le monde : elle ouvre la perspective de sa transformation.
Il faut ajouter que la méthode de Marx n'est pas seulement une méthode d'exposition mais aussi une méthode d'investigation. Marx insiste sur la distinction entre les deux dans la postface au ''Capital'' : l'investigation doit s'emparer de la matière dans le détail, en analyser les formes de développement et découvrir le lien interne de ces formes ; ce n'est qu'une fois ce travail accompli que le mouvement réel peut être présenté de manière adéquate. Le plan du ''Capital'' — de la marchandise à l'argent, de l'argent au capital, du capital à la plus-value, de la plus-value aux formes concrètes du profit, de l'intérêt et de la rente — ne retrace donc pas la genèse chronologique du capitalisme mais reconstruit sa logique interne, en partant des formes les plus abstraites pour en déployer progressivement la complexité. Cette démarche confère à l'œuvre sa rigueur architecturale, mais aussi sa difficulté : le lecteur doit accepter de traverser un long parcours de déterminations abstraites avant que le système ne se révèle dans sa totalité articulée.
== Marx et la nature : travail, technique, écologie ==
Un aspect de la pensée de Marx, longtemps négligé mais qui retient aujourd'hui une attention croissante, concerne son analyse du rapport entre le travail humain et la nature. Dans le livre I du ''Capital'', Marx définit le travail comme un processus entre l'homme et la nature, dans lequel l'homme « règle et contrôle par sa propre action son métabolisme avec la nature »<ref>K. Marx, Das Kapital, livre I, chap. 5, section 1, MEW, t. XXIII, p. 192 ; trad. fr. Lefebvre, p. 199-200.</ref>. Cette notion de métabolisme (''Stoffwechsel'') désigne l'échange matériel constant par lequel les sociétés humaines prélèvent des ressources naturelles, les transforment et rejettent des déchets dans l'environnement. Le travail humain est ainsi inscrit dans un rapport d'interdépendance avec les cycles naturels, et la terre est présentée, à côté du travail, comme l'une des deux sources de toute richesse.
Marx observe que le capitalisme, en concentrant la population dans les villes et en intensifiant l'exploitation agricole, perturbe ce métabolisme entre l'homme et la terre. Il s'appuie sur les travaux du chimiste Justus von Liebig concernant l'épuisement des sols pour montrer que l'agriculture capitaliste ne se contente pas d'exploiter le travailleur : elle épuise aussi la fertilité naturelle de la terre, en rompant le cycle des nutriments par la séparation entre la production agricole et la consommation urbaine<ref>K. Marx, Das Kapital, livre I, chap. 13, section 10, et livre III, chap. 47, MEW, t. XXIII, p. 528 et t. XXV, p. 821.</ref>. Marx décrit à ce propos une fracture irréparable (''unheilbarer Riß'') dans le métabolisme entre la société et la nature. Il est important de distinguer ce que Marx écrit effectivement — des passages dispersés dans le ''Capital'' et les ''Grundrisse'', portant sur l'épuisement des sols, le machinisme et la subordination de la nature à la logique du profit — de la reconstruction systématique qu'en proposent des travaux contemporains, notamment ceux de John Bellamy Foster (''Marx's Ecology'', 2000), Kohei Saito (''Karl Marx's Ecosocialism'', 2017) et Paul Burkett (''Marx and Nature'', 1999). Ces auteurs ont forgé le concept de « rupture métabolique » pour donner une cohérence écologique d'ensemble à des remarques que Marx n'avait pas lui-même rassemblées en une théorie unifiée de la nature. Le débat reste ouvert sur la portée qu'il convient de leur accorder.
Cette dimension de l'œuvre ne se réduit pas à quelques remarques isolées. Elle traverse l'analyse du machinisme, de la grande industrie et de l'agriculture capitaliste dans le ''Capital'', et elle se retrouve dans les réflexions sur la technique et ses effets ambivalents. Marx reconnaît dans le développement des forces productives une puissance d'émancipation potentielle — la machine pourrait libérer l'homme du labeur pénible et réduire le temps de travail nécessaire — mais il montre que, sous les rapports capitalistes, la machine se retourne contre le travailleur : elle l'assujettit à son rythme, le déqualifie, le rend remplaçable et allonge paradoxalement sa journée de travail en dévalorisant sa force de travail. La question de la technique chez Marx n'est donc ni celle d'un optimisme technologique sans réserve, ni celle d'une condamnation de la machine en tant que telle ; c'est la question de la forme sociale dans laquelle la technique est mise en œuvre, et des fins auxquelles elle est subordonnée.
== Philosophie de l'histoire et question du progrès ==
La pensée de Marx a souvent été réduite à un schéma évolutionniste linéaire, selon lequel l'humanité passerait nécessairement de la communauté primitive au communisme en traversant les stades de l'esclavage antique, du féodalisme et du capitalisme. Cette lecture téléologique s'autorise de certaines formulations, notamment de la préface de 1859, mais elle méconnaît la complexité et les hésitations de la réflexion marxienne sur l'histoire.
Plusieurs textes tardifs conduisent à corriger une interprétation trop téléologique de l'œuvre. Dans une lettre de 1877 à la rédaction des ''Otétchestvennyïé Zapiski''<ref>K. Marx, lettre à la rédaction des Otétchestvennyïé Zapiski, novembre 1877, MEW, t. XIX, p. 111 ; trad. fr. in Œuvres, éd. cit., t. II, p. 1553-1555.</ref>, Marx refuse explicitement que l'on transforme son esquisse de l'origine du capitalisme en Europe occidentale en une « théorie historico-philosophique de la marche générale, fatalement imposée à tous les peuples ». L'étude des modes de production extra-européens, notamment du mode de production asiatique, témoigne de la conscience que le développement historique ne se réduit pas à une trajectoire unique. Les notes ethnologiques de la fin de sa vie, consacrées notamment aux travaux de Lewis Morgan sur les sociétés amérindiennes, montrent un intérêt soutenu pour les formes sociales non-européennes et les voies alternatives de développement. La question de savoir si ces textes suffisent à dégager Marx de toute philosophie de l'histoire, ou s'il subsiste dans l'œuvre une tension non résolue entre l'analyse historique concrète et les schémas de développement hérités de Hegel, reste discutée dans la littérature savante.
La question du progrès se pose chez Marx de manière ambivalente. D'un côté, le développement des forces productives sous le capitalisme est présenté comme une condition nécessaire de l'émancipation future : c'est parce que le capitalisme a développé à un degré sans précédent la capacité productive de l'humanité que devient possible, en principe, une société dans laquelle la satisfaction des besoins de tous serait assurée. De l'autre, ce développement s'accompagne de formes d'exploitation et de destruction — destruction de la nature, épuisement des travailleurs, déracinement des communautés traditionnelles — qui en font un processus profondément contradictoire. Marx ne célèbre pas le progrès capitaliste ; il en analyse les conditions, les formes et les limites, en montrant que le développement des forces productives et le développement humain ne coïncident pas nécessairement dans le cadre des rapports capitalistes.
== Communisme et émancipation humaine ==
Le communisme tel que Marx l'envisage ne se réduit pas à un programme politique ou à un modèle d'organisation économique. Il est conçu comme le mouvement réel d'abolition de l'état de choses existant — c'est-à-dire comme un processus historique et non comme un idéal abstrait à réaliser. Dans les ''Manuscrits de 1844'', Marx le définit comme « l'appropriation réelle de l'essence humaine par l'homme et pour l'homme », comme « le retour complet de l'homme à lui-même en tant qu'être social ». Loin de se limiter à un changement de propriété des moyens de production, le communisme vise une transformation de l'ensemble des rapports humains — rapport au travail, à la nature, aux autres et à soi-même.
Marx refuse de dessiner en détail les contours de la société future, estimant que c'est là une tâche qui incombe à la pratique historique elle-même et non à la spéculation philosophique. Les quelques indications qu'il donne portent moins sur les institutions que sur les principes. Dans une société communiste, la division sociale du travail serait dépassée, non pas au sens où chacun ferait la même chose, mais au sens où l'assignation des individus à une activité exclusive et contrainte — ouvrier, paysan, intellectuel — serait abolie au profit d'un développement pluriel des capacités. La célèbre formulation de ''L'Idéologie allemande'', selon laquelle chacun pourrait « chasser le matin, pêcher l'après-midi, faire de l'élevage le soir, faire de la critique après le repas »<ref name="n3" />, exprime moins un programme littéral qu'une aspiration à la libre disposition de son temps et de ses activités.
L'abolition de la propriété privée des moyens de production — qui ne se confond pas avec l'abolition de la propriété personnelle — vise à supprimer les conditions structurelles de l'exploitation et de la domination de classe. La production serait désormais organisée de manière consciente et collective, au lieu d'être abandonnée à l'anarchie du marché et à la logique aveugle de l'accumulation. Marx envisage le communisme comme un « libre développement de chacun » qui est la « condition du libre développement de tous », selon la formule du ''Manifeste''<ref name="n4" />. L'individu n'y est pas dissous dans la collectivité, mais c'est au contraire la collectivité qui constitue le cadre dans lequel l'individualité peut s'épanouir librement, une fois libérée des contraintes de la nécessité matérielle et de la domination sociale.
Il convient de distinguer cette conception de celle que les régimes se réclamant du marxisme ont mise en œuvre au XXe siècle. Bien des éléments de la pensée de Marx — son attachement à la liberté individuelle, sa méfiance à l'égard de l'État, sa critique de la bureaucratie — contrastent avec les pratiques autoritaires des États qui se sont réclamés de son héritage. Le rapport entre la pensée de Marx et le marxisme ultérieur — dans ses multiples variantes : léninisme, social-démocratie, maoïsme, marxisme occidental — constitue une question historique et philosophique à part entière, qu'il importe de ne pas confondre avec l'interprétation de l'œuvre elle-même.
Si l'on peut, à des fins de synthèse, reconstituer dans la pensée de Marx une conception de la liberté irréductible tant au libéralisme classique qu'au collectivisme autoritaire, il faut préciser que cette reconstruction ne correspond pas à un exposé systématique que Marx aurait lui-même proposé. La liberté, telle qu'on peut la dégager de ses textes, ne se réduit pas à l'absence de contrainte extérieure ni à la jouissance de droits formels — droit de propriété, liberté de contracter, égalité devant la loi — qui, dans le cadre du capitalisme, coexistent avec une servitude réelle du travailleur. Elle suppose que les individus maîtrisent collectivement les conditions de leur existence, au lieu de subir les contraintes d'un ordre social qui s'impose à eux comme une puissance étrangère. La réduction du temps de travail constitue à cet égard un enjeu central : c'est dans le « règne de la liberté », qui commence au-delà du domaine de la nécessité matérielle, que l'épanouissement des facultés humaines peut se déployer comme une fin en soi<ref>K. Marx, Das Kapital, livre III, chap. 48, MEW, t. XXV, p. 828 ; trad. fr. Éditions sociales, 1977, t. VIII, p. 198-199.</ref>. Mais ce règne de la liberté ne peut s'épanouir que sur la base d'un règne de la nécessité organisé rationnellement, c'est-à-dire d'une production collective qui minimise le temps de travail contraint tout en assurant la satisfaction des besoins.
== Portée et postérité philosophique ==
L'influence de Marx sur la pensée contemporaine dépasse de loin les frontières du mouvement politique qui se réclame de lui. La sociologie, l'histoire, l'anthropologie, la critique littéraire, la géographie humaine et la philosophie politique ont été profondément marquées par ses catégories et ses méthodes d'analyse. Des penseurs aussi différents que Max Weber, qui dialogue constamment avec le matérialisme historique même pour s'en écarter, Émile Durkheim, dont la réflexion sur la division du travail social entretient un rapport étroit avec les analyses marxiennes, ou Thorstein Veblen, qui reprend à sa manière la critique de l'économie orthodoxe, témoignent de la diffusion des questionnements ouverts par Marx bien au-delà du cercle de ses partisans.
Dans le champ philosophique, la pensée de Marx a donné lieu à des reprises et à des réélaborations variées. Le marxisme occidental, représenté notamment par Georg Lukács, Antonio Gramsci, l'École de Francfort (Horkheimer, Adorno, Marcuse), a approfondi l'analyse de l'idéologie, de la réification et de la culture, en intégrant des éléments empruntés à la phénoménologie, à la psychanalyse et à la sociologie de la connaissance. Lukács, dans ''Histoire et conscience de classe'' (1923), a développé le concept de réification — la transformation des rapports sociaux en rapports entre choses — comme clé de lecture de la société capitaliste, en reliant le fétichisme de la marchandise à la rationalisation bureaucratique analysée par Weber. Gramsci a élargi la notion de domination de classe en introduisant le concept d'hégémonie, qui désigne la capacité de la classe dominante à obtenir le consentement des classes dominées par l'intégration culturelle et idéologique, et non par la seule coercition.
L'école structuraliste, avec Louis Althusser, a proposé dans les années 1960 une relecture du ''Capital'' qui insiste sur la scientificité de l'entreprise marxienne et sur la rupture avec l'humanisme philosophique des œuvres de jeunesse. Althusser distingue entre l'idéologie et la science, et soutient que le ''Capital'' opère un changement de terrain théorique — une « coupure épistémologique » — par rapport aux textes antérieurs à 1845. Cette lecture a suscité des débats importants, notamment avec les partisans d'une interprétation humaniste qui voient dans le concept d'aliénation le fil conducteur de l'ensemble de l'œuvre. D'autres courants — l'opéraïsme italien, le marxisme analytique anglo-saxon, les approches de la reproduction sociale — ont renouvelé de diverses manières la réflexion sur les catégories marxiennes en les confrontant aux transformations du capitalisme contemporain.
La pensée de Marx demeure ainsi un objet de travail philosophique vivant, irréductible tant à l'hagiographie qu'au rejet sommaire. Ses analyses de la marchandise, du fétichisme, de l'idéologie et de l'accumulation du capital continuent de nourrir la réflexion sur les formes contemporaines du capitalisme, sur les mécanismes de la domination sociale et sur les conditions d'une émancipation possible. La question de savoir dans quelle mesure les catégories forgées par Marx pour analyser le capitalisme du XIXe siècle restent opératoires pour comprendre les transformations du XXIe siècle — financiarisation, mondialisation, numérique, écologie — reste l'un des enjeux de la discussion philosophique et politique actuelle.
== Notes et références ==
{{Reflist}}
== Indications bibliographiques ==
=== Œuvres de Marx ===
K. Marx, ''Œuvres'', éd. M. Rubel, Paris, Gallimard, « Bibliothèque de la Pléiade », 4 vol., 1963-1994. — Édition française de référence, richement annotée.
K. Marx et F. Engels, ''Marx-Engels-Werke'' (MEW), Berlin, Dietz Verlag, 44 vol., 1956-2018. — Édition allemande complète de référence.
K. Marx et F. Engels, ''Marx-Engels-Gesamtausgabe'' (MEGA²), Berlin, Akademie Verlag puis De Gruyter, 1975-. — Édition historico-critique en cours, qui renouvelle l'accès aux manuscrits et aux variantes.
K. Marx, ''Le Capital. Critique de l'économie politique'', trad. J.-P. Lefebvre, Paris, PUF, « Quadrige », livre I : 1993 ; livres II et III : Éditions sociales, 1977-1978. — Traduction française du livre I la plus utilisée dans le monde universitaire francophone.
K. Marx, ''Manuscrits économico-philosophiques de 1844'', trad. F. Fischbach, Paris, Vrin, 2007.
K. Marx, ''Grundrisse'', trad. sous la dir. de J.-P. Lefebvre, Paris, Éditions sociales, 2011.
=== Études et commentaires ===
L. Althusser, ''Pour Marx'', Paris, Maspero, 1965. — Lecture structuraliste défendant la thèse de la « coupure épistémologique » entre le jeune Marx et le Marx du ''Capital''.
L. Althusser et É. Balibar, ''Lire le Capital'', Paris, Maspero, 1965 ; rééd. PUF, « Quadrige », 1996.
É. Balibar, ''La Philosophie de Marx'', Paris, La Découverte, « Repères », 1993 ; rééd. augmentée 2014. — Introduction synthétique et rigoureuse.
J. Bidet, ''Que faire du « Capital » ?'', Paris, PUF, 2000. — Relecture critique de l'architecture du ''Capital''.
P. Burkett, ''Marx and Nature: A Red and Green Perspective'', New York, St. Martin's Press, 1999.
J. B. Foster, ''Marx's Ecology: Materialism and Nature'', New York, Monthly Review Press, 2000. — Ouvrage fondateur de la lecture écologique de Marx.
A. Gramsci, ''Cahiers de prison'', trad. sous la dir. de R. Paris, Paris, Gallimard, 5 vol., 1978-1996. — Élaboration du concept d'hégémonie.
M. Heinrich, ''Die Wissenschaft vom Wert'', Münster, Westfälisches Dampfboot, 1999 ; trad. angl. ''An Introduction to the Three Volumes of Karl Marx's Capital'', New York, Monthly Review Press, 2012. — Présentation rigoureuse de la « Neue Marx-Lektüre ».
D. Harvey, ''A Companion to Marx's Capital'', Londres, Verso, 2010. — Guide de lecture détaillé du livre I.
G. Lukács, ''Histoire et conscience de classe'' (1923), trad. K. Axelos et J. Bois, Paris, Minuit, 1960. — Développement du concept de réification.
K. Saito, ''Karl Marx's Ecosocialism: Capital, Nature, and the Unfinished Critique of Political Economy'', New York, Monthly Review Press, 2017. — Approfondissement de la lecture écologique à partir des manuscrits tardifs.
G. Stedman Jones, ''Karl Marx: Greatness and Illusion'', Londres, Allen Lane, 2016. — Biographie intellectuelle récente, attentive aux contextes historiques.
{{DEFAULTSORT:Marx}}
[[Catégorie:Philosophe]]
jspjibpj922uw2rulbmz82tse8xw059
Dictionnaire de philosophie/Julien Offray de La Mettrie
0
83746
768684
762061
2026-06-26T04:31:21Z
PandaMystique
119061
Bot : ajout du paramètre lecture=oui au modèle {{DicoPhilo}}
768684
wikitext
text/x-wiki
{{DicoPhilo|Julien Offray de La Mettrie|lecture=oui}}
Julien Offray de La Mettrie est né à Saint-Malo le 19 décembre 1709 — une tradition biographique ancienne, issue de l'éloge funèbre rédigé par Frédéric II, a longtemps diffusé la date du 25 décembre —, dans une famille de commerçants aisés. Après des études de rhétorique au collège de Coutances, puis de philosophie à Paris, il se tourna vers la médecine et soutint sa thèse de doctorat à Reims en 1733. Il se rendit ensuite à Leyde pour suivre l'enseignement du plus célèbre clinicien de l'époque, Herman Boerhaave, dont il devint le traducteur français et le disciple. De retour en France, il exerça la médecine à Saint-Malo, puis fut nommé médecin des Gardes françaises et participa à plusieurs campagnes militaires, notamment celle de Flandre durant la guerre de Succession d'Autriche. C'est durant un épisode de fièvre chaude contracté au siège de Fribourg, en 1744, qu'il fit, selon son propre récit, l'observation qui allait orienter toute sa réflexion philosophique : il constata sur lui-même que les opérations de la pensée dépendaient étroitement de l'état du corps, et que la fièvre altérait profondément le cours des idées et des sentiments. Cette expérience clinique, banale en apparence, devint le point de départ d'une méditation philosophique qui ne cessa de se déployer jusqu'à sa mort.
Cette méditation inaugura une série d'ouvrages qui, en l'espace de quelques années, valurent à La Mettrie une notoriété scandaleuse et un exil sans retour. Son ''Histoire naturelle de l'âme'' (1745), publiée sous le masque d'une traduction anglaise fictive, fut condamnée à être lacérée et brûlée par arrêt du Parlement de Paris le 7 juillet 1746. ''L'Homme machine'', publié anonymement à Leyde chez Élie Luzac fils fin 1747 sous la date de 1748, subit le même sort aux Provinces-Unies et suscita un tollé si violent que La Mettrie dut fuir les Pays-Bas. Il trouva refuge à la cour de Berlin, où Frédéric II de Prusse, amateur d'esprits libres et protecteur des philosophes persécutés, le nomma lecteur du roi et membre de l'Académie royale des sciences et belles-lettres. Il y poursuivit une activité philosophique intense jusqu'à sa mort, survenue le 11 novembre 1751, vraisemblablement à la suite d'un repas trop copieux. La légende, soigneusement entretenue par ses adversaires, en fit une mort exemplaire, celle d'un épicurien puni par son propre vice ; Frédéric II, dans l'éloge funèbre qu'il rédigea lui-même, s'employa au contraire à rendre hommage à l'homme et au penseur.
L'œuvre philosophique de La Mettrie, composée en moins d'une dizaine d'années, forme un ensemble cohérent dont les différents volets — théorie de la matière, psychophysiologie, morale hédoniste, spéculations cosmogoniques — s'articulent autour d'une intuition centrale : l'être humain est un être de nature, entièrement soumis aux lois de l'organisation corporelle, et c'est dans cette organisation, et non dans une substance spirituelle séparée, qu'il faut chercher le principe de la pensée, du sentiment et du bonheur. Par la hardiesse de ses thèses, par la vivacité polémique de son écriture et par sa position singulière à la croisée de la médecine et de la métaphysique, La Mettrie occupe une place à part dans le matérialisme des Lumières, en amont de d'Holbach et de Diderot, et en marge de l'Encyclopédie, à laquelle il n'a pas contribué. Ses ouvrages, publiés en ordre dispersé et dans des circonstances souvent précipitées, se laissent néanmoins rassembler autour de quelques thèses fondamentales qu'il importe de restituer dans leur enchaînement.
== La formation médicale et l'héritage de Boerhaave ==
L'itinéraire intellectuel de La Mettrie ne se comprend pas indépendamment de sa formation de médecin. Le séjour à Leyde auprès de Boerhaave, qui dura environ un an, fut l'événement intellectuel fondateur de sa carrière. Boerhaave représentait alors une synthèse originale de la tradition iatromécaniste, héritée de Borelli et de Baglivi, et de l'observation clinique à la manière hippocratique. Il concevait le corps humain comme une machine hydraulique dont il importait de comprendre les ressorts, les fluides et les solides, sans recourir à des principes occultes ni à des qualités cachées. La Mettrie traduisit en français plusieurs de ses traités — les ''Institutiones medicae'', les ''Aphorismes'', le ''Traité de la matière médicale'' — et ce travail de traduction fut aussi un travail d'appropriation philosophique. De Boerhaave, La Mettrie retint la conviction que la médecine, fondée sur l'observation et l'expérience, constitue la seule voie légitime pour connaître la nature humaine, et que les philosophes qui n'ont pas été médecins ont « parcouru le labyrinthe de l'homme » en aveugles, sans rien y découvrir de certain.
Cette confiance dans la médecine comme voie royale vers la connaissance de l'homme se doublait, chez La Mettrie, d'une pratique effective et d'une observation aiguë. Ses premières publications furent des ouvrages strictement médicaux, à commencer par le ''Traité du vertige'' (1737), qui décrit les symptômes, les causes et les traitements de cette affection, ainsi que des observations cliniques tirées de son expérience militaire. Plus tard, une fois installé à Berlin, il composa l'''Ouvrage de Pénélope, ou Machiavel en médecine'' (1748-1750), satire virulente des charlatans, des facultés de médecine et des pratiques médicales de son temps, qui acheva de lui aliéner ses anciens confrères. Mais il est significatif que, même dans ses écrits les plus spéculatifs, La Mettrie ne cesse d'invoquer l'autorité de la clinique, de l'anatomie comparée et de la physiologie : ses arguments philosophiques reposent presque toujours sur des observations pathologiques, sur des cas de maladie ou de folie, sur les effets des substances (opium, café, vin, viande crue) ou des états corporels (fièvre, grossesse, sommeil, jeûne, continence) sur la pensée et le caractère. Le médecin n'abandonne jamais le philosophe ; il lui fournit au contraire son arsenal de preuves.
Il faut cependant noter que l'héritage de Boerhaave, tel que La Mettrie se le réappropria, dépasse largement les intentions du maître. Boerhaave lui-même n'était nullement matérialiste ; il admettait l'existence d'une âme immatérielle et n'étendait le mécanisme qu'au domaine du corps. C'est La Mettrie qui franchit le pas en appliquant au domaine de l'esprit les principes que Boerhaave réservait au corps, et en tirant de la méthode expérimentale des conclusions métaphysiques que le prudent clinicien hollandais se serait bien gardé de formuler. Haller, l'autre grand élève de Boerhaave, avec lequel La Mettrie entretint des relations d'abord amicales puis orageuses, ne pardonna jamais à son ancien condisciple d'avoir compromis par ses audaces philosophiques la respectabilité de l'école de Leyde.
== La théorie de la matière et de l'âme sensitive ==
Le premier ouvrage où La Mettrie expose de manière systématique sa philosophie de la nature est l'''Histoire naturelle de l'âme'' (1745) — même s'il avait déjà abordé des questions non strictement médicales dans des écrits antérieurs, tels que les ''Essais sur l'esprit et les beaux-esprits''. La Mettrie remania cet ouvrage sous le titre de ''Traité de l'âme'' pour l'édition des ''Œuvres philosophiques'' de 1751, où il figure comme « second mémoire ». C'est le texte le plus systématique de La Mettrie, celui où il pose les fondements ontologiques de son matérialisme avec le plus de rigueur scolaire. L'ouvrage comprend quinze chapitres organisés en une progression méthodique, de la matière en général jusqu'aux facultés intellectuelles de l'homme, en passant par les propriétés mécaniques passives, la puissance motrice, la faculté sensitive et les différents degrés de l'âme. Il s'ouvre par une déclaration d'humilité épistémologique qui est aussi un programme : l'essence de l'âme, comme celle de la matière, est et restera inconnue. « L'âme et le corps ont été faits ensemble dans le même instant, et comme d'un seul coup de pinceau. » Celui qui veut connaître les propriétés de l'âme doit donc rechercher celles qui se manifestent dans les corps, dont l'âme est le « principe actif ». Le seul guide en cette recherche, ce sont les sens : « Voilà mes philosophes. »
La Mettrie distingue, dans la matière considérée en elle-même et indépendamment de toute forme particulière, deux types de propriétés. Les premières sont passives et dérivent de l'étendue : grandeur, figure, repos, situation. Ce sont les propriétés mécaniques que les cartésiens reconnaissaient à la matière. Les secondes sont actives : la puissance motrice et la faculté de sentir. C'est sur ce point que la rupture avec le cartésianisme est la plus nette. Pour Descartes et ses disciples, la matière se réduisait à l'étendue et n'avait par elle-même aucune force ; le mouvement devait lui être communiqué de l'extérieur par Dieu, et la pensée appartenait à une substance radicalement distincte, la ''res cogitans''. La Mettrie, en s'appuyant sur la tradition aristotélicienne et sur certains modernes (il cite Goudin, théologien thomiste du dix-septième siècle), soutient au contraire que la matière possède en elle-même une « puissance motrice » et une « faculté de sentir » qui sont des propriétés essentielles, aussi anciennes que l'étendue elle-même. La matière n'est pas le simple réceptacle inerte que les cartésiens décrivaient ; elle contient en puissance toutes les formes que le mouvement actualise, y compris les formes les plus complexes de la vie et de la sensibilité. En reprenant les termes d'Aristote, La Mettrie déclare que « si la matière est la mère des formes, elle ne l'est que par son mariage avec la force motrice même ».
De cette ontologie de la matière découle une théorie de l'âme conçue non pas comme une substance distincte, mais comme une forme, c'est-à-dire une modification, de la matière organisée. L'âme est le nom que l'on donne au « principe moteur » qui fait battre le cœur, sentir les nerfs et penser le cerveau. La Mettrie ne prétend pas avoir percé le mystère de ce principe ; il se borne à montrer que l'on peut en étudier les propriétés sans recourir à la notion d'une substance immatérielle. La démonstration procède par degrés : La Mettrie examine tour à tour l'âme « végétative » (les fonctions de nutrition, de croissance et de reproduction, communes à tous les vivants), l'âme « sensitive » (les sensations, la mémoire, l'imagination, les passions, communes aux animaux et à l'homme) et l'âme « raisonnable » (les perceptions, la réflexion, le jugement, le raisonnement, propres à l'homme). À chaque étape, il s'efforce de montrer que les facultés dites « supérieures » ne sont que des développements et des complications des facultés « inférieures », et que toutes dépendent en dernière instance de l'organisation corporelle — de la finesse des fibres nerveuses, de la consistance du cerveau, de la circulation des fluides.
Le chapitre sur la faculté sensitive de la matière est le cœur du ''Traité de l'âme''. La Mettrie y entreprend de montrer que la sensibilité n'est pas un privilège de l'âme immatérielle, mais une propriété inhérente à la matière organisée d'une certaine façon. Il analyse le mécanisme des sensations, les lois qui les régissent — notamment le principe selon lequel les sensations ne font pas connaître la nature des corps tels qu'ils sont en eux-mêmes, mais seulement les modifications que les objets extérieurs produisent dans nos organes —, la diversité anatomique des organes sensoriels et ses conséquences sur la diversité des perceptions, et conclut que « l'être sensitif est par conséquent matériel ». La Mettrie anticipe ici, dans un langage encore tributaire de la scolastique aristotélicienne, des thèses que Condillac développera de manière plus rigoureuse dans le ''Traité des sensations'' (1754). Mais à la différence de Condillac, La Mettrie ne se contente pas de dériver les opérations de l'esprit de la sensation transformée ; il les ancre systématiquement dans la physiologie du cerveau et du système nerveux, et il n'hésite pas à en tirer des conséquences matérialistes que l'abbé Condillac se gardera bien de formuler.
Le ''Traité'' se clôt par un chapitre consacré aux « histoires » qui confirment que toutes nos idées viennent des sens : le sourd de Chartres, découvrant le monde sonore après avoir recouvré l'ouïe ; l'aveugle-né opéré par le chirurgien Cheselden, qui dut apprendre à voir comme un enfant apprend à parler ; un enfant trouvé parmi les ours, réduit à un état quasi animal faute d'éducation ; la méthode d'Amman pour apprendre aux sourds à parler. Ces cas cliniques et anthropologiques, que La Mettrie emprunte en partie à la littérature médicale et philosophique de son temps, servent de preuves empiriques à la thèse centrale : privé de ses organes sensoriels ou de l'éducation qui les développe, l'homme ne possède aucune idée. Il n'y a pas d'idées innées ; tout vient de l'expérience et de l'organisation, et la « foi seule peut fixer notre croyance sur la nature de l'âme raisonnable ».
== L'Homme machine ==
''L'Homme machine'' est l'ouvrage le plus célèbre et le plus controversé de La Mettrie. Publié anonymement à Leyde chez Élie Luzac fils et mis en circulation fin 1747 sous la date de 1748, dédié dans une longue épître liminaire à Albrecht von Haller — qui s'empressa de récuser cette dédicace embarrassante et de dénoncer publiquement l'ouvrage —, le texte est un essai relativement bref, d'une centaine de pages, rédigé dans un style vif, déclamatoire, volontiers provocateur, très éloigné de l'exposition méthodique du ''Traité de l'âme''. La dédicace elle-même est un morceau de bravoure : La Mettrie y célèbre les plaisirs de l'étude, la volupté intellectuelle du savant, la supériorité des jouissances de l'esprit sur celles des sens — autant de thèmes qui annoncent sa philosophie morale ultérieure.
La thèse centrale du livre est annoncée dès les premières pages : il n'existe que deux systèmes possibles sur l'âme de l'homme, le matérialisme et le spiritualisme. Le premier se recommande par sa cohérence et par sa conformité à l'expérience ; le second n'est qu'un édifice de mots. La Mettrie récuse d'emblée le cartésianisme et son dualisme des substances, les monades de Leibniz (« une hypothèse inintelligible » qui a « plutôt spiritualisé la matière que matérialisé l'âme »), et la prudence de Locke, qui s'est demandé si la matière pourrait penser sans oser franchir le pas. La Mettrie se propose de montrer, non pas que l'âme est matérielle — car l'essence de la matière nous est inconnue —, mais que toutes les opérations attribuées à l'âme s'expliquent par la seule organisation du corps. L'expérience et l'observation, et non la spéculation métaphysique, doivent servir de guides : « Prenons donc le bâton de l'expérience, et laissons là l'histoire de toutes les vaines opinions des philosophes. »
L'argumentation repose sur l'accumulation méthodique d'observations montrant la dépendance de la pensée et du caractère à l'égard des états corporels. La Mettrie passe en revue, avec une abondance d'exemples tirés de la médecine et de l'histoire, les effets du tempérament, des humeurs, des maladies, du sommeil, de l'alimentation, du climat, de l'âge et du sexe sur les facultés mentales. La mélancolie, la bile, le phlegme « font de chaque homme un homme différent ». Dans les maladies, « tantôt l'âme s'éclipse et ne montre aucun signe d'elle-même ; tantôt on diroit qu'elle est double, tant la fureur la transporte ; tantôt l'imbécillité se dissipe, et la convalescence d'un sot fait un homme d'esprit ». Les paralytiques croient sentir des membres qu'ils n'ont plus ; les fous s'imaginent transformés en loups-garous ou en vampires ; les hystériques et les hypochondriaques témoignent de l'empire du viscère sur le cerveau. L'opium, le café, le vin agissent directement sur l'état de l'âme. La grossesse altère les jugements moraux au point de faire commettre les actes les plus contraires à la nature. Le bailli suisse Steiguer de Wittighofen était le plus intègre des juges à jeun, mais après un grand dîner, il était homme à faire pendre l'innocent comme le coupable. La viande crue rend les animaux féroces, et les hommes le deviendraient par la même nourriture. La conclusion s'impose : « les divers états de l'âme sont toujours corrélatifs à ceux du corps ».
La Mettrie fait ensuite appel à l'anatomie comparée pour montrer la continuité entre l'homme et l'animal. La structure du cerveau est fondamentalement la même chez l'homme et chez les quadrupèdes, les oiseaux, et même les poissons ; la différence n'est que de degré : l'homme a proportionnellement plus de cerveau, et un cerveau plus plissé et plus tortueux, que les autres espèces. L'intelligence, la docilité, la sagacité des animaux croissent avec le volume et la complexité de cet organe. Le corps calleux, où Lancisi avait placé le siège de l'âme — opinion que La Peyronie illustra ensuite par de nombreuses expériences —, est plus développé chez l'homme que chez le singe, chez le singe que chez le chien, et ainsi de suite. Il s'ensuit qu'entre l'homme et l'animal, la différence n'est pas de nature mais de degré d'organisation. La Mettrie envisage même, dans une spéculation hardie, la possibilité d'apprendre à parler à un grand singe en le soumettant à la méthode d'Amman pour les sourds-muets — spéculation qui témoigne de la radicalité de sa pensée sur la continuité entre l'homme et l'animal.
La formule qui donne son titre à l'ouvrage n'apparaît qu'après cette longue préparation empirique : « L'homme est une machine si composée, qu'il est impossible de s'en faire d'abord une idée claire, et conséquemment de la définir. » Mais le terme de « machine » ne doit pas être entendu au sens strictement cartésien d'un automate à rouages. La Mettrie insiste sur le fait que « le corps humain est une machine qui monte elle-même ses ressorts », une « vivante image du mouvement perpétuel ». La machine humaine est auto-organisée, autorégulée ; elle n'a pas besoin d'un principe extérieur pour se mettre en mouvement. Les aliments entretiennent ce que la fièvre excite ; le sang circule par lui-même ; les organes exercent spontanément leurs fonctions. C'est en cela que le matérialisme de La Mettrie se distingue du mécanisme cartésien : la matière organisée possède en elle-même le principe de son activité. L'âme n'est pas un pilote dans un navire, mais le mouvement même du navire ; elle « s'endort avec le corps » et « suit les progrès du corps, comme ceux de l'éducation ».
L'ouvrage se termine par des considérations sur les rapports entre la science naturelle et la religion. La Mettrie adopte une stratégie argumentative subtile : il soutient que si la révélation est authentique, elle ne saurait contredire la nature, puisque Dieu est l'auteur de l'une et de l'autre. « L'expérience seule peut rendre raison de la foi. » Mais ce concordisme de façade ne trompe personne : la conclusion réelle du livre est que l'hypothèse d'une âme immatérielle est inutile, et que l'homme peut être pleinement compris comme un être de nature, sans recours à la théologie. La Mettrie va même plus loin : l'athéisme n'est pas incompatible avec la moralité. Le matérialiste n'a nul besoin de craindre un enfer pour bien agir ; c'est même lui, selon La Mettrie, qui a le moins de raisons de nuire, puisqu'il ne compte sur aucune rémission future de ses fautes et qu'il sait que le bonheur ici-bas est le seul bien auquel il puisse prétendre.
== Matérialisme, athéisme et religion ==
La question des rapports entre le matérialisme de La Mettrie et la religion mérite un examen attentif, car elle éclaire la singularité de sa position parmi les philosophes du dix-huitième siècle. À la différence des déistes comme Voltaire, qui combattaient l'Église tout en maintenant l'existence d'un Dieu créateur et législateur de la nature, La Mettrie est l'un des premiers philosophes des Lumières françaises à soutenir, quoique avec des précautions rhétoriques, que l'hypothèse de Dieu est inutile à l'explication de la nature et à la fondation de la morale. L'univers se suffit à lui-même ; la matière, dotée de puissance motrice et de sensibilité, n'a besoin d'aucun agent extérieur pour se mettre en mouvement et pour produire les formes les plus complexes de la vie et de la pensée.
La Mettrie ne développe pas cependant une argumentation anti-théologique systématique, à la manière dont d'Holbach le fera dans le ''Système de la nature'' (1770). Son athéisme, s'il en est un, procède moins d'une démonstration en règle de l'inexistence de Dieu que d'un constat d'inutilité : la notion de Dieu n'ajoute rien à notre compréhension de la nature, et elle encombre la recherche philosophique de préjugés dont il convient de se débarrasser. Dans ''L'Homme machine'', La Mettrie adopte une stratégie oblique : il feint de maintenir la compatibilité entre la science de la nature et la révélation, tout en montrant, par le détail de son argumentation, que cette compatibilité est purement formelle. Si la nature suffit à tout expliquer, la révélation n'a plus d'objet. La conclusion implicite est que l'athéisme est la position la plus cohérente, même si La Mettrie évite le plus souvent de la formuler en ces termes.
Sur la question de la morale sans Dieu, La Mettrie avance une thèse qui le distingue de presque tous ses contemporains. Là où les autres philosophes matérialistes s'efforcent de montrer que la morale peut être fondée sur la raison naturelle, sur l'intérêt bien entendu ou sur le sentiment de sympathie — autant de substituts séculiers de la morale religieuse —, La Mettrie conteste la nécessité même d'un fondement moral universel. Le bonheur étant une affaire d'organisation, et l'organisation variant d'un individu à l'autre, il n'y a pas de recette universelle du bien-vivre. Les « vertus sociales », c'est-à-dire le respect d'autrui et la bienveillance, suffisent au fonctionnement de la société ; elles n'ont pas besoin d'être garanties par un Dieu rémunérateur, ni même par un système philosophique. Cette position, dans laquelle certains commentateurs ont pu voir une anticipation de formes modernes de relativisme moral, fut jugée nihiliste et dangereuse par les philosophes du temps, qui voyaient dans le matérialisme athée de La Mettrie un péril pour l'ordre social qu'ils cherchaient à réformer, non à détruire.
== La continuité des êtres : L'Homme plante et Les Animaux plus que machines ==
Dans les ouvrages qui suivent ''L'Homme machine'', La Mettrie prolonge et diversifie sa réflexion sur la continuité entre les différents règnes de la nature. ''L'Homme plante'' (1748) est un bref essai, divisé en trois chapitres, dans lequel La Mettrie établit un parallèle systématique entre le règne végétal et le règne animal. Les racines correspondent aux intestins, la tige au corps, les feuilles aux poumons, les fleurs aux organes de la reproduction. L'analogie n'est pas seulement morphologique ; elle est fonctionnelle : la nutrition, la croissance, la reproduction obéissent dans les deux règnes aux mêmes principes généraux. L'homme est une plante qui marche, comme la plante est un homme enraciné. L'intention de La Mettrie n'est pas de rabaisser l'homme au rang du végétal, mais de montrer que la nature ne fait pas de sauts, que les transitions entre les formes du vivant sont graduelles, et que l'homme ne saurait être isolé de la grande chaîne des êtres. Cette idée de continuité, héritée de la tradition de l'''échelle des êtres'' (''scala naturae'') chère à Leibniz et à Bonnet, reçoit chez La Mettrie une coloration matérialiste : la continuité n'est plus l'expression d'un ordre providentiel, mais le résultat du jeu uniforme de la matière organisée selon des degrés de complexité croissants.
''Les Animaux plus que machines'' (1750) est un texte plus complexe et plus ironique, dont le titre est à entendre comme un retournement polémique de la thèse cartésienne de l'animal-machine. L'argument est le suivant : si les animaux ne sont que des machines dépourvues d'âme, comme le soutenait Descartes, alors l'homme n'est lui aussi qu'une machine, comme La Mettrie l'a montré dans ''L'Homme machine''. Mais si l'on refuse cette conséquence pour l'homme et qu'on lui reconnaît une âme, il faut par analogie la reconnaître aussi aux animaux, qui possèdent les mêmes organes, les mêmes sens, les mêmes facultés de perception, de mémoire et de jugement. Sous couvert de défendre le spiritualisme et l'existence d'une âme immatérielle chez les animaux — thèse qu'il attribue au médecin Tralles de Breslau, qu'il feint d'approuver pour mieux la réfuter par l'absurde —, La Mettrie démontre en réalité que tous les arguments en faveur de l'âme immatérielle conduisent à des difficultés insolubles. Si l'âme voit sans le cerveau, entend sans l'oreille, sent sans les nerfs, comment expliquer que les maladies du cerveau abolissent la pensée ? Le texte est un exercice de virtuosité ironique qui a dérouté plus d'un lecteur.
L'argument principal porte sur la continuité entre l'homme et l'animal. L'anatomie comparée révèle « les mêmes parties, les mêmes fonctions ; c'est partout le même jeu, le même spectacle ». Les sens internes « ne manquent pas plus aux animaux que les externes » ; ils sont donc « doués comme nous de toutes les facultés spirituelles qui en dépendent, je veux dire de la perception, de la mémoire, de l'imagination, du jugement, du raisonnement ». La parole seule manque aux animaux, et cette absence s'explique par un défaut anatomique, non par un défaut d'âme. Les animaux rêvent, se souviennent, craignent, se réjouissent, choisissent les moyens les plus propres à atteindre leurs fins : autant de signes qui prouvent que « notre vanité, en leur assignant l'instinct, pour nous décorer de cet être bizarre, inconstant et volage, nommé la raison, nous a plus distingués de nom que d'effet ».
== Le Système d'Épicure : spéculations cosmogoniques ==
Le ''Système d'Épicure'', publié dans les ''Œuvres philosophiques'' de 1751, est un texte bref composé d'aphorismes numérotés, dans lequel La Mettrie s'aventure sur le terrain de la cosmogonie et de l'origine des espèces. Issu d'un remaniement des ''Réflexions philosophiques sur l'origine des animaux'' (1750), il développe une hypothèse transformiste d'inspiration lucrétienne, selon laquelle les formes actuelles du vivant résultent d'un long processus d'essais et d'erreurs de la nature, sans dessein ni providence.
Le point de départ est un aveu d'ignorance lucide : les causes premières nous sont inaccessibles ; « les premiers ressorts nous sont cachés, et le seront vraisemblablement toujours ». Mais cette ignorance, loin de décourager la recherche, libère la spéculation. La Mettrie compare la nature à un enfant qui, avec une pipe et de l'eau savonneuse, produit des bulles colorées : « elle prend, sans y songer, les moyens les plus simples pour opérer ». Il en coûte aussi peu à la nature pour produire un prince que pour faire éclore un brin d'herbe ; « un peu de boue, une goutte de morve, forme l'homme et l'insecte ». L'air contient des « graines ou semences, tant animales que végétales », qui se développent lorsqu'elles rencontrent des conditions favorables. La terre elle-même a pu servir de « matrice » aux premières générations d'êtres vivants, y compris les êtres humains, tout comme elle sert encore aujourd'hui de matrice aux végétaux.
Mais la nature, à ses débuts, n'a pas produit d'emblée des êtres parfaits. « Les premières générations ont dû être fort imparfaites. Ici l'œsophage aura manqué ; là l'estomac, la vulve, les intestins, etc. » Seuls les individus pourvus de tous les organes nécessaires à la survie et à la reproduction ont pu se perpétuer ; les autres ont péri sans descendance. « La perfection n'a pas plus été l'ouvrage d'un jour pour la nature que pour l'art. » La Mettrie illustre cette idée par un cas clinique contemporain qu'il a observé : celui d'une femme née sans aucun organe génital ni sexuel, « animal indéfinissable, tout à fait châtré dans le sein maternel », dépourvue de vulve, de clitoris, de vagin et de matrice. Si de telles erreurs de la nature se produisent encore aujourd'hui, combien ont-elles dû être plus fréquentes dans les temps reculés où les générations étaient « incertaines, difficiles, mal établies, et plutôt des essais que des coups de maître » ?
Cette esquisse d'une sélection naturelle avant la lettre — La Mettrie reprend ici directement l'argument d'Empédocle tel que le rapporte Lucrèce au livre V du ''De rerum natura'' — a souvent été relevée par les historiens des sciences. Il convient toutefois de ne pas en exagérer la portée : La Mettrie ne dispose ni du concept de variation héréditaire, ni de celui de durée géologique, ni de celui de mécanisme sélectif au sens darwinien. Sa cosmogonie reste dans le cadre d'une physique spéculative, plus proche de la mythologie naturelle des anciens que de la biologie moderne. Mais l'idée que les formes actuelles du vivant résultent d'un processus aveugle, sans plan ni intention, et que la nature procède par combinaisons fortuites dont seules les plus viables se conservent, fait du ''Système d'Épicure'' un jalon remarquable dans l'histoire de la pensée transformiste.
== La morale du bonheur : La Volupté, l'Anti-Sénèque et L'Art de jouir ==
La pensée morale de La Mettrie représente peut-être l'aspect le plus original et le plus incompris de son œuvre. Elle se déploie principalement dans trois textes : ''La Volupté'' — publiée sous des titres voisins et dans des versions successives à partir de 1745-1746, d'abord comme ''De la Volupté'', puis sous le titre ''L'École de la Volupté'' —, l'''Anti-Sénèque ou Discours sur le bonheur'' (1748-1751) et ''L'Art de jouir'' (1751). Ces ouvrages, qui furent ceux qui suscitèrent le plus d'indignation — même parmi les philosophes des Lumières —, développent un hédonisme naturaliste dont la cohérence avec le matérialisme de ''L'Homme machine'' est rigoureuse : si l'homme est tout entier un être de nature, si la pensée et le sentiment dépendent de l'organisation corporelle, alors le bonheur ne saurait résider dans l'exercice de facultés prétendument indépendantes du corps, mais dans le bon usage de la sensibilité naturelle.
''La Volupté'', dont la première version parut dès 1745 sous le titre ''De la Volupté'' avant d'être reprise et augmentée sous le titre ''L'École de la Volupté'', fut publiée sous le pseudonyme du « chevalier de M***, capitaine au régiment Dauphin ». C'est une célébration littéraire du plaisir, écrite dans le style galant de l'époque, qui emprunte la forme d'un discours adressé à une amante et parsemé d'hommages aux écrivains voluptueux — Voltaire, Crébillon, Chaulieu, Gresset, Bernis, Piron, Pétrone, Lucrèce. La Mettrie y distingue deux catégories d'auteurs : ceux qui sont « obscènes et dissolus » et ceux qui sont « des maîtres de volupté plus épurée ». Il se range parmi les seconds. Le texte, qui peut paraître n'être qu'un exercice de littérature galante, a en réalité une fonction programmatique : il annonce que la volupté, loin d'être un objet indigne de la philosophie, en est le sujet le plus noble, puisque le bonheur est la fin naturelle de l'homme. L'originalité de La Mettrie est de lier inextricablement la réflexion sur le plaisir à la réflexion sur le corps : l'imagination voluptueuse procure des jouissances aussi réelles que celles des sens, car l'imagination n'est elle-même qu'un état du corps.
C'est dans l'''Anti-Sénèque'' que La Mettrie développe le plus systématiquement sa philosophie morale. Le titre indique l'adversaire : les stoïciens, et à travers eux tous les moralistes qui font consister le souverain bien dans le renoncement aux passions, le mépris de la douleur et du plaisir, l'indifférence aux biens extérieurs. Sénèque avait défini le bonheur comme la vie selon la vertu, accompagnée de la connaissance de la vérité, libre de toute crainte et de tout désir. « Que nous serons anti-stoïciens ! » proclame La Mettrie. « Ces philosophes sont sévères, tristes, durs ; nous serons doux, gais, complaisants. Tout âme, ils font abstraction de leur corps ; tout corps, nous ferons abstraction de notre âme. Ils se montrent inaccessibles au plaisir et à la douleur ; nous nous ferons gloire de sentir l'un et l'autre. » Le programme est clair : fonder une morale sur le corps, le sentiment et la nature, contre la morale de la vertu abstraite et du renoncement.
La thèse fondamentale de l'''Anti-Sénèque'' est que le bonheur n'est pas une conquête de la volonté ou de la raison, mais un état du corps, une « modification qui nous plaît ». Nos organes sont susceptibles d'un sentiment qui nous fait aimer la vie. « Si l'impression de ce sentiment est courte, c'est le plaisir ; plus longue, c'est la volupté ; permanente, on a le bonheur ; c'est toujours la même sensation, qui ne diffère que par sa durée et sa vivacité. » Le bonheur le plus stable est donc le « bonheur organique », celui qui dépend de la constitution corporelle, du tempérament, de l'organisation : certains hommes sont naturellement heureux, par le seul jeu de leur machine, sans avoir besoin de richesses, de gloire, de science ni même de philosophie. La Mettrie va jusqu'à soutenir que les « heureux imbéciles » sont souvent plus heureux que les gens d'esprit, car « il semble que l'esprit donne la torture au sentiment ». Les animaux eux-mêmes, lorsqu'ils sont en bonne santé et que leurs appétits sont satisfaits, goûtent le sentiment agréable attaché à cette satisfaction et sont heureux à leur manière. Sénèque le niait en vain, sous prétexte que les animaux n'ont pas la connaissance intellectuelle du bonheur — « comme si les idées métaphysiques influaient sur le bien-être ».
La Mettrie tire de ces prémisses des conséquences morales qui choquèrent profondément ses contemporains. Si le bonheur dépend de l'organisation, il ne saurait être réservé aux « gens de bien » ; il y en aura, dit-il, « pour les méchants comme pour les bons ». Le remords lui-même n'est qu'une habitude de l'éducation, un pli imprimé à l'organisme par les préjugés de l'enfance, dont le philosophe peut et doit se libérer au flambeau de la raison. « On peut être heureux, j'en conviens, en ne faisant point ce qui donne des remords ; mais par là on s'abstient souvent de ce qui fait plaisir, de ce que demande la nature. » Le vrai bonheur consiste à ne se priver de rien qui ne fasse tort à personne, et à ne pas se repentir du plaisir qu'on a eu. L'illusion elle-même — qu'elle soit produite par des médicaments, par des rêves ou par la folie — est une source légitime de bonheur, puisqu'elle procure des sensations réellement agréables : « s'il m'est permis de le dire », écrit La Mettrie, le rêve et la folie offrent parfois un bonheur plus constant que la veille et la raison.
Ces thèses valurent à La Mettrie la réprobation unanime de ses contemporains, y compris des philosophes les plus matérialistes. Diderot dénonça la dissolution de ses mœurs et le désordre de ses idées ; Voltaire se distancia publiquement de lui ; d'Holbach ne le mentionna pas dans le ''Système de la nature''. Le reproche principal portait sur le fait que La Mettrie, en dissociant le bonheur de la vertu et en soutenant que les méchants peuvent être heureux, sapait les fondements de la morale sociale que les autres philosophes s'efforçaient de reconstruire sur des bases séculières. La Mettrie répondait que la vertu n'a pas besoin d'être fondée sur des illusions métaphysiques ou des menaces théologiques pour être pratiquée : les « vertus sociales », celles qui sont nécessaires à la vie en commun, suffisent au maintien de la société, et elles se fondent non sur des raisonnements, mais sur le sentiment naturel de bienveillance que la nature a placé dans la plupart des hommes.
''L'Art de jouir'', dernier texte moral de La Mettrie, est un hymne en prose à la volupté physique, célébrant sans détour les plaisirs des sens et de l'amour. Si le texte a souvent été lu comme un morceau de littérature libertine sans portée philosophique, il possède en réalité une fonction théorique : il illustre la thèse selon laquelle le plaisir est la fin naturelle de l'homme et que la philosophie n'a d'autre but que de contribuer au bonheur, dont les jouissances du corps font partie intégrante. En cela, il représente le complément pratique de la théorie formulée dans l'''Anti-Sénèque''.
== La Vénus métaphysique : l'origine de l'âme ==
La ''Vénus métaphysique ou Essai sur l'origine de l'âme humaine'' (1752), publiée de manière posthume, pose un problème d'attribution qui n'a jamais été résolu de manière satisfaisante. Le texte porte la simple mention « par M.L. » ; l'édition Fayard le reproduit au sein des ''Œuvres philosophiques'', mais une partie notable de la critique considère aujourd'hui que l'attribution à La Mettrie est incertaine et doit être accueillie avec une grande réserve. Le style de l'ouvrage, plus scolaire et plus prudent que celui des écrits authentifiés de La Mettrie, la tonalité leibnizienne de certains passages, et le maintien formel de la distinction entre l'âme et le corps, éloignent ce texte du matérialisme affiché dans ''L'Homme machine''. Si l'on admet néanmoins de le mentionner dans le cadre d'une présentation du corpus lamettrien, c'est à titre d'hypothèse et avec les précautions qui s'imposent. L'ouvrage traite de la question de l'origine de l'âme humaine et passe en revue les différents systèmes philosophiques sur ce sujet : la préexistence des âmes (d'inspiration platonicienne, reprise par Leibniz dans le cadre de l'harmonie préétablie), la création particulière de chaque âme par Dieu au moment de la conception (thèse des créationnistes cartésiens et scolastiques), et le « tradux », c'est-à-dire la propagation de l'âme par les âmes des parents elles-mêmes.
La ''Vénus métaphysique'' défend, avec des précautions rhétoriques considérables, la possibilité du tradux, réinterprété de manière compatible avec la thèse de l'influx physique : les âmes des parents communiqueraient leur « force sensitive » au fœtus par le moyen de la génération, « comme un feu allumé par un autre feu, la liqueur séminale servant de véhicule ». L'acte de génération n'est pas un simple événement mécanique ; l'âme y participe avec une vivacité telle que ses forces sensitives se communiquent au nouvel être. L'auteur compare cette communication à l'aimantation : de même que l'aimant communique sa force magnétique au fer, l'âme des parents communique sa force sensitive au fœtus. L'intérêt de ce texte réside dans l'esquisse d'une continuité générative entre les êtres, où l'âme elle-même est conçue comme quelque chose qui se transmet et se propage naturellement, par les voies ordinaires de la reproduction. L'ouvrage reste cependant en retrait par rapport aux thèses les plus audacieuses de ''L'Homme machine'', puisqu'il maintient formellement la distinction entre l'âme et le corps, tout en soutenant que cette distinction n'empêche pas une communication et une propagation des forces de l'une à travers l'autre.
== Contextes philosophiques : le Discours préliminaire et l'Abrégé des systèmes ==
Le ''Discours préliminaire'' qui ouvre les ''Œuvres philosophiques'' de 1751 est un remaniement de la conclusion de l'''Histoire naturelle de l'âme''. C'est un texte programmatique dans lequel La Mettrie expose sa méthode et sa position dans le paysage philosophique de son temps. Il y réaffirme la primauté de l'expérience sur la spéculation, la nécessité de prendre la médecine et la physiologie pour guides dans l'étude de l'homme, et l'illégitimité des prétentions de la métaphysique traditionnelle à connaître l'essence des choses. Ann Thomson, qui en a donné une édition critique (1981), a montré que ce texte constituait le manifeste méthodologique de l'ensemble de l'entreprise lamettrienne.
L'''Abrégé des systèmes pour faciliter l'intelligence du Traité de l'âme'' complète cette mise en perspective en passant en revue les principales positions philosophiques sur l'âme et la matière, d'Aristote à Leibniz en passant par Descartes, Malebranche, Locke et Spinoza. La Mettrie y montre que l'histoire de la philosophie est l'histoire d'un long débat entre ceux qui séparent l'âme du corps et ceux qui refusent cette séparation. Il se range du côté des seconds, tout en marquant ses distances avec chacun d'eux : il reproche à Aristote son obscurité, à Descartes son dualisme, à Leibniz ses « petits romans métaphysiques » (les monades et l'harmonie préétablie), à Spinoza un panthéisme qui n'est à ses yeux qu'un « athéisme masqué ». La seule philosophie qui trouve grâce à ses yeux est celle qui s'en tient aux faits observables, qui consulte l'expérience plutôt que les systèmes, et qui renonce à percer le mystère des substances pour se concentrer sur leurs propriétés manifestes.
== Le style philosophique de La Mettrie ==
L'écriture de La Mettrie est l'un des traits les plus caractéristiques de sa pensée et l'une des raisons pour lesquelles elle a été longtemps sous-estimée. À l'exception du ''Traité de l'âme'', qui adopte une forme relativement scolaire avec ses divisions en chapitres et paragraphes numérotés, la plupart de ses ouvrages sont rédigés dans un style libre, digressif, volontiers provocateur, mêlant l'argumentation philosophique, l'observation clinique, l'anecdote, la satire et la confidence personnelle. La Mettrie écrit comme un médecin qui pense et comme un homme de lettres qui s'amuse ; il n'hésite pas à parsemer ses textes de plaisanteries, de comparaisons triviales, de références littéraires (Pope, Voltaire, Montaigne, Pétrone, Virgile, Cicéron) et d'interpellations directes au lecteur. Ce style, qui a souvent déplu aux historiens de la philosophie soucieux de rigueur systématique, est inséparable de la démarche de La Mettrie : il exprime le refus de la solennité métaphysique, la volonté de traiter des questions les plus graves sur le ton de la conversation entre gens d'esprit, et la conviction que la philosophie ne doit pas être une mortification de l'entendement mais un exercice plaisant de la pensée.
Ce style explique aussi en partie les malentendus dont l'œuvre a fait l'objet. Ses contemporains ne surent pas toujours distinguer, dans ses textes, ce qui relevait de la thèse soutenue, ce qui appartenait à l'ironie, ce qui était provocation délibérée et ce qui n'était que jeu d'esprit. Le cas des ''Animaux plus que machines'' est à cet égard exemplaire : le texte, qui feint de défendre le spiritualisme contre le matérialisme, fut pris au premier degré par certains lecteurs, alors que son intention ironique est transparente pour qui le lit avec attention. L'ambiguïté constitutive de l'écriture lamettrienne, ce mélange de sérieux et de bouffonnerie, de rigueur argumentative et de désinvolture rhétorique, en fait à la fois la richesse et la difficulté.
Il faut ajouter que le rapport de La Mettrie à l'écriture est indissociable de son rapport à la médecine. Ses textes philosophiques procèdent souvent comme un examen clinique : ils accumulent les « cas », les observations de fait, les symptômes, avant de poser un diagnostic. Le philosophe raisonne comme le médecin au chevet du malade : il part de ce qu'il voit, de ce qu'il touche, de ce que le corps lui montre, et il en tire prudemment des conclusions qui restent toujours révisables. La Mettrie a d'ailleurs explicitement théorisé cette parenté entre la démarche médicale et la démarche philosophique : ce sont les médecins, dit-il, et non les théologiens ni les métaphysiciens, qui sont les « seuls physiciens » ayant « droit de parler » de l'âme. Leurs observations valent plus que tous les systèmes, parce qu'elles portent sur des réalités tangibles et non sur des abstractions verbales. Le style médical de La Mettrie — concret, descriptif, attentif aux détails corporels, parfois cru — est donc l'expression formelle d'une conviction philosophique profonde : il n'y a de connaissance véritable que celle qui passe par le corps.
== L'unité d'une pensée ==
Les différents volets de l'œuvre de La Mettrie — ontologie de la matière, psychophysiologie, morale hédoniste, cosmogonie épicurienne — ne constituent pas des compartiments séparés, mais les développements logiques d'une intuition unique. Si la matière possède en elle-même la puissance motrice et la faculté de sentir, alors l'homme n'est qu'une organisation particulièrement complexe de cette matière sensible ; si l'homme n'est qu'une machine auto-organisée, alors ses opérations mentales — idées, volitions, passions — ne sont que des produits de cette organisation ; si les opérations mentales sont des produits de l'organisation, alors le bonheur, qui est une certaine qualité du sentiment, dépend lui aussi de l'organisation ; et si l'organisation actuelle des espèces résulte d'un long processus d'essais et d'erreurs de la nature, alors il n'y a pas de dessein providentiel, et l'homme est livré à lui-même pour trouver son bien dans les limites de sa constitution naturelle. Chaque ouvrage de La Mettrie s'emboîte dans les autres selon cette logique, et c'est ce qui donne à l'ensemble, malgré les disparités de ton et de méthode, une cohérence profonde.
On peut mesurer l'originalité de cette pensée en la comparant à celle des deux grands matérialistes qui lui succéderont : d'Holbach et Diderot. D'Holbach, dans le ''Système de la nature'' (1770), développera un matérialisme plus systématique, plus argumenté, mais aussi plus dogmatique et plus abstrait, qui perd le contact avec l'observation médicale et clinique qui faisait la force de La Mettrie. Diderot, dans le ''Rêve de d'Alembert'' (1769), reprendra l'idée d'une matière dotée de sensibilité et la poussera dans des directions neuves (la sensibilité universelle de la matière, le passage du minéral au vivant, la formation des monstres), mais sans développer la dimension morale avec l'audace de La Mettrie. Aucun des deux ne reprendra la thèse lamettrienne selon laquelle le bonheur peut se passer de la vertu et que les « méchants » peuvent être heureux — thèse que l'un et l'autre jugeaient ruineuse pour la cause de la philosophie. La Mettrie occupe ainsi une position singulière : l'un des tout premiers, dans l'ordre chronologique, à formuler un matérialisme complet au siècle des Lumières, il occupe aussi, parmi eux, une position particulièrement tranchée sur le terrain de la morale.
== Réception et postérité ==
La réception de La Mettrie fut d'emblée marquée par le scandale et le rejet. De son vivant, il fut rejeté par toutes les institutions auxquelles il avait appartenu : la faculté de médecine, l'armée, les Provinces-Unies, où ses livres furent brûlés. Les philosophes des Lumières, qui partageaient pourtant certaines de ses prémisses, prirent soin de se démarquer de lui, jugeant son matérialisme trop cru, son hédonisme trop radical, et craignant que ses provocations ne compromettent la cause de la philosophie et de la tolérance. Voltaire, qui l'avait côtoyé à la cour de Berlin, le traita de « fou » dans ses lettres privées ; Diderot refusa toute parenté intellectuelle avec lui et le malmena dans plusieurs textes ; d'Holbach ne le revendiqua guère. Seul Frédéric II lui rendit durablement hommage. Cette mise au ban eut des effets durables : pendant plus d'un siècle, La Mettrie fut traité dans les histoires de la philosophie comme un auteur mineur, un provocateur sans profondeur, un médecin égaré dans la métaphysique.
La réévaluation de son œuvre est un phénomène relativement récent. Au vingtième siècle, les travaux d'Aram Vartanian, qui donna une édition critique de ''L'Homme machine'' en 1960 accompagnée d'une monographie sur les origines intellectuelles du texte, d'Ann Thomson, qui édita le ''Discours préliminaire'' en 1981 et publia par la suite une étude contextuelle de grande ampleur (''Bodies of Thought'', 2008), de Kathleen Wellman (''La Mettrie : Medicine, Philosophy, and Enlightenment'', 1992) et de Francine Markovits, qui révisa le texte de l'édition Fayard des ''Œuvres philosophiques'' (1987), ont contribué à faire reconnaître l'originalité et la cohérence de la pensée de La Mettrie. On s'est avisé que La Mettrie, loin d'être un épigone superficiel du matérialisme, en avait proposé l'une des premières formulations systématiques dans la France du dix-huitième siècle ; que sa théorie de la matière dotée de sensibilité anticipait les développements de d'Holbach et de Diderot ; que sa morale hédoniste posait des questions qui n'ont rien perdu de leur acuité philosophique ; et que ses spéculations sur la continuité des êtres et sur l'origine des espèces par essais et erreurs faisaient de lui un précurseur, si lointain soit-il, de la pensée transformiste.
La Mettrie reste aujourd'hui un penseur qui suscite le débat. Sa pensée est traversée par une tension que ses adversaires n'ont pas manqué de relever et que ses défenseurs s'efforcent de résoudre : entre un matérialisme qui réduit l'homme à son organisation corporelle et un hédonisme qui fait du plaisir le bien suprême, il y a une difficulté que La Mettrie n'a peut-être pas entièrement surmontée. Si tout est machine, si la pensée n'est qu'un jeu de ressorts, comment fonder la valeur du plaisir et la légitimité de la recherche du bonheur ? La réponse de La Mettrie, si l'on peut la formuler en termes qui ne sont pas exactement les siens, est que cette question est mal posée : le bonheur n'a pas besoin d'être « fondé » par la raison ou par la métaphysique ; il est un fait naturel, un état de l'organisme, et la philosophie n'a d'autre tâche que de lever les obstacles — préjugés, superstitions, morales ascétiques, crainte de la mort et de l'au-delà — qui empêchent l'homme de jouir de ce que la nature lui a donné. En cela, La Mettrie s'inscrit bien dans le mouvement général des Lumières, même si les moyens qu'il propose et les conclusions qu'il en tire ont toujours suscité de vives controverses, y compris parmi les philosophes qui partageaient ses prémisses.
On a pu noter que les développements contemporains des neurosciences et de la philosophie de l'esprit présentent certaines analogies lointaines avec des thèses de La Mettrie. L'idée que les états mentaux dépendent des états du cerveau, que la conscience est liée à l'organisation neuronale, que les émotions sont modelées par la chimie corporelle — autant de propositions dont on peut trouver, chez La Mettrie, une préfiguration partielle, formulée avec les moyens conceptuels de son temps. De même, la question du bonheur comme état physiologique, explorée par certains courants de la psychologie contemporaine, n'est pas sans parenté avec les intuitions de l'''Anti-Sénèque'' sur le « bonheur organique ». Ces rapprochements doivent cependant être maniés avec précaution : La Mettrie ignorait la neurophysiologie moderne, et son vocabulaire (les « esprits animaux », les « fibres », les « humeurs ») appartient à une science révolue. Il n'y a pas de continuité directe entre l'homme-machine et les neurosciences contemporaines, mais plutôt une convergence de direction : chercher dans le corps l'explication de l'esprit, et dans la nature l'explication du bonheur. C'est cette orientation générale qui confère à l'œuvre de La Mettrie un intérêt qui dépasse celui de la simple curiosité historique.
== Bibliographie ==
=== Sources primaires ===
* {{ouvrage|auteur=La Mettrie, Julien Offray de|titre=Œuvres philosophiques|volume=2 tomes|responsabilité=texte revu par Francine Markovits|lieu=Paris|éditeur=Fayard|collection=Corpus des œuvres de philosophie en langue française|année=1987}}. — Édition de référence, qui reprend le texte de l'édition de Londres [Berlin], « chez Jean Nourse », 1751, la seule édition de ses œuvres réunies que La Mettrie ait donnée de son vivant. Le tome I contient le ''Discours préliminaire'', ''L'Homme machine'', le ''Traité de l'âme'', l{{'}}''Abrégé des systèmes'', ''L'Homme plante'', ''Les Animaux plus que machines'' et le ''Système d'Épicure''. Le tome II contient le ''Traité du vertige'', ''La Volupté'', l'''Anti-Sénèque ou Discours sur le bonheur'', ''L'Art de jouir'' et la ''Vénus métaphysique''.
* {{ouvrage|auteur=Vartanian, Aram (éd.)|titre=La Mettrie's L'Homme machine. A Study in the Origins of an Idea. Critical Edition with an Introductory Monograph and Notes|lieu=Princeton|éditeur=Princeton University Press|année=1960}}. — Édition critique du texte de ''L'Homme machine'', précédée d'une monographie sur les origines intellectuelles de l'ouvrage. L'introduction de Vartanian constitue à elle seule une étude de référence.
* {{ouvrage|auteur=Thomson, Ann (éd.)|titre=Materialism and Society in the Mid-Eighteenth Century. La Mettrie's Discours Préliminaire|lieu=Genève-Paris|éditeur=Droz|année=1981}}. — Édition critique du ''Discours préliminaire'' accompagnée d'une étude contextuelle approfondie sur le matérialisme et la société au milieu du {{s-|XVIII|e}}.
=== Littérature secondaire ===
* {{ouvrage|auteur=Thomson, Ann|titre=Bodies of Thought. Science, Religion, and the Soul in the Early Enlightenment|lieu=Oxford|éditeur=Oxford University Press|année=2008}}. — Ouvrage de référence sur les débats autour de l'âme et de la matière pensante, de Locke à La Mettrie, situant la pensée de ce dernier dans le contexte intellectuel européen.
* {{ouvrage|auteur=Wellman, Kathleen|titre=La Mettrie. Medicine, Philosophy, and Enlightenment|lieu=Durham|éditeur=Duke University Press|année=1992}}. — Monographie qui met en lumière les racines médicales de la philosophie de La Mettrie et sa dette envers Boerhaave.
* {{ouvrage|auteur=Lémée, Pierre|titre=Julien Offray de La Mettrie, sa vie, son œuvre|lieu=Mortain|année=1954}}. — Biographie ancienne mais utile pour les données factuelles et bibliographiques.
* {{ouvrage|auteur=Bloch, Olivier (dir.)|titre=Le Matérialisme du XVIII{{e}} siècle et la littérature clandestine|lieu=Paris|éditeur=Vrin|année=1982}}. — Recueil d'études situant le matérialisme lamettrien dans le contexte plus large de la littérature philosophique clandestine des Lumières.
* {{ouvrage|auteur=Israel, Jonathan|titre=Radical Enlightenment. Philosophy and the Making of Modernity, 1650-1750|lieu=Oxford|éditeur=Oxford University Press|année=2001}}. — La Mettrie y figure parmi les représentants du courant dit « radical » des Lumières européennes.
* Wolfe, Charles T., « La Mettrie and the Science of the Soul », in ''Journal of the History of Ideas''. — Ainsi que divers articles du même auteur consacrés au matérialisme et à la sensibilité de la matière au {{s-|XVIII|e}}.
{{DEFAULTSORT:Lamettrie}}
gaed7phczj6rzwvbrl0noxqh62vupt3
Dictionnaire de philosophie/Moritz Schlick
0
83747
768697
764803
2026-06-26T04:33:31Z
PandaMystique
119061
Bot : ajout du paramètre lecture=oui au modèle {{DicoPhilo}}
768697
wikitext
text/x-wiki
{{DicoPhilo|Moritz Schlick|lecture=oui}}
== Formation et origines (1882–1911) ==
Friedrich Albert Moritz Schlick naît le 14 avril 1882 à Berlin dans une famille de la bourgeoisie cultivée. Après des études secondaires au Luisenstädtisches Realgymnasium, il s'inscrit à l'automne 1900 à la Königliche Friedrich-Wilhelms-Universität de Berlin pour y étudier la physique, les mathématiques, la chimie et la philosophie. L'intérêt pour les questions philosophiques s'était manifesté dès les années de lycée, à travers la lecture de Descartes, Schopenhauer, Nietzsche et Kant, tandis que l'enseignement scientifique orientait ses « tendances naturphilosophiques » naissantes vers une pensée plus disciplinée, en particulier par la découverte d'Ernst Mach. Néanmoins, Schlick ne songe pas un instant à faire de la philosophie l'objet principal de ses études universitaires : il considère que les questions philosophiques ultimes — celles qui touchent à l'homme et au sens de l'existence — doivent être travaillées de manière autonome, tandis que l'étude rigoureuse de la physique fournira les fondements sûrs dont toute réflexion sur la connaissance a besoin. C'est donc un besoin philosophique qui le conduit à la physique, et non l'inverse : il cherche dans la science exacte cette certitude « soustraite à la querelle des opinions » qu'il ne trouve pas dans les cours de philosophie auxquels il assiste sans profit.
Son maître en physique est Max Planck, dont les cours magistraux, déployés sur six semestres d'introduction aux différentes branches de la physique théorique, exercent sur lui une influence décisive. Schlick étudie également à Heidelberg et à Lausanne avant de revenir à Berlin pour y rédiger, sous la direction de Planck, une dissertation intitulée ''Über die Reflexion des Lichtes in einer inhomogenen Schicht'', soutenue le 20 mai 1904 avec la mention magna cum laude. Ce travail, qui porte sur un problème spécial d'optique — la réflexion de la lumière dans une couche dont l'indice de réfraction varie continûment —, appartient encore entièrement à la physique théorique. Planck en approuve la rigueur formelle et en recommande l'acceptation. Mais déjà, ce qui intéresse Schlick dans la physique n'est pas le détail empirique : c'est l'architecture logique des théories, les « dernières formules dans lesquelles les événements peuvent être exprimés ». Tout le reste n'est, selon ses propres termes, que « porte et vestibule ».
Après la promotion, Schlick tente brièvement de poursuivre dans la recherche expérimentale, d'abord à Göttingen auprès de Woldemar Voigt, puis à nouveau à Heidelberg. Il reconnaît cependant que ce type de travail ne correspond pas à sa nature et que ses véritables capacités se situent du côté de la réflexion théorique et philosophique. La transition vers la philosophie s'opère progressivement entre 1905 et 1908. Schlick s'installe à Zurich à la fin de l'année 1907 — il a épousé Blanche Guy Hardy aux États-Unis à l'automne — et y entreprend des études de psychologie auprès de Gustav Störring. Sa première œuvre philosophique, la ''Lebensweisheit. Versuch einer Glückseligkeitslehre'', avait été rédigée pour l'essentiel dès avant la fin de 1907, à Heidelberg, et paraît cette même année portant la date de 1908. Cette transition ne représente cependant pas une rupture : Schlick conserve la conviction que la philosophie doit être fondée sur la connaissance scientifique exacte, et que les questions philosophiques authentiques sont ultimement des questions empiriques susceptibles d'un traitement rigoureux.
La tentative de Schlick de s'habiliter à Zurich en philosophie échoue en 1909. La Faculté de philosophie, dont Störring est alors le doyen, émet un avis défavorable, et le Conseil de l'éducation du canton rejette le dossier au motif qu'il n'existe pas de besoin pour les matières demandées et que Schlick, ancien élève d'un Realgymnasium, ne peut attester de la connaissance du grec requise pour l'histoire de la philosophie grecque. La ''Lebensweisheit'', seul ouvrage d'ampleur publié, est en outre jugée trop populaire pour servir de base à une habilitation académique. Mais cette période zurichoise (1907-1910) est déterminante pour sa formation intellectuelle : c'est là, dans le séminaire de psychologie de Störring et à travers ses lectures de Mach, Helmholtz, Poincaré, qu'il élabore les premiers éléments de sa conception de la connaissance comme désignation par des signes. Ses travaux sur la nature du temps et de l'espace — conservés dans des manuscrits datant de cette période — anticipent déjà les thèses de l{{'}}''Allgemeine Erkenntnislehre'' : le temps confère au réel son caractère d'actualité, l'espace objectif est un schéma d'ordonnancement distinct de l'espace perçu. En 1911, Schlick se habilite avec succès à l'Université de Rostock, où il prononce le 29 juin sa leçon inaugurale (''Antrittsvorlesung''), intitulée « Die Aufgabe der Philosophie in der Gegenwart ». Il y définit la philosophie comme l'activité qui réalise l'unité des sciences : elle ne se tient pas à côté des sciences particulières comme une discipline coordonnée, mais les englobe en un certain sens, en tant qu'elle vise la « perfection harmonieuse de la vie intellectuelle, dans la mesure où elle est atteignable par des moyens intellectuels ». Dès le semestre d'hiver 1911-1912, il donne sa première série de cours sous le titre « Grundzüge der Erkenntnislehre und Logik ». La structure de ces cours — divisés en trois parties : « essence de la connaissance », « problèmes de la pensée » et « problèmes de la réalité » — préfigure exactement la tripartition de l{{'}}''Allgemeine Erkenntnislehre'', dont ils constituent la plus importante des ''Vorarbeiten''.
== Premiers travaux philosophiques ==
La ''Lebensweisheit'' présente une doctrine du bonheur fondée sur une explication causale du comportement humain. L'ouvrage, placé sous une épigraphe tirée du Zarathustra de Nietzsche — « en ce temps-là la vie m'était plus chère que toute ma sagesse » — et profondément marqué par la Fröhliche Wissenschaft, conçoit l'éthique non comme une discipline normative établissant des impératifs a priori, mais comme une science empirique qui étudie les conditions du bonheur humain. Schlick avance que seuls peuvent être buts de l'action les états dont la représentation est associée au plaisir — ce qui ne revient pas à faire du plaisir lui-même le but visé, car le plaisir en tant que tel ne peut être représenté mais seulement éprouvé. L'action n'atteint pas sa forme la plus accomplie lorsqu'elle se fixe des buts lointains, mais lorsqu'elle devient sa propre fin, c'est-à-dire en jeu. Cette idée, selon laquelle l'activité ludique — l'activité qui est à elle-même son propre but — représente la forme la plus haute de l'existence, forme le noyau de la Lebensweisheit. Schlick l'applique à la science elle-même, qu'il décrit comme un « jeu de l'esprit » (''Spiel des Geistes'') : la science atteint sa plénitude lorsqu'elle cesse d'être un labeur contraint et devient une activité librement exercée pour elle-même. Ce thème de la science comme activité ludique, inspiré à la fois de Schiller et de Nietzsche, trouvera un prolongement dans la théorie esthétique de Schlick et réapparaîtra dans ses dernières réflexions sur le sens de la vie.
Dans son article de 1909 sur le « problème fondamental de l'esthétique » (''Das Grundproblem der Ästhetik in entwicklungsgeschichtlicher Beleuchtung''), Schlick applique au beau le même type d'explication : l'esthétique est pour lui une discipline psychogénétique qui retrace l'émergence du sens du beau à partir du plaisir associé à la perception d'objets utiles, selon les lois de la biologie et de la psychologie. Le plaisir esthétique dérive de ce plaisir lié à l'utilité, plaisir qui s'est progressivement autonomisé au cours de l'évolution pour devenir le plaisir désintéressé que nous éprouvons devant le beau. L'esthétique, comme l'éthique, repose donc entièrement sur des bases empiriques et ne relève pas d'une « philosophie pure » distincte des sciences particulières. L'article procède directement de l'approfondissement, dans le séminaire de Störring, d'un thème majeur de la ''Lebensweisheit'' : l'idée que l'action humaine la plus sensée est celle qui est devenue jeu, c'est-à-dire ''Selbstzweck''. L'application de cette idée à l'esthétique conduit Schlick à la théorie du jeu artistique (''Spieltheorie der Kunst'') dans la tradition de Schiller, qu'il reformule en termes évolutionnistes.
Ces premiers travaux révèlent déjà une orientation directrice : les disciplines traditionnellement considérées comme philosophiques — éthique, esthétique — sont pour Schlick des cas particuliers de l'investigation scientifique de la nature humaine. Elles soulèvent les mêmes questions épistémologiques que la physique et appellent le même type de réponse. C'est cette prise de conscience qui conduit Schlick de l'éthique et de l'esthétique vers l'épistémologie : si toute connaissance valide est de nature empirique, il faut déterminer en quoi consiste exactement la connaissance, quelles sont ses conditions et ses limites. Schlick lui-même reconnaît rétrospectivement que l'intérêt pour les questions ultimes de la connaissance de la nature s'est développé parallèlement au souci de clarifier les problèmes de l'existence.
== Premiers écrits épistémologiques (1910–1913) ==
Le premier écrit proprement épistémologique de Schlick est l'article de 1910 « Das Wesen der Wahrheit nach der modernen Logik », dans lequel il pose la question de la nature de la vérité et examine les différentes réponses qui lui ont été données. Schlick y critique les théories pragmatistes de la vérité, selon lesquelles le critère de la vérité réside dans l'utilité pratique des jugements. Contre William James et les pragmatistes, il fait valoir que l'utilité est un concept trop vague et fluctuant pour servir de définition de la vérité, et que la proposition « vrai est ce qui est utile » est une moins bonne expression du rapport entre vérité et utilité que la proposition inverse « utile est ce qui est vrai ». Il critique également les conceptions néo-kantiennes de Windelband et Rickert, qui font de la vérité une valeur (''Wert'') et du jugement vrai un jugement conforme à un « devoir » (''Sollen''). Schlick leur oppose l'idée que la vérité est une relation objective entre des jugements et des faits, relation qu'il commence à caractériser en termes de « coordination univoque » (''eindeutige Zuordnung''). C'est dans cet article que se trouve en germe la conception de la vérité qui sera développée dans l{{'}}''Allgemeine Erkenntnislehre''.
En 1910 également paraît « Die Grenze der naturwissenschaftlichen und philosophischen Begriffsbildung », où Schlick examine le rapport entre la formation des concepts dans les sciences naturelles et en philosophie. Il y soutient que la philosophie ne se situe pas à côté des sciences particulières comme une discipline coordonnée, mais qu'elle les surplombe en un certain sens, qu'elle les englobe. La philosophie s'occupe des principes les plus généraux que les sciences particulières présupposent sans les thématiser. Ce rapport entre philosophie et sciences, que Schlick précisera dans l{{'}}''Allgemeine Erkenntnislehre'', est celui d'une théorie générale de la connaissance aux théories spéciales de la connaissance — philosophie de la nature, philosophie des mathématiques, philosophie de l'histoire — qui forment une couche intermédiaire entre les sciences et la réflexion épistémologique ultime.
L'article de 1913 « Gibt es intuitive Erkenntnis ? » représente un autre jalon important. Schlick y refuse l'idée, défendue par Bergson et par d'autres, qu'il existe un mode de connaissance intuitif, distinct de la connaissance conceptuelle et supérieur à elle. La connaissance intuitive prétend saisir son objet directement, sans la médiation des concepts et des signes. Schlick fait valoir au contraire que toute connaissance passe nécessairement par la conceptualisation, en d'autres termes par la désignation au moyen de signes. L'intuition, au sens de Bergson, n'est pas une forme de connaissance, mais un vécu (''Erlebnis'') qui, en tant que tel, ne livre aucune vérité communicable. Connaître et vivre sont deux choses distinctes : on peut vivre une douleur sans la connaître au sens strict, et la connaissance d'une douleur — par exemple sa classification physiologique — n'implique pas qu'on la vive. Cette distinction entre ''Erkennen'' (connaître) et ''Erleben'' (vivre) est capitale dans la pensée de Schlick et traverse toute l{{'}}''Allgemeine Erkenntnislehre''.
Dès 1912, Schlick rédige en outre une ébauche de naturphilosophie dans laquelle il pose les bases de sa conception de la physique. L'objectif ultime de la physique, écrit-il, est de formuler des lois qui expriment la dépendance de l'état régnant en un point de l'univers par rapport aux états des points immédiatement voisins — soit des lois sous forme d'équations différentielles, sans recours à l'action à distance. Cette exigence de détermination locale et continue du réel par des grandeurs quantitatives se retrouvera dans les analyses de la connaissance physique au sein de l{{'}}''Allgemeine Erkenntnislehre'' et dans ''Raum und Zeit in der gegenwärtigen Physik''. C'est aussi dans ces notes de 1912 que Schlick formule pour la première fois l'exigence de la validité universelle du principe de relativité pour l'ensemble du savoir physique, exigence que la rencontre avec la théorie d'Einstein viendra confirmer et préciser.
== L{{'}}''Allgemeine Erkenntnislehre'' ==
C'est à Rostock, où il s'est habilité en 1911 et enseigne désormais comme ''Privatdozent'', que Schlick entreprend la rédaction de son œuvre maîtresse, l{{'}}''Allgemeine Erkenntnislehre'', dont la première partie est achevée vers la fin de 1913 ou le début de 1914 et l'essentiel du texte rédigé entre le milieu de 1913 et l'automne 1915. L'ouvrage paraît en 1918 chez Julius Springer et connaît une seconde édition, révisée et augmentée, en 1925. Schlick le présente comme une théorie générale de la connaissance, soit une investigation portant sur les principes suprêmes et ultimes de toute connaissance, par distinction avec les théories spéciales — philosophie de la nature, des mathématiques, de l'histoire — qui s'arrêtent à un niveau de généralité moindre. L'exposition se veut accessible au lecteur non spécialiste de philosophie, les quelques passages de discussion technique pouvant être sautés sans dommage. L'ouvrage se divise en trois parties : « L'essence de la connaissance » (''Das Wesen der Erkenntnis''), « Problèmes de la pensée » (''Denkprobleme'') et « Problèmes de la réalité » (''Wirklichkeitsprobleme'').
=== L'essence de la connaissance ===
La première partie s'ouvre par une réflexion sur le sens et la tâche de la théorie de la connaissance. Schlick observe que la connaissance est une activité que nous exerçons constamment sans en comprendre la nature, tout comme nous remuons nos membres sans connaître les processus nerveux et musculaires qui rendent ce mouvement possible. La théorie de la connaissance n'a pas pour objet de rendre la connaissance possible — celle-ci s'exerce très bien sans elle — mais de comprendre en quoi elle consiste. Elle n'est pas une psychologie de l'acte de connaître, qui décrirait les processus mentaux impliqués, mais une analyse de ce qui fait d'un acte mental un acte de connaissance, autrement dit des conditions de sa validité. Son domaine est celui de la signification et de la vérité, non celui des événements psychiques.
Pour déterminer l'essence de la connaissance, Schlick part de l'examen de ce que nous appelons déjà « connaître » dans la vie quotidienne et dans la science. Dans la vie ordinaire, connaître un objet, c'est le reconnaître, c'est-à-dire le retrouver comme identique à quelque chose de déjà connu : reconnaître une plante comme un tilleul, un visage comme celui d'un ami. La connaissance quotidienne réside ainsi dans un acte de ''Wiedererkennen'' — de re-connaissance — par lequel le nouveau est subsumé sous le connu. La connaissance scientifique ne fait que prolonger et systématiser ce processus : elle aussi consiste à retrouver le connu dans l'inconnu, mais elle le fait avec une rigueur et une systématicité incomparablement plus grandes. Le physicien qui identifie la lumière comme un phénomène ondulatoire « reconnaît » la lumière comme un cas particulier des phénomènes vibratoires, et par là réduit l'inconnu au connu.
L'instrument de cette re-connaissance est le concept (''Begriff''). Schlick distingue soigneusement les concepts des représentations mentales (''Vorstellungen''). Les représentations — images visuelles, souvenirs sensoriels, associations — sont des événements psychiques individuels, variables d'un sujet à l'autre et d'un moment à l'autre. Elles ne peuvent pas servir d'instruments de connaissance, car elles sont essentiellement indéterminées et fluctuantes : la représentation que je me fais d'un arbre n'est jamais tout à fait la même et ne correspond jamais exactement à l'arbre réel. Les concepts, au contraire, sont des signes (''Zeichen'') qui désignent des objets de manière univoque. Un concept n'est pas une image qui « copie » la réalité ; c'est une « chose de pensée » (''Gedankending'') dont la seule fonction est de permettre la désignation exacte des objets à des fins de connaissance. Entre un concept et l'objet qu'il désigne, il n'y a pas de ressemblance, pas plus qu'entre le nom « César » et la personne historique de César : il y a une relation de désignation, soit une coordination (''Zuordnung'').
La question se pose alors de savoir comment les concepts acquièrent leur signification, en somme comment ils se trouvent mis en relation avec les objets qu'ils désignent. Schlick distingue deux modes de définition : la définition concrète et la définition abstraite ou implicite. La définition concrète consiste à montrer l'objet désigné — par exemple, montrer du doigt une couleur et dire « ceci est jaune ». Elle permet de relier un concept à une intuition (''Anschauung''), à un contenu d'expérience. Mais elle ne suffit pas à fonder un système de connaissance, car les contenus d'expérience sont subjectifs et incommunicables en tant que tels. La définition implicite, que Schlick emprunte à la pratique des mathématiciens et en particulier à David Hilbert, constitue l'autre voie. Un concept est défini implicitement lorsqu'il est caractérisé uniquement par les relations qu'il entretient avec d'autres concepts au sein d'un système d'axiomes. Ainsi, les termes « point », « droite » et « plan » de la géométrie de Hilbert ne reçoivent pas de définition directe par ostension ou par description ; leur signification est entièrement déterminée par les axiomes qui énoncent les relations entre eux. Un concept défini implicitement n'a donc pas de contenu intuitif : il est une pure place dans un réseau de relations.
Cette conception de la définition implicite a des conséquences considérables pour la théorie de la connaissance telle que Schlick la développe dans l{{'}}''Allgemeine Erkenntnislehre''. Elle montre que les concepts scientifiques n'ont pas besoin de « ressembler » à ce qu'ils désignent, ni de contenir un résidu d'intuition sensible. Ils peuvent être entièrement abstraits, pourvu que les relations entre eux soient univoquement déterminées. La connaissance ne procède pas par la reproduction d'images du réel, mais par la construction de systèmes de signes dont la structure formelle correspond à la structure des faits. Cette idée se rattache à la Zeichentheorie de Helmholtz — la théorie selon laquelle nos sensations sont des signes des choses, et non des copies — mais Schlick la radicalise en l'étendant des sensations aux concepts scientifiques eux-mêmes. Il convient toutefois de noter que le concept de définition implicite, central dans l{{'}}''Allgemeine Erkenntnislehre'', disparaît des écrits de Schlick après l'article « Erleben, Erkennen, Metaphysik » (1926), qui en représente la dernière défense publiée. Cette évolution, probablement liée à l'influence de Carnap et de Wittgenstein, n'affecte cependant pas la thèse générale selon laquelle la connaissance est connaissance de structures formelles.
La connaissance proprement dite ne réside cependant pas dans les concepts isolés, mais dans les jugements (''Urteile''). Un jugement met en relation deux ou plusieurs concepts et, ce faisant, exprime un fait — autrement dit l'existence d'une relation entre les objets désignés par ces concepts. Le jugement « la lumière est un phénomène ondulatoire » coordonne les concepts de « lumière » et de « phénomène ondulatoire » de telle sorte qu'ils désignent le même objet : la lumière est reconnue comme vibration, et cette reconnaissance constitue un acte de connaissance. Le jugement est donc l'unité fondamentale de la connaissance : seuls les jugements sont vrais ou faux, seuls les jugements contiennent de la connaissance. Les concepts isolés ne sont ni vrais ni faux ; ils ne sont que des instruments de désignation.
La vérité d'un jugement consiste, selon Schlick, dans la coordination univoque (''eindeutige Zuordnung'') de ce jugement aux faits. Un jugement est vrai lorsqu'à chacun de ses éléments correspond, de manière univoque, un élément du fait qu'il désigne, et que la structure relationnelle du jugement reproduit la structure relationnelle du fait. La vérité n'est donc ni l'utilité pratique (contre le pragmatisme), ni la conformité à un « devoir » de penser (contre le néo-kantisme), ni la cohérence interne d'un système de jugements (contre la théorie de la cohérence). Elle est une relation de coordination entre deux systèmes — le système des jugements et le système des faits —, relation qui doit être univoque pour que la connaissance soit accomplie. Schlick précise que cette univocité (''Eindeutigkeit'') de la coordination est le critère suprême de la vérité et, par extension, de toute connaissance : toutes les sciences travaillent à construire le grand réseau de jugements dans lequel le système des faits doit être « pris au filet », et la première et suprême condition pour que ce travail ait un sens est que chaque maillon du réseau de jugements soit univoquement coordonné à un maillon du réseau de faits.
Le rôle de l'égalité (''Gleichheit'') mérite une mention particulière dans ce contexte. Schlick accorde à la relation d'égalité une place privilégiée parmi toutes les relations : c'est elle qui rend possible la re-connaissance (''Wiedererkennen'') et, par suite, l'univocité de la coordination. Sans la possibilité de reconnaître que deux choses — ou deux aspects de la même chose — sont identiques ou semblables, aucune désignation univoque ne serait possible. Toute relation, pour être identifiée comme telle, doit être reconnue comme « la même » que des relations antérieurement rencontrées. L'égalité est ainsi la condition première de toute connaissance, celle sans laquelle rien de « cognitif » n'existerait. Cette thèse est précisée dans la seconde édition à la suite d'un échange avec le psychologue Wolfgang Köhler, qui avait objecté que la reconnaissance d'égalités n'est qu'un cas particulier de la constatation de relations. Schlick lui répond que l'égalité occupe bel et bien une position privilégiée, car constater n'importe quelle relation revient toujours à la reconnaître comme identique à des relations antérieures.
Dans la seconde édition de l'ouvrage, Schlick ajoute un paragraphe important (§ 11) consacré à la distinction entre définitions, conventions et jugements d'expérience. Cette distinction, qui intervient après l'élucidation de la nature de la vérité, précise la structure logique des systèmes scientifiques de jugements. Les définitions sont des jugements analytiques qui fixent le sens des concepts par stipulation ; leur vérité est affaire de convention, non de correspondance avec les faits. Les conventions, au sens de Poincaré, sont des jugements qui ne sont ni de pures définitions ni de purs jugements empiriques : ils contiennent un élément de choix, une libre création de l'esprit, mais ce choix est guidé par l'expérience et contraint par l'exigence de simplicité. Les jugements d'expérience (''Erfahrungsurteile'') sont les seuls jugements synthétiques ; leur vérité dépend de la correspondance avec les faits et doit être vérifiée empiriquement. Schlick nie catégoriquement l'existence d'une troisième classe de jugements — les jugements synthétiques a priori de Kant — et soutient que tous les jugements qui ne sont pas analytiques sont empiriques, en d'autres termes soumis au contrôle de l'expérience. Cette distinction tripartite entre définitions analytiques, conventions et jugements d'expérience synthétiques forme la charpente logique de la théorie schlickienne de la science.
La première partie se clôt par l'examen de ce que la connaissance n'est pas. Schlick critique d'abord l'idée que connaître consisterait à « voir » intuitivement l'essence des choses, que ce soit par une intuition intellectuelle (Schelling, Husserl) ou par une intuition vitale (Bergson). La connaissance ne relève pas de l'intuition, mais d' une coordination conceptuelle. Il critique ensuite l'idée que la connaissance ait pour but de reproduire le réel dans la conscience, d'en fournir une « copie » mentale. Le but de la connaissance ne réside pas dans l'Abbildung (la représentation-copie), mais la Zuordnung (la coordination-désignation). Enfin, il examine la valeur de la connaissance et juge que celle-ci ne réside pas dans un bénéfice pratique, mais dans l'économie de pensée qu'elle permet : plus un système de connaissance réduit le nombre de concepts fondamentaux nécessaires pour désigner l'ensemble des faits, plus le degré de connaissance est élevé.
Ce principe d'économie de pensée (''Denkökonomie''), que Schlick emprunte à Avenarius et à Mach, joue un rôle régulateur dans toute sa conception de la science. L'idéal de la science est de parvenir à un système de jugements aussi unifié et parcimonieux que possible, dans lequel un minimum de concepts fondamentaux et de lois fondamentales suffit à désigner de manière univoque la totalité des faits. Le progrès scientifique se mesure à la réduction du nombre de principes indépendants : chaque unification de domaines autrefois séparés — par exemple, l'unification de l'optique et de l'électromagnétisme par Maxwell — constitue une avancée de la connaissance. Ce n'est pas l'accumulation de faits qui fait progresser la science, mais leur subsomption sous des principes de plus en plus généraux et de moins en moins nombreux. Schlick ne confond cependant pas l'économie de pensée avec une simple commodité pratique : l'économie est la forme que prend la vérité dans un système accompli de jugements, la marque d'une coordination réussie entre les signes et les faits.
=== Problèmes de la pensée ===
La deuxième partie de l{{'}}''Allgemeine Erkenntnislehre'', intitulée « Problèmes de la pensée », quitte le terrain de l'analyse de la connaissance en général pour examiner les conditions de l'enchaînement des connaissances dans la pensée effective. Les problèmes traités sont ceux du raisonnement, de la logique, de la psychologie, de l'évidence et de la vérification. Schlick commence par analyser la structure du raisonnement déductif rigoureux. Tout raisonnement revient à substituer les uns aux autres les signes qui désignent les mêmes objets : si A = B et B = C, alors A = C. Le raisonnement ne produit pas de contenu objectivement nouveau ; il déploie les conséquences de ce qui était déjà contenu dans les prémisses. C'est le processus de l'analyse, dont les lois relèvent de la logique formelle. La déduction est donc un mouvement analytique : elle clarifie et explicite, mais n'ajoute rien au contenu des prémisses.
L'examen sceptique du raisonnement conduit Schlick à affirmer la validité indépassable des principes logiques. Le principe de non-contradiction et les autres principes de la logique ne sont pas des hypothèses que l'on pourrait accepter ou rejeter : ils sont constitutifs de la pensée elle-même. Toute assertion, toute négation les présuppose. Le scepticisme logique — le doute portant sur la validité de la logique elle-même — est autoréfutant, car il recourt nécessairement à la logique pour s'énoncer. Cela ne signifie pas que nous ne commettions jamais d'erreurs dans l'application des règles logiques ; mais ces erreurs sont des accidents psychologiques, toujours corrigibles en principe, et elles n'entament pas la validité des principes eux-mêmes.
La question de l'unité de la conscience (Einheit des Bewußtseins) est abordée ensuite. Le raisonnement lie des jugements successifs ; ces jugements appartiennent à une même conscience. L'unité de cette conscience semble être une condition nécessaire du raisonnement, car il faut que les prémisses et la conclusion soient présentes à un même sujet pour que la déduction s'effectue. Schlick reconnaît cette condition, mais en limite la portée : l'unité de la conscience est une condition psychologique de l'exercice de la pensée, non un fondement logique de sa validité. La validité d'une inférence ne dépend pas de l'unité du sujet qui l'effectue ; elle dépend des relations logiques entre les propositions. L'appel à l'unité de la conscience — que l'on trouve notamment dans la doctrine kantienne de l'aperception transcendantale — ne peut donc pas fonder la logique ni garantir la vérité.
Cette conclusion prépare la distinction, capitale dans la pensée de Schlick, entre le psychologique et le logique. Les lois logiques ne sont pas des lois naturelles décrivant le fonctionnement de l'esprit humain : elles sont des normes (Normen) de la pensée correcte. Le fait que les êtres humains pensent de telle ou telle manière — qu'ils fassent des associations, qu'ils raisonnent parfois mal, qu'ils soient sujets à des biais — relève de la psychologie empirique et n'a aucune pertinence pour la validité logique. Le psychologisme — la doctrine qui réduit les lois logiques à des lois psychologiques — est donc une erreur de principe. Schlick rejoint ici la critique du psychologisme formulée par Frege et par Husserl dans les Prolégomènes à la logique pure, mais sa position propre est plus nuancée : il admet que toutes nos constatations présupposent des conditions psychologiques et refuse de jouer au « cache-cache avec soi-même » en niant ce fait, tout en maintenant fermement que les règles logiques ne sont pas des régularités psychologiques.
L'examen de l'évidence (''Evidenz'') poursuit cette critique de la confusion entre le psychologique et le logique. L'évidence est un sentiment de certitude qui accompagne certaines pensées, un état psychologique que nous éprouvons lorsque quelque chose nous paraît « clair et distinct ». Mais un sentiment, aussi fort soit-il, ne peut pas servir de critère de vérité, car il reste un fait psychologique subjectif. L'histoire des sciences montre que des propositions tenues pour évidentes se sont révélées fausses. L'évidence peut accompagner la vérité, mais elle ne la constitue pas. De même, la perception interne (innere Wahrnehmung) — l'observation que le sujet fait de ses propres états mentaux — n'a pas le privilège épistémologique que Descartes et d'autres lui ont attribué. La perception de nos propres pensées et sentiments est faillible au même titre que la perception des objets extérieurs. La deuxième partie se conclut par l'examen de la vérification (''Verifikation''), processus par lequel les jugements empiriques sont confrontés à l'expérience pour déterminer leur vérité. La vérification est le mode propre de justification des jugements synthétiques, mais elle n'atteint jamais une certitude absolue : elle reste toujours faillible et révisable.
=== Problèmes de la réalité ===
La troisième partie, « Problèmes de la réalité », marque un tournant dans l'ouvrage. Les deux premières parties avaient examiné la forme de la connaissance — en quoi elle consiste et comment elle s'enchaîne dans la pensée — sans se prononcer sur son contenu, c'est-à-dire sur la nature des objets connus. La troisième partie passe de la forme au contenu : elle se tourne vers le système des faits, vers les objets désignés par les jugements, et pose la question de la réalité. Schlick la subdivise en trois sections : la « position du réel » (die Setzung des Wirklichen), qui demande ce qu'est le réel et comment on le distingue de l'irréel ; la « connaissance du réel » (die Erkenntnis des Wirklichen), qui examine ce que nous pouvons savoir du monde réel et sous quelle forme ; la « validité de la connaissance du réel » (die Gültigkeit der Wirklichkeitserkenntnis), qui interroge les conditions de possibilité et les limites de notre connaissance de la réalité.
La « position du réel » s'ouvre par une distinction entre l'attitude naïve — le réalisme spontané de la vie quotidienne, qui prend les objets perçus pour des réalités indépendantes — et les attitudes philosophiques qui la mettent en question : l'idéalisme, le phénoménalisme, le positivisme. Schlick avance que le critère de la réalité est la temporalité : est réel ce qui s'insère dans le temps, ce qui advient, dure et passe. Cette thèse, qu'il avait déjà formulée dans ses notes de Zurich, exclut du domaine du réel les entités atemporelles — les idées platoniciennes, les valeurs absolues — mais inclut les entités physiques non immédiatement perçues, pourvu qu'elles soient temporellement déterminées. Elle fournit un critère formel de réalité qui ne préjuge pas du contenu de ce qui est réel.
La question de la « chose en soi » (''Ding an sich'') et de la pensée d'immanence (''Immanenzgedanke'') est ensuite abordée avec une ampleur qui en fait l'une des discussions centrales du livre. La pensée d'immanence, défendue par des philosophes comme Avenarius, Schuppe et, sous une forme différente, Mach, soutient que nous ne connaissons que nos propres représentations, que le monde perçu est immanent à la conscience et qu'il est illégitime de postuler l'existence de choses « en soi » au-delà de l'expérience. Schlick soumet cette position à une critique détaillée. Il distingue deux problèmes : celui des objets non perçus (qu'advient-il de la table quand personne ne la regarde ?) et celui des objets perçus par plusieurs sujets (comment expliquer que différents individus perçoivent le « même » objet ?). Le premier problème conduit à examiner la tentative de réduire les objets non perçus à de simples « possibilités de sensation » (Möglichkeiten der Empfindung), dans l'esprit de Mill et de Mach. Schlick montre que cette réduction échoue : les possibilités ne sont pas des réalités, elles ne peuvent pas figurer comme termes dans des relations causales, et la science a besoin de postuler l'existence de réalités indépendantes de la perception pour maintenir la continuité de l'enchaînement causal. Le second problème confirme ce résultat : la concordance des perceptions de différents sujets ne peut être expliquée que par l'existence d'un monde objectif commun qui en est la cause. Schlick aboutit ainsi à un réalisme critique : il y a bien des choses « en soi », des réalités indépendantes de la conscience, mais nous ne les connaissons pas telles qu'elles sont « en elles-mêmes » — nous ne connaissons que leur structure, c'est-à-dire les relations qui existent entre elles.
La section consacrée à « la connaissance du réel » développe les conséquences de ce réalisme structurel. Schlick examine d'abord la distinction entre « essence » (''Wesen'') et « apparence » (''Erscheinung''). Cette distinction n'est pas ontologique, comme si les apparences étaient moins réelles que les essences ; elle est fonctionnelle : l'apparence est ce qui varie selon les conditions de l'observation, l'essence est ce qui reste constant sous ces variations. La science vise précisément à dégager, sous la variabilité des apparences, les structures invariantes qui constituent le réel.
Schlick procède ensuite à l'examen systématique de la subjectivité du temps, de l'espace et des qualités sensibles. Le temps vécu — le temps tel que nous en faisons l'expérience, avec son flux irréversible et la singularité du « maintenant » — est subjectif ; il dépend de notre constitution psychologique. Mais la structure temporelle objective — l'ordre de succession des événements, les relations de « avant » et « après » — existe indépendamment du sujet et peut être désignée par des concepts scientifiques. De même, l'espace perçu — avec ses qualités visuelles, tactiles, son horizon et sa profondeur — est subjectif ; il varie d'un sens à l'autre et d'un individu à l'autre. Mais l'espace objectif est un système d'ordonnancement abstrait, un schéma conceptuel qui permet la localisation univoque des événements. Les qualités sensibles — couleurs, sons, odeurs — sont intégralement subjectives : ce sont des « signes » que notre système nerveux produit en réponse aux stimulations physiques, et rien dans ces signes ne ressemble à ce qu'ils désignent. L'objectivité de notre connaissance du monde ne réside donc pas dans les qualités — qui sont subjectives et incommunicables — mais dans les relations quantitatives entre les grandeurs physiques.
Cette analyse conduit Schlick à l'une des thèses les plus importantes de l'ouvrage : la distinction entre connaissance quantitative et connaissance qualitative. La connaissance qualitative — celle qui saisit les qualités sensibles, les couleurs et les sons — ne dépasse pas la sphère du subjectif. La connaissance quantitative — celle qui établit des relations mesurables entre des grandeurs — est la seule qui atteigne l'objectivité, la seule qui mérite pleinement le nom de connaissance scientifique. Toute mesure repose sur la méthode des coïncidences ''raumzeitlicher Koinzidenzen'' : mesurer, c'est observer la coïncidence spatio-temporelle de deux points — l'extrémité d'un objet et la graduation d'un instrument. Cette méthode, qui sera développée dans ''Raum und Zeit in der gegenwärtigen Physik'', forme la base de toute détermination physique du réel.
Le traitement du problème psychophysique — le rapport entre le physique et le psychique — occupe une place considérable dans la troisième partie. Schlick défend l'idée que le physique et le psychique ne sont pas deux « substances » distinctes (dualisme), ni que l'un se réduit à l'autre (matérialisme ou idéalisme), mais qu'ils représentent deux modes de désignation d'une même réalité sous-jacente. Les concepts de la physique et les concepts de la psychologie peuvent être coordonnés aux mêmes processus réels, mais ils les désignent sous des descriptions différentes. Ce qui est décrit physiquement comme un processus cérébral est décrit psychologiquement comme une perception ou une pensée. Le « parallélisme » psychophysique n'est pas une relation causale entre deux séries d'événements distincts — physiques d'un côté, psychiques de l'autre — mais l'expression du fait qu'une même réalité admet deux systèmes de désignation. Schlick défend le principe de la « causalité physique fermée » (''geschlossene Naturkausalität''), selon lequel tout événement physique a une cause physique suffisante et le monde physique forme un système clos de déterminations causales. Les qualités psychiques — couleurs, sons, émotions — n'apparaissent pas dans les lois physiques et ne jouent aucun rôle causal dans le monde physique. Elles sont les « signes » subjectifs par lesquels la réalité se manifeste à la conscience. La position de Schlick s'apparente ainsi à un monisme neutre : il n'y a qu'une seule réalité, dont le physique et le psychique sont deux expressions.
La dernière section de la troisième partie, consacrée à la « validité de la connaissance du réel », se confronte directement à la philosophie de Kant. Schlick y examine les trois prétentions kantiennes : l'existence de formes pures de l'intuition (''Anschauungsformen''), l'existence de formes pures de la pensée (''Denkformen'') et l'existence de catégories a priori. Il nie d'abord qu'il existe une intuition pure, c'est-à-dire une forme de l'espace ou du temps qui serait donnée a priori indépendamment de toute expérience et qui constituerait une condition de possibilité de l'expérience. L'espace de la géométrie n'est pas un donné intuitif mais une construction conceptuelle ; il est tout aussi facile de « penser » des relations géométriques non euclidiennes que des relations euclidiennes, car il ne s'agit dans les deux cas que de l'ajout de concepts par lesquels les données intuitives sont interprétées. Il nie ensuite qu'il existe des formes pures de la pensée qui soient à la fois synthétiques et a priori. Les formes logiques sont bien a priori — elles sont présupposées par toute pensée — mais elles sont analytiques : elles ne produisent pas de contenu nouveau sur le monde. Les jugements synthétiques, en revanche, sont tous a posteriori : leur vérité ne peut être déterminée que par l'expérience. La catégorie de causalité fait l'objet d'un examen particulier : Schlick la considère comme un principe heuristique de la recherche scientifique, une présupposition méthodologique selon laquelle tout événement naturel obéit à des lois, mais il refuse de la considérer comme un jugement synthétique a priori au sens kantien. Le principe de causalité n'est pas une vérité nécessaire sur le monde ; il exprime la conviction, fondée sur l'expérience passée et toujours révisable, que la nature est régulière et que des lois universellement valides peuvent y être découvertes.
L'ouvrage se termine par un examen de la connaissance inductive, que Schlick traite avec une concision qu'il reconnaît lui-même insuffisante, le sujet exigeant « un livre à part ». L'induction — le passage de l'observation de cas particuliers à des lois générales — ne peut jamais atteindre une certitude logique, car la conclusion dépasse toujours les prémisses. Schlick esquisse une conception probabiliste de l'induction : les hypothèses scientifiques ne sont jamais définitivement vérifiées, mais leur probabilité augmente à mesure que l'expérience les confirme. La science avance ainsi par un processus de coordination toujours plus fine entre les jugements et les faits, sans atteindre jamais une vérité définitive et complète.
== Philosophie de la relativité ==
Parallèlement à la rédaction de l{{'}}''Allgemeine Erkenntnislehre'', puis dans son prolongement, Schlick s'engage dans une réflexion approfondie sur les fondements philosophiques de la physique contemporaine, et en particulier de la théorie de la relativité d'Einstein. Cette réflexion, amorcée dès 1912 dans ses notes de naturphilosophie et dans son échange épistolaire avec Max von Laue, aboutit en 1917 à la publication de l'article « ''Raum und Zeit in der gegenwärtigen Physik'' » dans la revue ''Die Naturwissenschaften'', article qui sera ensuite développé en un livre à part entière, publié en quatre éditions successives (1917, 1919, 1920, 1922). Einstein lui-même lit et approuve le manuscrit avant publication, y voyant une interprétation philosophique juste de sa théorie.
L'engagement de Schlick avec la théorie de la relativité ne doit pas être compris comme un simple exercice d'interprétation rétrospective. Dès son article de 1915 « Die philosophische Bedeutung des Relativitätsprinzips », il voit dans la relativité une confirmation de ses thèses épistémologiques les plus essentielles : l'abandon de l'espace et du temps absolus de Newton confirme que l'espace et le temps ne sont pas des réalités indépendantes mais des schémas d'ordonnancement ; la covariance générale confirme que seules les coïncidences spatio-temporelles — les Koinzidenzen — ont une réalité physique ; le rôle des conventions dans le choix de la géométrie confirme la distinction entre définitions et jugements d'expérience. La théorie de la relativité fournit ainsi à Schlick un cas privilégié d'application de sa théorie générale de la connaissance, et réciproquement, sa théorie de la connaissance fournit à la relativité un cadre philosophique adéquat. Einstein reconnaît cette adéquation mutuelle et la correspondance entre les deux hommes, qui s'étend de 1915 à la mort de Schlick, demeure un document majeur de l'histoire de la philosophie des sciences.
L'intention de Schlick dans ''Raum und Zeit'' n'est pas d'exposer la théorie de la relativité en tant que telle, mais d'en dégager les implications épistémologiques, à savoir montrer que cette théorie confirme et radicalise les conclusions atteintes dans l{{'}}''Allgemeine Erkenntnislehre'' concernant la nature de l'espace, du temps et de la connaissance physique. La thèse centrale est que la relativité achève le processus par lequel l'espace et le temps ont perdu « le dernier reste de réalité physique indépendante ». L'espace et le temps ne sont pas des réalités absolues, des cadres préexistants dans lesquels les événements physiques se déroulent ; ils sont des schémas d'ordonnancement abstraits, des systèmes de relations dont la structure est déterminée en partie par convention et en partie par l'expérience.
Schlick développe à cette occasion la « méthode des coïncidences » (''Koinzidenzmethode'') comme fondement de toute mesure physique. Toute mesure revient en dernière analyse à observer la coïncidence spatio-temporelle de deux événements — par exemple, la coïncidence d'un repère sur un instrument avec un point d'un objet mesuré. Einstein avait montré que la physique peut être entièrement formulée comme un ensemble de lois régissant ces coïncidences, et que tout ce qui ne se réduit pas à de telles coïncidences est dépourvu de réalité physique. Schlick saisit immédiatement la portée épistémologique de cette idée : la méthode des coïncidences est l'application physique de la méthode générale de la coordination univoque. De même que la connaissance en général revient à coordonner des signes aux faits, la connaissance physique consiste à coordonner des grandeurs mesurables aux événements par le moyen des coïncidences ponctuelles.
Schlick prend position dans le débat entre le positivisme strict de Mach, qui ne reconnaît de réalité qu'aux « éléments » directement vécus (couleurs, sons, pressions), et le réalisme critique qu'il défend. Il admet avec Mach que la base de toute physique est l'observation de coïncidences sensibles. Mais il objecte à Mach, que les grandeurs physiques qui apparaissent dans les équations différentielles de la physique — champs électriques, intensités magnétiques — ne sont pas de simples « fictions économiques » : elles peuvent désigner des réalités non immédiatement vécues, pourvu qu'on puisse leur assigner une détermination spatio-temporelle, en d'autres termes un « où » et un « quand » déterminés selon les règles de la recherche. Ce critère de la détermination spatio-temporelle — et non la simple mesurabilité en général — est ce qui distingue, pour Schlick, les objets réels des entités fictives. Le concept d'électron ou d'atome n'est pas nécessairement un simple outil de calcul ; il peut désigner un complexe réel d'éléments objectifs, de même que le concept du « moi » désigne un complexe réel d'éléments intuitifs.
Concernant la géométrie, Schlick adopte une position conventionnaliste inspirée de Poincaré. L'expérience ne nous contraint pas à utiliser une géométrie déterminée — euclidienne ou non euclidienne — pour décrire l'espace physique ; elle nous indique seulement laquelle nous devons choisir si nous voulons parvenir aux formulations les plus simples des lois de la nature. Le « choix » d'une géométrie relève donc en partie de la convention, mais c'est une convention guidée par l'expérience et soumise au critère de simplicité. Il n'a pas de sens de parler de la géométrie « de l'espace » indépendamment de la physique, car la structure géométrique n'est déterminée qu'en relation avec le comportement des corps physiques. Cette position se situe entre l'apriorisme kantien, qui fait de la géométrie euclidienne une vérité nécessaire de l'intuition pure, et l'empirisme radical, qui fait de la géométrie un simple résumé d'observations. Schlick la dirige aussi contre les tentatives de sauver une forme d'a priori constitutif à la lumière de la relativité. Ernst Cassirer, dans ''Zur Einsteinschen Relativitätstheorie'' (1921), cherche à conserver l'idée kantienne selon laquelle certains principes formels — la fonction d'objectivation, la loi de causalité — constituent les conditions de possibilité de l'expérience, même s'ils ne dictent pas à l'avance le contenu des lois de la nature. Hans Reichenbach, dans sa ''Relativitätstheorie und Erkenntnis Apriori'' (1920), tente de redéfinir l'a priori kantien en le dépouillant de son caractère de nécessité et d'universalité absolues : les « principes constitutifs » ne seraient plus des vérités immuables, mais des présuppositions révisables qui structurent l'expérience à un stade donné de la science. La position de Reichenbach en 1920 est subtile et ne se réduit pas à un néo-kantisme ordinaire ; elle constitue plutôt une tentative originale de conciliation entre kantisme et empirisme, que Reichenbach lui-même abandonnera d'ailleurs rapidement au profit d'un conventionnalisme plus radical. Schlick rejette ces deux tentatives : pour lui, dès lors qu'un principe est révisable et dépendant de l'expérience, il n'est plus a priori en aucun sens philosophiquement intéressant du terme, et l'usage du mot « a priori » pour désigner des présuppositions empiriques révisables ne fait qu'entretenir une confusion terminologique.
== La période viennoise ==
En 1922, après un bref passage par l'Université de Kiel, Schlick est appelé à occuper la chaire de philosophie des sciences inductives à l'Université de Vienne, chaire qui avait été celle d'Ernst Mach puis de Ludwig Boltzmann. Autour de lui se constitue progressivement un groupe de discussion — le futur « Cercle de Vienne » (Wiener Kreis) — qui réunit des philosophes, des mathématiciens et des scientifiques : Rudolf Carnap, Otto Neurath, Friedrich Waismann, Hans Hahn, Herbert Feigl, parmi d'autres. Ce groupe entreprend l'élaboration d'une philosophie scientifique rigoureuse, opposée à la métaphysique traditionnelle et fondée sur l'analyse logique du langage. Schlick en est le foyer intellectuel et organisationnel, quoiqu'il ne s'identifie pas à toutes les positions collectivement attribuées au Cercle.
=== Wittgenstein et le principe de vérification ===
L'événement intellectuel décisif de la période viennoise est la rencontre avec la pensée de Ludwig Wittgenstein, et en particulier avec le ''Tractatus Logico-Philosophicus'' (1921). Le Cercle de Vienne lit le ''Tractatus'' en détail au cours de séances régulières en 1926-1927, et Schlick entretient à partir de 1927 une relation personnelle avec Wittgenstein. L'influence du ''Tractatus'' sur Schlick est considérable, bien que sélective. Schlick retient principalement deux idées : d'une part, que les propositions de la logique et des mathématiques sont des tautologies, c'est-à-dire des propositions vides de contenu factuel, qui ne disent rien sur le monde mais explicitent les règles de notre système de signes ; d'autre part, que la signification d'une proposition est déterminée par les conditions de sa vérification, autrement dit par les observations qui la confirmeraient ou l'infirmeraient. Cette seconde idée, transformée en un critère général de signification, devient le « principe de vérification » (''Verifikationsprinzip''), qui forme la pierre angulaire de l'empirisme logique tel que le Cercle de Vienne le conçoit.
L'idée que les vérités logiques et mathématiques sont des tautologies s'articule de manière naturelle avec les analyses de l'''Allgemeine Erkenntnislehre''. Dans l'ouvrage de 1918/1925, Schlick avait déjà soutenu que le raisonnement déductif est analytique — qu'il ne produit pas de contenu nouveau — et que les définitions sont des jugements dont la vérité est affaire de stipulation. La thèse wittgensteinienne des tautologies précise et radicalise cette position : les vérités de la logique ne sont pas des « lois de la pensée » au sens psychologique, ni des « lois de l'être » au sens métaphysique ; elles sont des expressions de la structure de notre langage qui ne disent rien sur le monde. Cette reformulation linguistique de la distinction analytique/synthétique permet à Schlick d'affiner sa critique du synthétique a priori kantien : si les vérités logiques et mathématiques sont des tautologies, il ne reste aucun espace pour des vérités qui seraient à la fois synthétiques — autrement dit porteuses d'un contenu factuel — et a priori — autrement dit indépendantes de l'expérience.
Le principe de vérification énonce qu'une proposition n'a de sens (''Sinn'') que si l'on peut indiquer, au moins en principe, les observations qui permettraient de la vérifier ou de la réfuter. Les propositions qui ne satisfont pas cette condition — qui ne sont reliées à aucune expérience possible — sont dépourvues de signification cognitive, même si elles ont l'apparence grammaticale de propositions bien formées. Ce critère permet de tracer une ligne de démarcation entre les propositions scientifiques, qui sont vérifiables et donc douées de sens, et les propositions métaphysiques, qui ne le sont pas. Schlick formule cette position notamment dans l'article « Positivismus und Realismus » (1932), où il déclare que la question métaphysique traditionnelle de l'existence du monde extérieur est un pseudo-problème (''Scheinproblem'') : elle ne peut être ni vérifiée ni réfutée par aucune expérience possible, et elle est donc dépourvue de contenu cognitif. Cette position ne revient pas à nier l'existence du monde extérieur, mais à déclarer que la question de son existence, telle que la métaphysique la pose, n'a pas de sens déterminé.
=== Réalisme, positivisme et ''Scheinproblem'' ===
L'adoption du principe de vérification modifie sensiblement la position épistémologique de Schlick par rapport à celle de l{{'}}''Allgemeine Erkenntnislehre''. Dans l'ouvrage de 1918/1925, la question du réalisme recevait une réponse positive : Schlick y défendait un réalisme critique, affirmant l'existence de choses en soi au-delà de l'expérience et critiquant l'immanentisme. Dans « Positivismus und Realismus », il abandonne ce réalisme sous sa forme antérieure. Il ne verse pas dans l'idéalisme, mais il affirme désormais que l'opposition traditionnelle entre réalisme et positivisme est un pseudo-problème : correctement comprises, les deux positions ne sont pas des doctrines métaphysiques rivales. Dire qu'un objet physique existe indépendamment de la perception, c'est dire que certaines séquences régulières d'expériences sont possibles, et cette affirmation est parfaitement vérifiable. Ce qui est rejeté n'est pas l'existence du monde extérieur, mais l'idée qu'affirmer ou nier cette existence constituerait une thèse métaphysique substantielle. Le réalisme, vidé de ses prétentions métaphysiques, se réduit à une hypothèse empirique — la plus simple et la plus féconde pour rendre compte de la régularité de l'expérience — et cesse d'être une position philosophique rivale du positivisme.
=== Le débat des énoncés protocolaires ===
La période viennoise est aussi celle du « débat des énoncés protocolaires » (''Protokollsatzdebatte''), qui oppose Schlick à Neurath et Carnap sur la question du fondement de la connaissance empirique. Pour Neurath, les énoncés de base de la science (Protokollsätze) sont des énoncés intersubjectifs, formulés dans un langage physiciste, et ils n'ont pas de statut épistémologique privilégié : ils peuvent être révisés comme n'importe quel autre énoncé du système scientifique. Carnap adopte une position voisine. Schlick, en revanche, soutient dans « Über das Fundament der Erkenntnis » (1934) qu'il existe des énoncés d'observation jouissant d'une certitude absolue, qu'il appelle « constatations » (''Konstatierungen''). Ces constatations sont toujours de la forme « ici, maintenant, tel et tel » (hier jetzt so und so) — par exemple : « ici coïncident maintenant deux points noirs », « ici maintenant du jaune jouxte du bleu ». Elles comportent des mots déictiques (ici, maintenant) dont le sens ne peut être donné que par un geste d'ostension accompagnant l'énoncé ; c'est pourquoi une constatation authentique ne peut pas être mise par écrit : dès que les mots « ici » et « maintenant » sont consignés sur le papier, ils perdent leur sens. Une constatation écrite se transforme immédiatement en un énoncé protocolaire, c'est-à-dire en une hypothèse faillible. Il est essentiel de bien distinguer ces trois niveaux, car toute la position de Schlick repose sur leur différence. La constatation (''Konstatierung'') est un événement ponctuel, un acte présent de comparaison entre un jugement et un fait, qui ne survit pas à l'instant où il s'accomplit. L'énoncé protocolaire (''Protokollsatz''), au sens de Neurath et Carnap, est un énoncé inscrit, du type « M. S. a perçu du bleu le 15 avril 1934 à tel endroit » : il contient le nom d'un observateur, une date, un lieu, et il est par nature une hypothèse, car rien ne garantit qu'il reproduit fidèlement la constatation qui l'a occasionné. L'hypothèse scientifique, enfin, est un énoncé général déduit du système de la science. Pour Schlick, les énoncés protocolaires ne sont qu'un cas particulier d'hypothèses ; les constatations, en revanche, sont les seuls énoncés synthétiques qui ne sont pas des hypothèses, mais elles ne peuvent servir de fondation logique précisément parce qu'elles sont évanescentes. La spécificité des constatations réside en ce que, chez elles, le processus par lequel on saisit le sens et celui par lequel on établit la vérité coïncident — exactement comme pour les jugements analytiques, à cette différence près que les constatations ont un contenu factuel. Schlick insiste sur le fait que ces constatations ne sont pas les « fondements » de la science au sens où le reste de la science serait déduit d'elles : la science ne repose pas sur elles, mais conduit à elles. Elles sont un terme absolu (''absolutes Ende'') : le point d'arrivée du processus de vérification, le moment où la coordination entre les signes et les faits est effectivement accomplie. Elles jouent un rôle de vérification, non de fondation logique.
Cette position de Schlick dans le débat des ''Protokollsätze'' est cohérente avec la conception développée dans l{{'}}''Allgemeine Erkenntnislehre'' : la connaissance est une coordination de jugements à des faits, et cette coordination doit, à un certain moment, entrer en contact avec l'expérience. Les ''Konstatierungen'' sont précisément ces moments de contact, et c'est en les atteignant que la science accomplit sa mission. Mais la position de 1934 se distingue de celle de 1918/1925 par l'accent mis sur le langage : la question n'est plus seulement celle de la coordination entre pensée et réalité, mais celle de la coordination entre énoncés linguistiques et observations. Ce déplacement reflète l'influence de Wittgenstein et du « tournant linguistique » qui caractérise l'empirisme logique viennois.
=== La philosophie comme activité ===
Un autre développement important de la période viennoise concerne la conception de la philosophie elle-même. Dans une série d'articles et de conférences — notamment « Die Wende der Philosophie » (1930), « The Future of Philosophy » (1931 et 1932) et « Philosophie und Naturwissenschaft » (1934) — Schlick avance que la philosophie n'est pas un système de propositions mais une activité (''Tätigkeit''), celle par laquelle le sens des énoncés est établi ou mis au jour. La philosophie clarifie les propositions, les sciences les vérifient. L'idée que la tâche de la philosophie vise à clarifier le sens des résultats scientifiques (''Sinnklärung'') n'est pas entièrement nouvelle chez Schlick : elle se trouve déjà, comme le signale l'appareil éditorial de la Gesamtausgabe, dans le dernier paragraphe de l{{'}}''Allgemeine Erkenntnislehre'' et même dans la première leçon de Rostock en 1911. Ce qui est nouveau, et vient clairement de Wittgenstein, c'est la thèse radicale que la philosophie n'est pas du tout une science, qu'elle ne produit pas de propositions vraies, que ses résultats ne sont pas des « propositions philosophiques » mais des actes d'élucidation. Cette thèse apparaît pour la première fois dans l'article « Erkenntnistheorie und moderne Physik », dont la rédaction remonte à 1925 mais qui ne paraît qu'en 1929 dans ''Scientia''. Mais c'est surtout dans « Die Wende der Philosophie » (1930), puis dans les deux versions de « The Future of Philosophy » (1931 et 1932), que cette conception prend sa forme publique, programmatique et tranchée. Les problèmes traditionnels de la métaphysique — l'existence du monde extérieur, la nature de l'âme, la liberté du vouloir — ne sont pas des problèmes insolubles mais des problèmes mal posés, en d'autres termes formulés qui ne satisfont pas les conditions de la signification. La métaphysique échoue non parce que la raison humaine n'est pas à la hauteur de la tâche, mais parce que cette tâche n'existe pas. Correctement analysés, les pseudo-problèmes de la métaphysique se dissolvent ou se transforment en questions empiriques traitables par les sciences. Cette conception de la philosophie comme activité clarificatrice, et non comme corps de doctrine, sera l'un des héritages les plus durables du Cercle de Vienne.
=== Philosophie de la nature et mécanique quantique ===
Schlick poursuit aussi à Vienne son travail de philosophie de la nature, auquel il consacre des cours réguliers tout au long de la période viennoise — en 1927, en 1932-1933 et encore en 1936. Dans ces cours, il applique le principe de vérification aux développements de la physique contemporaine, en particulier à la mécanique quantique. Il argue par exemple que si la position et la vitesse d'un électron ne peuvent être simultanément déterminées par aucune expérience — comme le montre la relation d'indétermination de Heisenberg —, il n'a pas de sens de dire que l'électron possède simultanément ces deux propriétés. La philosophie de la nature est pour lui une « interprétation du sens » (''Sinndeutung'') des propositions de la science naturelle : elle ne produit pas de connaissance nouvelle de la nature, mais clarifie la signification de la connaissance que la science a produite. Schlick s'engage aussi dans une réflexion sur les conséquences du nouvel indéterminisme physique pour le problème de la liberté de la volonté, faisant valoir que l'acausalité quantique ne fournit aucun fondement à la liberté au sens éthique du terme, car un comportement régi par le pur hasard ne serait ni libre ni responsable.
=== Forme et contenu ===
Dans ses derniers écrits épistémologiques, Schlick approfondit la distinction entre forme et contenu (''Form und Inhalt'') qui traverse toute sa pensée. L'ouvrage « ''Form and Content: An Introduction to Philosophical Thinking'' » (rédigé en anglais à partir de conférences données à Londres, publié posthume dans les ''Gesammelte Aufsätze 1926-1936'') développe systématiquement la thèse selon laquelle la connaissance ne saisit que la structure formelle de la réalité — les relations entre les choses — et non leur contenu qualitatif intrinsèque. Les qualités vécues — la rougeur du rouge, le caractère douloureux de la douleur — sont des contenus d'expérience (''Erlebnisse'') qui ne peuvent être ni communiqués ni connus au sens strict. Ce qui peut être communiqué et connu, ce sont les relations structurelles : la position d'une couleur dans l'espace chromatique, l'intensité relative d'une douleur, les lois qui relient un stimulus à une sensation. La science, en tant que système de propositions communicables et vérifiables, est nécessairement une connaissance de structures, une désignation des relations entre les choses, et non une reproduction de leurs qualités. Il faut cependant noter que Schlick lui-même prit ses distances avec cet ouvrage en janvier 1935, écrivant à Louis Rougier que « toute la conception du livre ne correspondait plus à [s]es vues actuelles » et qu'il ne le ferait probablement pas imprimer. L'ouvrage ne parut que dans l'édition posthume de 1938.
Cette thèse de la nature structurelle de la connaissance est en continuité directe avec les analyses de l{{'}}''Allgemeine Erkenntnislehre'' sur la distinction entre connaissance quantitative et connaissance qualitative, et avec la Zeichentheorie héritée de Helmholtz. Mais elle acquiert dans les derniers écrits une formulation plus nette, influencée à la fois par le ''Tractatus'' de Wittgenstein et par les discussions au sein du Cercle de Vienne. Schlick avance que la distinction forme/contenu recouvre deux types d'indicible qu'il a tendance à juxtaposer sans toujours les distinguer clairement. Le premier type, emprunté au ''Tractatus'' de Wittgenstein, est l'indicible de la forme logique : la forme que les propositions partagent avec la réalité pour pouvoir la représenter ne peut pas elle-même être dite dans des propositions, mais seulement montrée (''gezeigt''). Le second type, qui est la contribution propre de Schlick, est l'indicible du contenu qualitatif : les qualités vécues ne peuvent être communiquées par aucune proposition, mais seulement éprouvées (''erlebt''). La science dit les structures ; l'art, la poésie et l'expérience personnelle donnent accès aux contenus. Les deux sont nécessaires à une existence humaine complète, mais ils ne relèvent pas du même registre.
== Éthique ==
L'éthique occupe une place significative dans l'œuvre de Schlick, depuis la ''Lebensweisheit'' de sa jeunesse jusqu'aux ''Fragen der Ethik'', publiées en 1930. Ce dernier ouvrage, qui représente sa contribution la plus systématique à la philosophie morale, reprend et approfondit les intuitions de la ''Lebensweisheit'' dans le cadre conceptuel élaboré au fil des ans. La thèse fondamentale des ''Fragen der Ethik'' est que l'éthique n'est pas une discipline normative au sens où elle prescrirait des devoirs absolus, mais une discipline descriptive et explicative qui étudie les faits du comportement moral humain et en recherche les causes. La question centrale de l'éthique n'est pas « que devons-nous faire ? » mais « pourquoi les hommes agissent-ils comme ils agissent, et pourquoi certaines actions sont-elles approuvées et d'autres désapprouvées ? ». Cette formulation du problème éthique comme problème d'explication, et non de prescription, doit beaucoup à Schopenhauer, à qui Schlick consacre des cours répétés tout au long de sa carrière et dont il juge l'approche « beaucoup plus féconde que celle de la morale kantienne, qui nous donne de nouvelles énigmes au lieu de résoudre les anciennes ».
Schlick identifie la motivation comme le concept directeur de l'éthique. Tout comportement humain est motivé, en d'autres termes causalement déterminé par des désirs, des penchants, des dispositions psychologiques. Le déterminisme — la thèse selon laquelle tout événement, y compris tout acte humain, a des causes suffisantes — n'est pas incompatible avec la responsabilité morale, contrairement à ce que soutiennent les libertariens. La responsabilité n'exige pas que l'agent ait pu agir autrement en un sens absolu (liberté d'indifférence) ; elle exige seulement que l'action ait été déterminée par le caractère de l'agent, par ses dispositions et ses motifs, et non par une contrainte extérieure. Nous tenons un homme pour responsable lorsque son action exprime sa volonté propre, et cette attribution de responsabilité a elle-même une fonction causale : elle agit comme un motif supplémentaire dans la délibération future de l'agent. Schlick défend ainsi un compatibilisme : le déterminisme et la responsabilité morale sont non seulement compatibles, mais la seconde présuppose le premier.
Les ''Fragen der Ethik'' poursuivent la ''Lebensweisheit'' en fondant l'éthique sur une psychologie du plaisir. Ce qui est moralement approuvé est, en dernière analyse, ce qui procure du bonheur ; ce qui est moralement désapprouvé est ce qui cause de la souffrance. Les règles morales ne sont pas des commandements tombés du ciel ni des impératifs catégoriques de la raison pure ; elles sont des généralisations de l'expérience humaine concernant les conditions du bien-vivre en société. Elles sont révisables à la lumière de l'expérience, tout comme les hypothèses scientifiques. L'éthique est ainsi ramenée au statut d'une science empirique parmi d'autres, conformément à la conviction constante de Schlick que les questions philosophiques sont ultimement des questions empiriques.
Il faut noter que les ''Fragen der Ethik'' se confrontent explicitement à Kant et à la tradition de l'éthique déontologique. Schlick rejette le formalisme kantien — l'idée que l'impératif catégorique fournit un critère purement formel de l'action juste, indépendant de tout contenu empirique. Pour Schlick, un tel formalisme est vide : la forme logique « agis de telle sorte que la maxime de ton action puisse devenir une loi universelle » ne suffit pas à déterminer ce qu'il faut faire dans une situation concrète sans faire appel aux conséquences empiriques de l'action. De même, la notion kantienne de « devoir » (''Pflicht'') comme fondement ultime de la morale est critiquée : le devoir n'est pas un fait moral irréductible, mais l'expression de la pression sociale exercée sur l'individu par les normes de son groupe. L'éthique scientifique doit expliquer l'origine et la fonction de ces normes, non les poser comme des absolus. Schlick conçoit le progrès moral comme un passage de l'éthique du devoir (''Ethik der Pflicht'') à une éthique de la bonté (''Ethik der Güte''), dans laquelle l'action bonne n'est plus accomplie par contrainte ou par obéissance à une loi, mais procède spontanément du caractère de l'agent — processus qu'il illustre, dans ses cours sur Nietzsche, par la figure du Surhumain. Schlick reconnaît cependant que le sentiment du devoir est un fait psychologique puissant et que sa compréhension causale ne diminue en rien sa force motivatrice.
== Trajectoire d'ensemble ==
L'œuvre de Schlick, interrompue par son assassinat le 22 juin 1936 sur les marches de l'Université de Vienne, ne forme pas un système achevé et uniforme. Elle présente plutôt une trajectoire intellectuelle marquée par des déplacements, des rectifications et des ruptures partielles. Le Schlick de l{{'}}''Allgemeine Erkenntnislehre'', qui défend un réalisme critique, construit une théorie de la connaissance en termes de coordination univoque et recourt au concept de définition implicite, n'est pas identique au Schlick tardif de « Die Wende der Philosophie » et du débat des ''Protokollsätze'', qui soutient que la philosophie n'est pas une science et ne produit pas de propositions. Schlick lui-même, d'ailleurs, exprimait une certaine insatisfaction devant la seconde édition de l{{'}}''Allgemeine Erkenntnislehre'', qu'il jugeait insuffisamment poussée dans la direction nouvelle que lui indiquait la lecture de Wittgenstein. La doctrine des ''Konstatierungen'', elle-même, ne s'intègre pas sans tension au reste du système : ces « points de contact » entre connaissance et réalité sont par définition éphémères, non inscriptibles, et ne peuvent servir de fondation logique — ce qui entre en tension avec l'ambition systématique des premiers écrits.
Il y a cependant des continuités réelles, et elles ne sont pas négligeables. Le refus de la métaphysique intuitive, l'intérêt pour la physique comme terrain d'épreuve de l'épistémologie, le souci de précision conceptuelle et l'idée que la connaissance est affaire de structure formelle et non de contenu qualitatif — ces orientations traversent toute l'œuvre, de Rostock à Vienne, même si elles changent de formulation et de justification. La théorie de la connaissance comme coordination univoque (''Zuordnung'') de signes à des faits, formulée dès 1910 et systématisée dans l{{'}}''Allgemeine Erkenntnislehre'', reste un soubassement de la réflexion de Schlick, y compris dans sa phase viennoise, même si le vocabulaire se transforme sous l'influence de Wittgenstein. Le rapport au réalisme est plus complexe et ne se laisse pas résumer par une simple continuité. Le Schlick de l{{'}}''Allgemeine Erkenntnislehre'' défend clairement un réalisme critique : il affirme l'existence de choses en soi, critique l'immanentisme et soutient que la science connaît la structure du réel. Mais à partir de « Erleben, Erkennen, Metaphysik » (1926) et surtout de « Positivismus und Realismus » (1932), Schlick ne reprend plus à son compte le réalisme tel qu'il l'avait formulé. Il ne bascule pas dans un idéalisme, mais il reformule le débat de telle manière que l'alternative classique entre réalisme et positivisme perd, selon lui, son sens philosophique traditionnel : la question de savoir si le monde extérieur « existe vraiment » est déclarée pseudo-problème, non parce que la réponse serait négative, mais parce que la question, telle que la métaphysique la pose, ne satisfait pas les conditions de la signification. C'est un déplacement sémantique radical, et non un simple ajustement de surface. Le refus du synthétique a priori est peut-être la thèse la plus constante de toutes : toute connaissance du monde est empirique et révisable, et les structures logiques et mathématiques sont analytiques. Mais — et c'est un point capital — ce qui change entre les deux périodes est le statut de la philosophie elle-même. Pour le Schlick de l{{'}}''Allgemeine Erkenntnislehre'', la philosophie est encore, au moins en partie, une science qui énonce des vérités sur les conditions de la connaissance. Pour le Schlick tardif, la philosophie n'énonce rien : elle réside exclusivement dans l'activité par laquelle le sens des propositions scientifiques est clarifié. Le contenu de tout ce qui peut être dit avec sens appartient aux sciences empiriques ; la philosophie, quant à elle, n'a pas de contenu propre, mais seulement une fonction. C'est ce déplacement qui représente la véritable « Wende » de la pensée de Schlick.
=== Schlick et Kant ===
La relation de Schlick à Kant est particulièrement complexe et éclairante. Il emprunte à Kant la question transcendantale — comment la connaissance du réel est-elle possible ? — mais il refuse systématiquement la réponse kantienne. Il n'y a pas de formes a priori de l'intuition, pas de catégories constitutives de l'expérience, pas de jugements synthétiques a priori. L'espace et le temps ne sont pas des conditions subjectives de la sensibilité, mais des systèmes d'ordonnancement dont la structure est déterminée par la convention et l'expérience. La causalité n'est pas une catégorie a priori, mais un principe heuristique empiriquement motivé. La réponse de Schlick à la question transcendantale est que la connaissance du réel est possible parce que les faits se laissent coordonner de manière univoque par des systèmes de jugements, et que cette coordination n'exige ni formes a priori ni catégories métaphysiques.
=== Schlick et Mach ===
La relation à Mach est tout aussi significative. Schlick partage avec Mach l'orientation empiriste, l'idéal d'économie de pensée, la critique de la métaphysique. Mais il se sépare de Mach sur un point capital : le statut des entités théoriques de la physique. Pour Mach, les atomes, les électrons, les champs ne sont que des fictions économiques, des abrégés commodes pour résumer des régularités dans les données sensibles. Pour Schlick, ces entités peuvent désigner des réalités objectives, non immédiatement perceptibles mais dotées d'une détermination spatio-temporelle. Le critère de réalité n'est pas la perception directe mais la possibilité d'assigner à un objet un lieu et un temps déterminés. Cette divergence se manifeste dès l{{'}}''Allgemeine Erkenntnislehre'' dans la critique de la pensée d'immanence. Dans la période viennoise, la divergence avec Mach prend une forme différente : Schlick ne défend plus un réalisme critique au sens traditionnel, mais il ne verse pas non plus dans le phénoménalisme machien. Il affirme que l'opposition même entre réalisme et positivisme, correctement analysée, se dissout comme pseudo-problème — position qui reste incompatible avec le réductionnisme de Mach, mais qui ne reconduit plus le réalisme métaphysique de l{{'}}''Allgemeine Erkenntnislehre''.
=== Influence et postérité ===
L'influence de Schlick sur la philosophie du vingtième siècle s'exerce à travers plusieurs canaux : sa philosophie de la relativité, qui fut la première interprétation philosophique systématique de la théorie d'Einstein et reçut l'approbation d'Einstein lui-même ; sa fondation et son animation du Cercle de Vienne, qui constitua le noyau institutionnel de l'empirisme logique ; sa formulation du principe de vérification, qui devint la thèse la plus discutée et la plus controversée du positivisme logique ; et sa théorie structurelle de la connaissance, qui anticipe des développements ultérieurs en philosophie des sciences. L{{'}}''Allgemeine Erkenntnislehre'' elle-même, bien que rapidement dépassée par les développements de la logique formelle et de la philosophie du langage, reste un document central de l'histoire de l'épistémologie au vingtième siècle, par la clarté et la rigueur avec lesquelles elle formule les problèmes fondamentaux de la connaissance et propose une réponse systématique fondée sur la notion de coordination univoque.
Le style philosophique de Schlick se caractérise par une clarté délibérée et un souci constant de s'adresser au-delà du cercle des spécialistes. Il revendique une exposition « aussi simple que possible, bâtie lentement », et considère que l'obscurité en philosophie n'est pas le signe de la profondeur mais celui de la confusion. Cette exigence de clarté n'est pas un trait accidentel de son tempérament ; elle est la conséquence directe de sa conception de la philosophie comme activité de clarification. Si la philosophie a pour tâche de déterminer le sens des propositions et de dissoudre les pseudo-problèmes, elle ne peut s'acquitter de cette tâche qu'en étant elle-même parfaitement claire. La philosophie obscure est une philosophie qui a échoué dans sa mission. C'est cette conviction qui distingue Schlick de la tradition métaphysique allemande et le rapproche de la tradition empiriste anglo-saxonne, avec laquelle il entretient un dialogue constant — de Hume et Mill à Russell et Wittgenstein. Sa pensée forme ainsi un pont entre deux traditions intellectuelles que le vingtième siècle a souvent opposées, et c'est peut-être dans cette position de médiateur que réside l'une de ses contributions les plus durables à la philosophie contemporaine.
{{DEFAULTSORT:Schlick}}
[[Catégorie:Philosophe]]
teny5pmh4j7djxpsu0giyotv514bjmu
Dictionnaire de philosophie/Max Stirner
0
83905
768698
766808
2026-06-26T04:33:41Z
PandaMystique
119061
Bot : ajout du paramètre lecture=oui au modèle {{DicoPhilo}}
768698
wikitext
text/x-wiki
{{DicoPhilo|Max Stirner|lecture=oui}}
'''Max Stirner''', pseudonyme de Johann Caspar Schmidt (1806-1856), est un philosophe allemand issu du milieu jeune-hégélien. Son unique ouvrage majeur, ''Der Einzige und sein Eigentum'' (''[[s:L’Unique et sa propriété|L'Unique et sa propriété]]'', 1844), entend pousser à son terme la critique des [[Dictionnaire de philosophie/Aliénation|aliénations]] engagée par la gauche hégélienne. Selon Stirner, la tradition chrétienne, idéaliste, humaniste, libérale et socialiste moderne reconduit un même geste : la subordination de l'[[Dictionnaire de philosophie/Individu|individu]] concret à des [[Dictionnaire de philosophie/Abstraction|abstractions]] sacralisées (Dieu, l'Homme, l'État, la Société, la Morale, le Droit). À ces fantômes, Stirner oppose l'Unique (''der Einzige''), entendu comme l'individu corporel et singulier irréductible à toute essence. L'ouvrage, qui suscite immédiatement les réfutations de Feuerbach, Hess et Szeliga (ce dernier au nom de Bruno Bauer), ainsi qu'une longue réplique de [[Dictionnaire de philosophie/Karl Marx|Marx]] et Engels dans ''L'Idéologie allemande'', tombe rapidement dans l'oubli. Sa redécouverte, à partir des années 1880, l'inscrit dans plusieurs lignées de réception : la décomposition de la gauche hégélienne dont il marque l'un des points d'aboutissement, la formation négative de la pensée marxienne, l'[[Dictionnaire de philosophie/Anarchisme|anarchisme]] individualiste, le rapprochement (plus comparatif que démontré) avec Nietzsche, et certaines relectures existentialistes ou poststructuralistes du XXe siècle.
== Biographie ==
Johann Caspar Schmidt naît le 25 octobre 1806 à Bayreuth, dans une famille protestante de petite condition.<ref>Pour les éléments biographiques, la source primaire demeure la biographie de John Henry Mackay, ''Max Stirner. Sein Leben und sein Werk'' (Berlin, 1898 ; 2e éd. 1910 ; 3e éd. Leipzig, 1914), dont la plupart des études ultérieures dépendent. Pour la synthèse en langue anglaise, voir R. W. K. Paterson, ''The Nihilistic Egoist Max Stirner'', Oxford University Press, 1971, ch. I, « The Man and his Work », p. 1-17.</ref> Son père, Albert Christian Heinrich Schmidt, facteur de flûtes, meurt six mois après sa naissance. Sa mère se remarie deux ans plus tard avec un préparateur en pharmacie, Heinrich Friedrich Ludwig Ballerstedt, et le couple s'installe à Kulm, en Prusse occidentale, où l'enfant passe ses premières années. En 1818, il est renvoyé à Bayreuth chez son parrain pour y poursuivre ses études ; il accomplit le cycle complet du Gymnasium classique de la ville, dont le recteur n'est autre que Georg Andreas Gabler, futur successeur de Hegel à la chaire de philosophie de Berlin. C'est dans ce contexte scolaire que ses condisciples lui attribuent le sobriquet de « Stirner », du nom commun ''Stirn'' (« front »), en raison de la hauteur saillante de son front.<ref>L. S. Stepelevich, ''Max Stirner on the Path of Doubt'', Lanham, Lexington Books, 2020, p. 31, qui propose la glose plaisante de « Max the Highbrow » ; Paterson, ''op. cit.'', p. 4, donne la même étymologie.</ref> Le surnom lui restera comme nom de plume jusqu'à son œuvre majeure.
Inscrit en 1826 à la faculté de philosophie de l'université de Berlin, Stirner y suit pendant quatre semestres un programme d'enseignement classique. Sa formation berlinoise le met directement au contact de l'hégélianisme : il assiste notamment à trois séries de cours de Hegel lui-même, sur la philosophie de la religion (été 1827), l'histoire de la philosophie, et la philosophie de l'esprit, ainsi qu'aux enseignements d'éthique de Schleiermacher, d'histoire de l'Église de Neander et de dogmatique de Marheineke.<ref>Le détail des cours suivis est donné par Mackay et repris par Stepelevich, ''op. cit.'', p. 31-32, et Paterson, ''op. cit.'', p. 4-5.</ref> Paterson souligne que sa familiarité ultérieure avec la ''Phénoménologie de l'esprit'', l'''Encyclopédie'', les ''Principes de la philosophie du droit'' et les ''Leçons sur la philosophie de l'histoire'' est attestée par les références internes de ''L'Unique''.<ref>Paterson, ''op. cit.'', p. 47, cité par Stepelevich, ''op. cit.'', p. 32-33.</ref> Stirner gagne ensuite Erlangen, où il suit en 1828 les cours du hégélien Christian Kapp, puis Königsberg en 1829, avant qu'une longue interruption, sans doute liée à la maladie mentale grandissante de sa mère, ne le ramène à Berlin en 1832 pour y achever ses études.
L'épreuve du ''Staatsexamen'' (1834-1835) tourne mal. Son examinateur principal, Friedrich Adolf Trendelenburg, aristotélicien notoire et adversaire déclaré de la logique hégélienne, ne lui décerne qu'une ''facultas docendi'' limitée, qui l'autorise à enseigner sans pouvoir prétendre à un poste régulier de ''Gymnasiallehrer''.<ref>Paterson, ''op. cit.'', p. 5-6 ; Henri Arvon, ''Aux sources de l'existentialisme : Max Stirner'', Paris, PUF, 1954, p. 9-10.</ref> Stirner renonce dès lors à la carrière universitaire. Après un stage non rétribué à la Königliche Realschule de Berlin (1835-1836), il épouse en décembre 1837 Agnes Clara Kunigunde Butz, fille de sa logeuse ; sa jeune femme meurt en couches en août 1838, donnant naissance à un enfant mort-né. En octobre 1839, Stirner obtient enfin un poste rémunéré à l'institution privée pour jeunes filles de Madame Gropius, où il enseignera la littérature et l'histoire pendant cinq années, jusqu'à la veille de la publication de son livre.
À partir de la fin de l'année 1841, Stirner fréquente assidûment le cercle des « Affranchis » (''die Freien''), groupe de jeunes hégéliens berlinois qui se réunissait à la ''Hippels Weinstube'', sur la Friedrichstraße.<ref>Sur le cercle des ''Freien'', voir Jeff Spiessens, ''The Radicalism of Departure. A reassessment of the Hegelianism in Max Stirner's "Der Einzige und sein Eigentum"'', Newcastle upon Tyne, Cambridge Scholars Publishing, 2018, ch. I, « Die Freien and Stirner's philosophical allegiance before ''Der Einzige und sein Eigentum'' », p. 23-68.</ref> La figure intellectuelle dominante du cercle était Bruno Bauer ; on y croisait également Edgar et Egbert Bauer, Ludwig Buhl, Eduard Meyen, Arnold Ruge. [[Dictionnaire de philosophie/Karl Marx|Karl Marx]] fut lié au même milieu jeune-hégélien et au réseau de Bauer, mais sa fréquentation effective du cercle, et de Stirner en particulier, demeure mal attestée. La relation de Stirner à Friedrich Engels est mieux documentée : Engels, qui le connut personnellement, nous a laissé l'unique portrait connu du philosophe, dessiné de mémoire à Londres en 1892, et a témoigné dans plusieurs lettres avoir discuté à de nombreuses reprises avec lui de la dialectique hégélienne.<ref>Voir les témoignages d'Engels reproduits par Stepelevich, ''op. cit.'', p. 32-34, et le commentaire de John F. Welsh, ''Max Stirner's Dialectical Egoism: A New Interpretation'', Lanham, Lexington Books, 2010, p. 5-10.</ref> Stirner assistait également, en 1841-1842, au cours sur la ''Logique'' de Hegel donné par Karl Werder, où l'on trouvait par ailleurs Engels, Bakounine, Kierkegaard et Tourgueniev.<ref>Stepelevich, ''op. cit.'', p. 33, citant une lettre d'Engels qui rappelle leur intérêt commun pour la question hégélienne du commencement de la ''Logique''.</ref> Stepelevich relève que la formation hégélienne formelle de Stirner fut, à tout prendre, au moins aussi étendue que celle de la plupart des autres jeunes hégéliens.
Avant la parution de l'œuvre majeure, Stirner publie une série d'articles dans les périodiques de l'aile gauche : il donne notamment une recension du pamphlet anonyme de Bruno Bauer, ''Die Posaune des jüngsten Gerichts über Hegel den Atheisten und Antichristen'' (janvier 1842), un long essai intitulé ''Das unwahre Prinzip unserer Erziehung'' (avril 1842), une étude sur ''Kunst und Religion'' (juin 1842), des considérations sur l'État fondé sur l'amour (1843) et une recension du roman d'Eugène Sue, ''Les Mystères de Paris'' (1843).<ref>Ces écrits sont rassemblés dans Max Stirner, ''Kleinere Schriften und seine Entgegnungen auf die Kritik seines Werkes "Der Einzige und sein Eigentum"'', éd. John Henry Mackay, 2e éd. Berlin, 1914 (réimpr. Stuttgart-Bad Cannstatt, Frommann-Holzboog, 1976).</ref> Le 21 octobre 1843, il épouse en secondes noces Marie Wilhelmine Dähnhardt (1818-1902), jeune femme émancipée venue de Gadebusch, qui fréquentait les ''Freien'' et disposait d'un héritage substantiel. La cérémonie burlesque, où Bruno Bauer servit de témoin et où les anneaux furent improvisés à partir de sa bourse en cuivre, demeure l'une des rares anecdotes attachées à la vie du philosophe.<ref>L'anecdote est rapportée par Mackay et reprise par Paterson, ''op. cit.'', p. 9-10, et Arvon, ''op. cit.'', p. 13-14.</ref>
''L'Unique et sa propriété'' (''Der Einzige und sein Eigentum'') paraît à Leipzig chez Otto Wigand au début du mois de novembre 1844, daté de 1845 sur la page de titre. Sur les mille exemplaires du premier tirage, deux cent cinquante sont saisis par la ''Kreisdirektion'' de Leipzig, avant que le ministre saxon de l'Intérieur ne lève l'interdiction au motif que l'ouvrage serait « trop absurde » pour être dangereux.<ref>Paterson, ''op. cit.'', p. 10-11 ; Stepelevich, ''op. cit.'', p. 35-36.</ref> Le livre suscite immédiatement une vive polémique : Szeliga (pour Bauer), Moses Hess et Feuerbach publient chacun leur réfutation ; Marx et Engels, dans un manuscrit demeuré inédit jusqu'en 1932, ''L'Idéologie allemande'', consacrent près des trois quarts du volume à la démolition systématique de la pensée de « saint Max ». Stirner répond à ses critiques dès septembre 1845 dans les ''Rezensenten Stirners'', publié dans la troisième livraison du ''Wigands Vierteljahrsschrift'', puis, en 1847, à Kuno Fischer dans les ''Epigonen'', sous le pseudonyme transparent de « G. Edward ».<ref>''Kleinere Schriften'', éd. Mackay, p. 343-401 (« Rezensenten Stirners ») et p. 401 ''sq.'' (« Die philosophischen Reactionäre »).</ref>
L'engouement initial est aussi vif qu'éphémère. Stirner quitte son poste d'enseignant peu avant la parution du livre. Il se lance en 1845 dans une entreprise de distribution de lait dans les faubourgs berlinois qui dilapide la fortune de sa femme ; Marie Dähnhardt le quitte fin 1846, le divorce étant prononcé en 1850. Ruiné, Stirner subsiste péniblement en traduisant en allemand les œuvres économiques de Jean-Baptiste Say (1845-1846) et d'Adam Smith (1846-1847), huit volumes d'une facture honnête mais dépourvus des notes du traducteur initialement promises. Son dernier ouvrage, une ''Geschichte der Reaction'' (1852), n'est guère qu'un assemblage d'extraits de Burke, de Comte et d'auteurs conservateurs contemporains. Il connaît deux brefs séjours en prison pour dettes (1853 et 1854). Au mois de mai 1856, dans son logement de la Philippstraße, il est piqué au cou par un insecte venimeux et succombe le 25 juin à la septicémie qui s'ensuit. Bruno Bauer et Ludwig Buhl figurent parmi les rares assistants à ses obsèques au Sophien-Friedhof.<ref>Paterson, ''op. cit.'', p. 13-16.</ref>
== Le contexte de la décomposition de l'hégélianisme ==
L'enjeu propre de l'entreprise stirnérienne doit être situé dans le contexte de la décomposition de l'hégélianisme qui caractérise les années 1830-1840 en Allemagne. La mort de Hegel en 1831 laisse son école divisée. La droite hégélienne, conduite par Marheineke et Gabler, maintient la compatibilité du système avec le christianisme et défend la souveraineté de l'État rationnel. La gauche retourne la dialectique contre ses contenus : la ''Vie de Jésus'' de [[s:Auteur:David Strauss|David Friedrich Strauss]] (1835) réduit les récits évangéliques au statut de mythes ; Bruno Bauer, dans une critique plus radicale, fait de la conscience de soi le seul principe authentique de la philosophie ; [[s:Auteur:Ludwig Feuerbach|Ludwig Feuerbach]], dans ''[[s:Essence du Christianisme|L'Essence du christianisme]]'' (1841), procède à l'inversion généalogique de la théologie en anthropologie : Dieu n'est que la projection aliénée de l'essence générique de l'homme (''Gattungswesen'').
C'est contre la prétention de Feuerbach à avoir achevé la critique de la religion que Stirner construit son livre. Selon lui, en substituant l'Homme à Dieu, Feuerbach n'a pas dissous le sacré, il l'a déplacé. L'humanité feuerbachienne, érigée en essence devant laquelle l'individu concret est sommé de s'incliner, joue le même rôle fonctionnel que l'ancienne divinité. La thèse fondamentale de ''L'Unique et sa propriété'' consiste à soutenir que l'ensemble de la tradition philosophique, depuis le christianisme jusqu'au libéralisme et au socialisme, en passant par l'idéalisme et l'humanisme athée, ne fait que perpétuer un même geste : l'érection d'une entité abstraite (Dieu, l'Esprit, l'Homme, la Société, l'Humanité, la Morale, le Droit) au-dessus de l'individu, et la subordination de cet individu à cette entité. Stirner résume cette critique dans une formule fameuse : « Les plus récentes révoltes contre Dieu ne sont que des insurrections théologiques ».<ref>Cité d'après R.-L. Reclaire dans sa préface à sa traduction française de ''L'Unique et sa propriété'', Paris, P.-V. Stock, 1899, p. 12.</ref>
Le rapport de Stirner à Hegel a longtemps fait l'objet de lectures opposées. Henri Arvon, dans ''Aux sources de l'existentialisme : Max Stirner'' (1954), a vu dans ''L'Unique'' l'aboutissement logique de l'hégélianisme ; d'autres y ont lu un rejet pur et simple, ironique, de la dialectique. Les recherches récentes invitent à une formulation plus nuancée. Pour Stepelevich, Stirner serait le « légitime héritier » de la philosophie hégélienne, qui occupe le point de clôture du grand cercle dialectique.<ref>Stepelevich, ''op. cit.'', Préface (p. ix) : « ''both are linked by a profound dialectical logic'' [...] ''and Stirner rests at the point of its closure'' ».</ref> Pour Spiessens, ''L'Unique'' met en scène un départ de l'hégélianisme par les moyens mêmes de l'hégélianisme : Stirner s'approprie le langage et la logique hégéliens, qu'il traite comme sa propriété, afin de renverser la relation entre le penseur et la pensée. Là où, dans le système hégélien, l'individu reçoit son sens à l'intérieur du déploiement de l'Esprit, Stirner replace l'individu concret comme propriétaire de ses pensées et créateur de ses concepts.<ref>Spiessens, ''op. cit.'', p. 15-21.</ref>
== Structure de ''L'Unique et sa propriété'' ==
L'ouvrage s'ouvre et se clôt sur la même formule, empruntée à un poème de Goethe (''Vanitas! Vanitatum vanitas!'', 1806) : ''Ich hab' Mein' Sach' auf Nichts gestellt'', que R. L. Reclaire, dans sa traduction française de 1899, rend par « Je n'ai basé ma cause sur rien ».<ref>Stirner, ''L'Unique et sa propriété'', trad. Reclaire, p. 18 et p. 438 (incipit et explicit symétriques de l'ouvrage).</ref> Cette bipolarité formelle scelle le mouvement de l'œuvre, qui retourne à son point de départ après l'avoir traversé. Le livre se divise en deux parties dont la dichotomie reproduit la coupure que Stirner s'efforce d'instituer : la première, « L'Homme » (''Der Mensch''), retrace la généalogie historique et philosophique des aliénations auxquelles l'individu a été soumis ; la seconde, « Moi » (''Ich''), développe la position propre de l'auteur. Les deux parties se subdivisent triadiquement, ce qui confère à l'œuvre une structure étonnamment proche, dans sa forme, du modèle hégélien que son contenu prétend récuser. La seconde partie articule ainsi trois moments : ''Die Eigenheit'' (l'ipséité ou propriété de soi, rendue par Reclaire par « la propriété »), ''Der Eigner'' (le propriétaire) et ''Der Einzige'' (l'Unique). Le chapitre central se subdivise à son tour en ''Meine Macht'' (ma puissance), ''Mein Verkehr'' (mes relations) et ''Mein Selbstgenuß'' (ma jouissance de moi).
La première partie s'ouvre sur un chapitre intitulé « Une vie d'homme » (''Ein Menschenleben''), dont l'importance ne doit pas être sous-estimée : il propose une typologie en trois âges (enfance, jeunesse, âge adulte) qui structure l'ensemble du livre et constitue un détournement explicite de l'analyse hégélienne des âges de la vie (''die Reihe der Lebensalter'') dans l'''Encyclopédie''.<ref>Spiessens consacre tout son chapitre III à la démonstration de cette appropriation : ''op. cit.'', « Stirner's relation to Hegel (reconsidered) », p. 109-162.</ref> L'enfant vit dans un rapport « réaliste » au monde : il est dominé par les choses, par la sévérité du père et la verge, jusqu'à ce qu'il découvre sa propre ruse et son intériorité. Le jeune homme, parvenu à la « découverte de l'esprit » (''Geist''), entre dans la phase « idéaliste » : il s'affranchit du monde extérieur, mais se trouve désormais asservi à ses propres idées, à ses convictions, à sa conscience morale. Seul l'homme adulte, l'égoïste, accède à la position où ni les choses ni les idées ne le dominent plus, et où il dispose souverainement des unes et des autres.
Le second chapitre, « Les anciens et les modernes » (''Menschen der alten und der neuen Zeit''), transpose ce schéma sur le plan de l'histoire universelle. Les Anciens, c'est-à-dire le monde antique préchrétien, Grecs et Romains confondus, correspondent à l'enfance de l'humanité : leur attitude est réaliste, ils sont en lutte avec un monde qui leur résiste. Le christianisme inaugure l'âge moderne et l'adolescence spirituelle de l'humanité : il découvre que le véritable ennemi n'est plus le monde extérieur mais l'intériorité, les passions, le « vieil Adam ». Le combat chrétien est un combat de l'esprit contre la chair. Les Modernes, c'est-à-dire les chrétiens et leurs héritiers, vivent dans le « monde des fantômes » (''Gespenster''), où des entités invisibles exercent sur l'individu une emprise plus absolue que ne le faisait la nature.
== Fantômes et idées fixes ==
Le concept de fantôme (''Spuk'' ou ''Gespenst'') et celui d'idée fixe (''fixe Idee'') sont au cœur de la critique stirnérienne. Le fantôme désigne toute entité abstraite (Dieu, l'Homme, la Liberté, la Justice, le Droit, la Morale, la Société, l'Humanité) érigée en puissance autonome et sacrée devant laquelle l'individu concret est sommé de se prosterner. Dieu fut le fantôme suprême de l'ère chrétienne, mais ce que la modernité athée appelle Raison, Esprit, Humanité, Société ou Histoire ne sont, aux yeux de Stirner, que des fantômes plus subtils qui jouent rigoureusement le même rôle. L'idée fixe désigne pour sa part l'idée dont l'individu est possédé sans le savoir, la conviction qui s'est emparée de lui au point qu'il s'identifie à elle. Le rapport authentique de l'individu à la pensée est inverse : il doit posséder ses idées au lieu d'en être possédé.
La thèse la plus singulière de Stirner consiste à montrer que la modernité philosophique, loin d'avoir achevé une émancipation à l'égard du christianisme, n'a fait qu'en poursuivre et approfondir le mouvement. La Réforme luthérienne, en intériorisant la religion, a renforcé l'emprise de l'esprit sur l'individu ; les Lumières ont remplacé Dieu par la Raison sans diminuer en rien son autorité ; l'idéalisme allemand a porté la spiritualisation du monde à son comble en faisant de l'Esprit le principe même de la réalité ; et lorsque Feuerbach proclame que le secret de la théologie est l'anthropologie, il substitue un fantôme, l'Homme générique, à un autre. L'humanisme moderne est une religion qui a changé d'objet sans changer de structure.
Stirner étend cette critique à l'ensemble du spectre politique de son temps en distinguant trois figures du libéralisme. Le libéralisme politique, issu de la Révolution française, proclame la liberté et l'égalité des citoyens, mais cette émancipation est purement formelle : en constituant l'État comme entité souveraine, il subordonne l'individu au citoyen, et le citoyen à l'État. Le libéralisme social, c'est-à-dire le socialisme et le communisme contemporains, prétend corriger les insuffisances du libéralisme politique en abolissant la propriété privée, mais ne fait que substituer la Société à l'État comme instance de domination : l'individu y est désormais soumis à la collectivité, et doit conformer son existence aux exigences du travail social. Le libéralisme humain ou humanitaire, celui de Feuerbach et de Bruno Bauer, entend émanciper l'homme en le débarrassant de toute particularité (religieuse, nationale, sociale) pour ne retenir que son humanité universelle. C'est, aux yeux de Stirner, la plus radicale des dépossessions : en exigeant de l'individu qu'il ne soit rien d'autre qu'un homme, un représentant de l'espèce, le libéralisme humain le dépouille de tout ce qui constitue sa singularité.<ref>Spiessens consacre son chapitre V au traitement de cette tripartition : ''op. cit.'', « Stirner's critique of Young Hegelian "Liberalism" », p. 239-288.</ref>
Parallèlement à la division Anciens / Modernes, Stirner introduit dans la première partie une seconde grille historique qui éclaire son rapport à Hegel : celle des trois âges correspondant aux trois « races » selon la classification anthropologique du paragraphe 393 de l'''Encyclopédie'' hégélienne. Stirner distingue ainsi un âge « nègre », un âge « mongol » et un âge « caucasien », et soutient que « l'histoire de l'humanité tient à proprement parler tout entière dans l'histoire de la race caucasique ».<ref>Stirner, ''L'Unique'', trad. Reclaire, p. 62 (§ 3 : « La Hiérarchie »).</ref> Ce passage reprend une hiérarchie racialiste héritée de la philosophie hégélienne de l'histoire ; il ne relève pas seulement d'un biologisme racial au sens strict (Stirner traite ces périodes comme des moments « spirituels » et non comme des déterminations biologiques fixes), mais il n'en demeure pas moins porteur d'une vision eurocentrée et explicitement racialisante. Stirner appose à cette grille hégélienne un second usage : il subdivise l'histoire européenne elle-même en trois moments, la « négritude » des Caucasiens (la dépendance à l'égard du monde sensible et naturel, identifiée à l'Antiquité), leur « mongolisme » (la soumission à la coutume et à l'esprit objectivé en lois et institutions, identifiée à l'ère chrétienne), et l'avènement de la propre subjectivité qui leur permettrait de dominer l'une et l'autre. Comme le montre Spiessens, Stirner se sert ici de la philosophie hégélienne de l'histoire pour en renverser le sens : ce que Hegel décrit comme la marche progressive de la conscience de la liberté, Stirner le relit comme l'asservissement croissant de l'individu à des abstractions de plus en plus impérieuses.<ref>Spiessens, ''op. cit.'', ch. IV, « Stirner's appropriation of Hegel's philosophy of history », p. 163-238, et particulièrement la section « "Negroidity" and "Mongoloidity" », p. 207-220.</ref>
== L'Unique ==
La seconde partie, intitulée simplement « Moi », développe la position propre de Stirner. Le concept central en est celui d'Unique (''der Einzige''). Mais l'Unique n'est pas un concept au sens traditionnel du terme. Stirner écrit que de Moi comme de Dieu « aucun concept ne m'exprime, rien de ce qu'on donne comme mon essence ne m'épuise, ce ne sont que des noms ».<ref>Stirner, ''L'Unique'', trad. Reclaire, p. 280 (chapitre « III. L'Unique »).</ref> L'Unique ne désigne ni une essence, ni un type d'individu, ni une propriété commune à tous les individus, mais l'individu concret en tant qu'il est irréductible à toute catégorie. Il est, selon la formule reprise du prologue, « le Rien créateur, le Rien dont je tire tout ».<ref>Stirner, ''L'Unique'', trad. Reclaire, p. 19.</ref> La formule fondatrice du livre, ''Ich hab' Mein' Sach' auf Nichts gestellt'' (« Je n'ai basé ma cause sur rien »), signifie ainsi que l'individu n'a fondé sa cause ni sur Dieu, ni sur l'Homme, ni sur la Vérité, ni sur aucune entité préalable, mais sur lui-même comme « rien » de toute essence assignable.
L'Unique se distingue donc radicalement de l'Homme de Feuerbach, du Sujet de l'idéalisme et du citoyen du libéralisme. Toutes ces figures sont des abstractions qui prétendent saisir ce que l'individu ''est'' en essence, et qui, ce faisant, le soumettent à une norme extérieure. Comme l'écrit Stirner dans la conclusion de l'œuvre, « je ne suis pas un "moi" auprès d'autres "moi" : je suis le seul Moi, je suis Unique ».<ref>Stirner, ''L'Unique'', trad. Reclaire, p. 280.</ref> Chaque individu est un Unique incomparable, non parce qu'il possèderait des qualités exceptionnelles, mais parce que son existence singulière excède toute détermination conceptuelle. L'Unique n'est ni bon ni mauvais, ni libre ni aliéné, ni égoïste ni altruiste au sens moral de ces termes : il est celui qui se pose comme son propre fondement et ne reconnaît au-dessus de lui aucune instance ayant le droit de le juger.
== Ipséité et liberté ==
La distinction entre liberté (''Freiheit'') et ipséité ou propriété de soi (''Eigenheit'', traduit par Reclaire par « la propriété ») constitue l'une des articulations majeures de l'œuvre. La liberté, telle que la revendiquent les mouvements politiques et philosophiques modernes, est une notion essentiellement négative : elle désigne l'absence de contraintes extérieures, l'affranchissement à l'égard d'un maître. Or, observe Stirner, la liberté ainsi comprise est une aspiration sans fin : une fois libéré d'un maître, on en découvre un autre ; en abolissant les anciennes formes de domination, on en instaure de nouvelles. Plus fondamentalement, la liberté est toujours conditionnée par ce dont elle prétend libérer : elle est ''Befreiung'', processus d'affranchissement, et reste donc dépendante de son adversaire.
L{{'}}''Eigenheit'', à l'inverse, ne désigne pas un état à atteindre mais une réalité à exercer. Le terme, difficilement traduisible (« ipséité », « particularité », « propriété de soi »), renvoie à la fois à l'idée de ce qui est propre (''eigen''), à la possession de soi et à la singularité irréductible. Alors que la liberté est toujours dépendante de conditions extérieures, l'''Eigenheit'' ne dépend que de l'individu lui-même. Elle n'est pas un idéal à poursuivre mais une puissance à déployer. Stirner reformule ainsi la question fondamentale : ce n'est plus « suis-je libre ? », mais « suis-je propriétaire de moi-même ? ». Cette propriété de soi ne se conquiert pas par des révolutions politiques ou des émancipations sociales, mais par une reprise pratique de soi, un acte de désassujettissement qui modifie aussi bien l'attitude intérieure que la conduite envers l'État, le droit, les institutions et les engagements.
== Insurrection et révolution ==
La distinction entre la révolution (''Revolution'') et l'insurrection (''Empörung'', rendu par Reclaire par « insurrection » et parfois traduit ailleurs par « révolte ») est indissociable de la précédente. Stirner la formule lui-même en termes nets : « Révolution et insurrection ne sont pas synonymes. La première consiste en un bouleversement de l'ordre établi, du ''status'' de l'État ou de la Société, elle n'a donc qu'une portée politique ou sociale. La seconde entraîne bien comme conséquence inévitable le même renversement des institutions établies, mais là n'est point son but ; elle ne procède que du mécontentement des hommes ; elle n'est pas une levée de boucliers, mais l'acte d'individus qui s'élèvent, qui se redressent. » Et plus loin : « La révolution avait en vue un régime nouveau ; l'insurrection nous mène à ne plus nous laisser régir, mais à nous régir nous-mêmes. […] La révolution ordonne d'instituer, d'instaurer ; l'insurrection veut qu'on se soulève ou qu'on s'élève ».<ref>Stirner, ''L'Unique'', trad. Reclaire, p. 250-251.</ref>
La portée de cette opposition dépasse la simple stratégie politique. Toute révolution, en tant qu'elle vise à fonder un nouvel ordre, reste prisonnière de la logique de la domination : elle remplace une autorité par une autre, un fantôme par un autre. La Révolution française a aboli la monarchie pour instaurer la souveraineté du peuple et la puissance de l'État ; les révolutions socialistes projettent d'abolir la propriété privée pour instaurer la propriété collective, mais l'individu, dans les deux cas, demeure subordonné à une instance qui le dépasse. L'insurrection, au contraire, n'a pas pour objet la transformation de l'ordre existant mais l'affirmation de l'individu contre l'ordre, quel qu'il soit. Elle n'est pas un soulèvement collectif au nom d'un idéal supérieur, mais l'acte par lequel l'individu cesse de reconnaître toute autorité au-dessus de lui.
== La propriété ==
Le concept stirnérien de propriété (''Eigentum'') ne se confond ni avec la notion juridique de propriété privée que défendent les libéraux, ni avec celle que critiquent les socialistes. Il prolonge l'''Eigenheit'' : la propriété, pour Stirner, est tout ce dont l'individu peut s'emparer par sa propre puissance. « Ce dont je puis m'emparer, je le saisis et je me l'approprie ; ce qui m'échappe, je n'y ai aucun droit, et ce ne sont pas mes droits imprescriptibles dont je m'enorgueillis ou qui me consolent ».<ref>Stirner, ''L'Unique'', trad. Reclaire, p. 175-176.</ref> Stirner récuse simultanément la propriété garantie par le droit, qui n'est qu'un fantôme juridique, et la critique socialiste de la propriété privée, qui se borne à transférer cette même fonction à la « société », nouvelle entité sacrée. « Ma propriété, écrit-il, est ce qui est en mon pouvoir. »
Cette conception déborde le domaine économique. Les pensées elles-mêmes peuvent devenir la propriété de l'individu, ou, et c'est la condition de l'aliéné, l'individu peut être possédé par ses pensées. L'homme religieux est possédé par l'idée de Dieu ; l'homme moral, par l'idée du Bien ; le patriote, par celle de la nation. À chaque fois, une idée s'est emparée de l'individu au lieu que l'individu se soit emparé de l'idée. L'Unique, à l'inverse, possède ses idées au lieu d'en être possédé, et les utilise comme des instruments au service de sa jouissance et de sa puissance. La pensée cesse d'être une vocation sacrée pour devenir un outil.
== La critique de la morale et du sacré ==
La critique stirnérienne de la morale constitue l'un des aspects centraux de l'œuvre. Stirner refuse en bloc le vocabulaire moral (devoir, obligation, vertu, droit, péché, sacré), qu'il considère comme autant de vestiges théologiques. La pensée morale, qu'elle soit d'inspiration religieuse, kantienne ou humaniste, repose toujours sur la même structure : elle pose une norme extérieure à l'individu (commandement divin, impératif catégorique, exigence de l'essence humaine) et somme l'individu de s'y conformer. Tout « tu dois » (''du sollst'') est une injonction sacrée qui prétend exercer sur l'individu une autorité que celui-ci n'a pas reconnue.
À la morale, Stirner oppose ce qu'il nomme l'égoïsme conscient. L'égoïsme stirnérien n'a toutefois guère à voir avec l'égoïsme vulgaire, la simple poursuite de l'intérêt matériel. L'égoïste, au sens de Stirner, n'est pas nécessairement celui qui recherche la richesse ou le plaisir au sens trivial : c'est celui qui ne reconnaît d'autre mesure que lui-même, qui fait de sa jouissance et de sa puissance propres le seul critère de ses actes. L'égoïste peut être généreux, compatissant, solidaire, non parce qu'il le « doit », mais parce qu'il le « veut », parce que cela lui plaît, parce que cela sert sa jouissance. La différence entre la morale et l'égoïsme ne tient pas au contenu des actes mais à leur fondement : dans le premier cas, l'individu agit par devoir, soumis à une norme extérieure ; dans le second, il agit par jouissance de soi (''Selbstgenuß''), en tant que propriétaire de ses actes. Comme l'observe Saul Newman, l'égoïsme stirnérien doit être pensé moins comme une doctrine de l'intérêt que comme une forme d'autonomie et de maîtrise de soi.<ref>Saul Newman (dir.), ''Max Stirner'', Basingstoke, Palgrave Macmillan, 2011, introduction, p. 1-5.</ref>
La position de Stirner à l'égard de la religion excède l'athéisme classique. Les jeunes hégéliens se sont employés à démontrer que Dieu n'existe pas, que la religion est une illusion, que le sacré doit être dissous dans le profane. Stirner partage ce diagnostic mais le juge insuffisant : la critique athée de la religion reste prisonnière du sacré dans la mesure où elle érige de nouveaux absolus (la Raison, l'Homme, la Science, la Société) qui jouent le même rôle fonctionnel que l'ancien Dieu. L'athéisme feuerbachien est une théologie inversée ; l'humanisme, une religion qui a changé d'objet sans changer de structure. C'est pourquoi Stirner s'attaque non seulement à Dieu mais au sacré comme tel, à tout ce qui prétend être supérieur à l'individu et qui exige de lui respect, vénération ou sacrifice. La critique feuerbachienne est, selon une formule fameuse de Stirner, une simple « insurrection théologique », un soulèvement contre l'ancien dieu pour mieux installer le nouveau.
== L'État, le droit et l'union des égoïstes ==
La critique stirnérienne de l'État est indissociable de sa critique du droit. L'État, quelle que soit sa forme (monarchique, démocratique, socialiste), est toujours une puissance qui se pose au-dessus des individus et leur impose sa volonté par la force et par la loi. Le droit (''Recht'') n'est, aux yeux de Stirner, qu'une volonté étrangère à laquelle l'individu est sommé de se soumettre. Droit naturel, droits de l'homme, droit positif : autant de formes d'une même mystification qui prétendent que certaines normes s'imposent à l'individu indépendamment de sa volonté et de sa puissance. Pour Stirner, le seul fondement réel d'un pouvoir est la puissance effective de celui qui l'exerce : « la force, c'est moi-même, moi qui suis puissant, qui suis possesseur de la puissance ».<ref>Stirner, ''L'Unique'', trad. Reclaire, p. 176.</ref> Le droit n'est que la puissance des autres (État, société, classe dominante) érigée en norme sacrée et présentée comme légitime.
Cette analyse conduit Stirner à rejeter toute forme d'organisation politique fondée sur le droit. Il récuse également l'État libéral, qui garantit les libertés formelles au prix de la soumission à la loi, et l'État socialiste, qui projette de garantir l'égalité matérielle au prix de la soumission à la société. Plus profondément, il récuse l'idée même de société (''Gesellschaft'') en tant qu'entité douée d'une réalité propre et d'une autorité sur les individus. La société, telle que la conçoivent les penseurs politiques modernes, est un nouveau fantôme : elle n'existe pas indépendamment des individus qui la composent, et pourtant elle prétend exercer sur eux une souveraineté.
À toutes ces formes d'association, Stirner oppose ce qu'il appelle l'union des égoïstes (''Verein von Egoisten''). L'union se distingue radicalement de la société et de l'État en ce qu'elle ne repose sur aucun principe, aucune finalité, aucun contrat qui lui donneraient une existence indépendante de ses membres. Reclaire, dans une note du traducteur, souligne que ''Verein'' implique une coopération volontaire et active, là où ''Gesellschaft'' suppose une réunion passive imposée à ses membres.<ref>Reclaire, note du traducteur dans Stirner, ''L'Unique et sa propriété'', éd. cit., p. 277.</ref> L'union est un rapport librement consenti entre individus qui s'associent pour servir leurs intérêts propres, et qui peuvent à tout moment rompre cette association dès qu'elle cesse de leur convenir. Elle n'a pas de constitution, pas de lois, pas de droits ; elle ne possède pas ses membres, ce sont ses membres qui la possèdent. Tandis que dans la société, l'individu est un moyen au service du tout, dans l'union, le tout est un moyen au service des individus. Stirner précise que l'individu doit toujours être « plus que » l'union : si celle-ci venait à le subsumer, elle deviendrait à son tour un fantôme.
L'union des égoïstes ne se réduit pas à un programme politique au sens habituel. Stirner ne propose pas un modèle d'organisation alternative destiné à remplacer les institutions existantes. L'union est d'abord la description d'un mode de rapport entre Uniques : un rapport de puissance et de jouissance, fondé non sur le droit ou le devoir mais sur l'intérêt et le désir. L'Unique entre en relation avec d'autres Uniques non parce qu'il y est obligé par un contrat ou un impératif, mais parce que cette relation augmente sa jouissance. Dès qu'elle cesse de la servir, il s'en retire. L'union est donc par nature transitoire, instable, révocable, à l'opposé de la société, qui se veut permanente et obligatoire. Marx, dans ''L'Idéologie allemande'', verra dans cette association un simple « décalque idéal » de la société civile bourgeoise, interprétation que les commentateurs ultérieurs ont souvent jugée réductrice, l'union stirnérienne ne se laissant pas réduire à la fiction contractualiste libérale.<ref>Voir la discussion de Welsh, ''op. cit.'', p. 121-145, et celle de Newman, ''Max Stirner'', p. 51-72.</ref>
== Corporéité et jouissance de soi ==
L'insistance de Stirner sur la corporéité et la matérialité de l'individu joue un rôle important dans sa polémique contre les abstractions idéalistes. Stirner ramène inlassablement l'individu à son existence concrète, mortelle et sensible, contre les essences que la tradition prétend lui assigner. Il ne construit pas pour autant une philosophie systématique du corps, comparable à celles que développeront Feuerbach, Nietzsche ou Merleau-Ponty ; le corps fonctionne plutôt chez lui comme un argument polémique, comme la pierre de touche concrète à laquelle se brisent les universaux moraux et métaphysiques. C'est en ce sens qu'il écrit : « Quand le monde se trouve sur mon chemin (et il s'y trouve toujours), je le consomme pour apaiser la faim de mon égoïsme ».<ref>Stirner, ''L'Unique'', trad. Reclaire, p. 233.</ref> La métaphore alimentaire revient à plusieurs reprises : le rapport de l'individu au monde est un rapport de consommation, d'appropriation et de jouissance, et non un rapport de connaissance contemplative ou de communion spirituelle.
Cette articulation entre corporéité, consommation et jouissance de soi (''Selbstgenuß'') est centrale pour la cohérence de l'œuvre. La jouissance, chez Stirner, n'est ni un état contemplatif ni un plaisir hédoniste au sens vulgaire : elle est l'exercice actif d'une puissance qui s'éprouve dans l'appropriation, le mouvement par lequel l'individu se déploie en s'appropriant les choses, les pensées et les rapports qui lui sont accessibles. Là où la tradition idéaliste, depuis Platon jusqu'à l'idéalisme allemand, fait de la pensée un détachement à l'égard du sensible et de l'attitude contemplative la forme la plus haute du rapport au monde, Stirner inverse l'évaluation : la pensée n'a de valeur que comme instrument de l'individu corporel qui s'en sert, et le monde n'a de réalité que comme matière à consommer. Cette articulation prépare la dernière section de l'œuvre, ''Mein Selbstgenuß'', où l'égoïste se présente comme « celui qui se consume lui-même » en se déployant, sans se réserver pour une cause supérieure qui le justifierait par avance.
== Réception et postérité ==
Après le bref éclat des années 1844-1846, ''L'Unique et sa propriété'' tombe dans un oubli presque total. L'échec de la Révolution de 1848, à laquelle Stirner ne prit aucune part et dont il avait par avance condamné les tendances libérales et sociales, emporte avec lui les discussions des jeunes hégéliens : les idées contre lesquelles Stirner avait lutté ayant elles-mêmes sombré, l'intérêt pour son livre, essentiellement polémique, s'évanouit. Il n'en demeure que de rares mentions chez Erdmann (1866), Lange (''Histoire du matérialisme'', 1866) et Eduard von Hartmann.
La redécouverte du livre s'amorce en 1888 : le poète John Henry Mackay, Allemand malgré son nom, est intrigué par le titre de l'ouvrage mentionné par Lange. Il consacre dix années à reconstituer la vie de Stirner, lance des appels à témoins dans la presse, et publie en 1898 une biographie qui demeure la source principale de toutes les biographies ultérieures : ''Max Stirner. Sein Leben und sein Werk''.<ref>Arvon, ''op. cit.'', p. 3-5, retrace les étapes de cette redécouverte.</ref> L'épisode du 28 mars 1892, où le chef d'orchestre Hans von Bülow, après une exécution de la Neuvième de Beethoven, prononça publiquement le nom de Stirner en l'associant à celui de Bismarck, contribue à la diffusion du nom dans le grand public.<ref>L'épisode est relaté en détail par Arvon, ''op. cit.'', p. 4-5.</ref>
L'essor parallèle de la philosophie de Nietzsche favorise un rapprochement qui sera durablement contesté. Les études comparatives entre ''L'Unique'' et ''Ainsi parlait Zarathoustra'' se multiplient à la fin du XIXe siècle, mais l'évidence d'une influence directe fait défaut. Selon un témoignage tardif et indirect, Nietzsche aurait recommandé en 1874 la lecture de Stirner à son élève Adolf Baumgartner ; il en aurait probablement appris le nom dans Lange. Mais Nietzsche ne cite Stirner ni dans ses écrits ni dans sa correspondance, et la liste des emprunts qu'il fait à la bibliothèque de Bâle entre 1869 et 1879 ne mentionne pas l'ouvrage. Comme le conclut Arvon, « nous n'avons pas de document qui permette d'affirmer que Stirner ait eu une influence sur Nietzsche ».<ref>Arvon, ''op. cit.'', p. 5-6, citant la conclusion d'A. Lévy dans son étude sur Stirner et Nietzsche.</ref>
La postérité stirnérienne se déploie selon plusieurs lignées. L'anarchisme individualiste, notamment dans sa version américaine avec Benjamin Tucker, qui édita la première traduction anglaise du livre en 1907 sous le titre ''The Ego and His Own'' et le qualifia de « plus importante œuvre littéraire qu'il eût jamais éditée », revendiqua Stirner comme un précurseur, bien que celui-ci n'eût jamais accepté le qualificatif d'anarchiste et que sa pensée excède les cadres de tout programme politique.<ref>Voir l'analyse détaillée de Paterson, ''op. cit.'', ch. VI, « Stirner and the Anarchists », p. 126-143.</ref> Paterson souligne la distance qui sépare l'égoïsme sans principe de Stirner du contractualisme moralisé de Tucker. Les rapports avec les grandes figures de l'anarchisme historique (Proudhon, Bakounine, Kropotkine, Tolstoï) sont au mieux marginaux : Stirner ne partage avec eux que l'opposition à l'État, mais son individualisme égoïste demeure étranger à leurs préoccupations sociales et éthiques.
Au XXe siècle, la pensée stirnérienne nourrit des relectures variées. Henri Arvon, dans ''Aux sources de l'existentialisme : Max Stirner'' (1954), propose une généalogie qui fait de Stirner un précurseur de l'existentialisme français : le primat de l'existence sur l'essence, le refus de toute nature humaine prédéterminée, la centralité de l'insurrection rapprochent Stirner de certains motifs sartriens et camusiens, même si la filiation directe demeure incertaine. Certains lecteurs situationnistes ou post-anarchistes ont pu, par la suite, réactiver des motifs stirnériens : Saul Newman, en particulier, voit en Stirner un précurseur de la critique foucaldienne du pouvoir et de la subjectivation.<ref>Newman, ''Max Stirner'', ch. 8, « Critique of Subjectivity and the Politics of Autonomy », p. 167-188.</ref> Les études récentes (Stepelevich, Welsh, Spiessens) tendent au contraire à réinscrire Stirner dans l'héritage hégélien dont il est issu, montrant que ses concepts (l'Unique, l'''Eigenheit'', l'insurrection, l'union) ne prennent leur sens véritable que rapportés à la dialectique qu'ils mettent en œuvre tout en la subvertissant.
Resté longtemps marginalisé dans l'histoire officielle de la philosophie, et souvent réduit à l'image caricaturale qu'en donnent Marx et Engels dans ''L'Idéologie allemande'', Max Stirner apparaît aujourd'hui comme l'un des points d'aboutissement de la dissolution de l'hégélianisme : celui qui pousse la critique de l'aliénation jusqu'à son terme en refusant que quelque essence que ce soit, fût-elle l'essence humaine, puisse fonder l'individu. Sa pensée demeure une provocation contre toute forme de sacralisation, qu'il s'agisse de Dieu, de l'Homme, de la Société, du Marché ou de quelque autre instance qui prétend se poser au-dessus de l'individu concret pour exiger de lui respect, obéissance ou sacrifice.
== Notes et références ==
{{references|colonnes=2}}
== Bibliographie ==
=== Sources primaires ===
* Stirner, Max. ''Der Einzige und sein Eigentum''. Leipzig : Otto Wigand, 1845 [paru en novembre 1844]. — Édition originale.
* Stirner, Max. ''Der Einzige und sein Eigentum. Ausführlich kommentierte Studienausgabe''. Éd. Bernd Kast. Fribourg-en-Brisgau / Munich : Karl Alber, 2009. — Édition scientifique de référence en allemand, avec apparat critique exhaustif ; constitue le texte de travail des études contemporaines.
* Stirner, Max. ''Kleinere Schriften und seine Entgegnungen auf die Kritik seines Werkes "Der Einzige und sein Eigentum" aus den Jahren 1842-1848''. Éd. John Henry Mackay. 2e éd. augmentée, Berlin, 1914. Réimpression : Stuttgart-Bad Cannstatt, Frommann-Holzboog, 1976. — Recueil de référence rassemblant les articles préparatoires (recensions de Bauer, Sue, essais sur l'éducation et la religion) et les réponses aux critiques (''Rezensenten Stirners'', 1845 ; ''Die philosophischen Reactionäre'', 1847).
* Stirner, Max. ''L'Unique et sa propriété''. Trad. R. L. Reclaire. Paris : P.-V. Stock, 1899 (Bibliothèque sociologique, n° 23). — Première traduction française intégrale, demeurée longtemps la seule disponible ; les citations françaises usuelles s'y rapportent encore aujourd'hui.
* Stirner, Max. ''L'Unique et sa propriété''. Trad. Henri Lasvignes. Paris : Stock, 1900 (rééd. La Table Ronde, coll. « La Petite Vermillon »). — Traduction concurrente, parfois préférée pour sa fluidité.
* Stirner, Max. ''The Ego and Its Own''. Trad. Steven T. Byington (1907), éd. David Leopold. Cambridge : Cambridge University Press, coll. « Cambridge Texts in the History of Political Thought », 1995. — Édition anglaise de référence, avec introduction et notes critiques de David Leopold.
=== Critiques contemporaines de Stirner ===
* Marx, Karl & Engels, Friedrich. ''Die deutsche Ideologie'' [1845-1846, publié en 1932], dans ''Marx-Engels Werke'' (MEW), Bd. 3. Berlin : Dietz Verlag, 1958. — Trad. fr. : ''L'Idéologie allemande''. La section centrale, « Saint Max », occupe près des trois quarts du volume et constitue la première critique systématique de ''L'Unique''.
* Feuerbach, Ludwig. « Über das "Wesen des Christentums" in Beziehung auf den "Einzigen und sein Eigentum" ». ''Wigands Vierteljahrsschrift'', 1845.
* Hess, Moses. ''Die letzten Philosophen''. Darmstadt, 1845.
* Szeliga [Franz Zychlin von Zychlinski]. ''Der Einzige und sein Eigentum von Max Stirner''. Recension parue dans les ''Norddeutsche Blätter'', 1844.
=== Littérature secondaire ===
* Mackay, John Henry. ''Max Stirner. Sein Leben und sein Werk''. Berlin, 1898 ; 2e éd. 1910 ; 3e éd. Leipzig, 1914. — Biographie fondatrice de toutes les études stirnériennes ; demeure la source documentaire principale, malgré son ton apologétique.
* Arvon, Henri. ''Aux sources de l'existentialisme : Max Stirner''. Paris : PUF, coll. « Épiméthée », 1954. — Grande étude française classique. Présente Stirner comme précurseur de l'existentialisme tout en réfutant prudemment la thèse d'une influence directe sur Nietzsche.
* Paterson, R. W. K. ''The Nihilistic Egoist Max Stirner''. Oxford : Oxford University Press, 1971. — La principale monographie anglophone du XXe siècle. Combine biographie, analyse philosophique du livre et examen détaillé de la postérité, notamment anarchiste et existentialiste.
* Welsh, John F. ''Max Stirner's Dialectical Egoism: A New Interpretation''. Lanham : Lexington Books, 2010. — Lecture systématique de Stirner comme penseur dialectique de la modernité, en discussion avec la lignée individualiste américaine (Tucker, Rand).
* Newman, Saul (dir.). ''Max Stirner''. Basingstoke : Palgrave Macmillan, coll. « Critical Explorations in Contemporary Political Thought », 2011. — Volume collectif rassemblant des contributions de Newman, Widukind De Ridder, Paul Thomas et d'autres, dans la perspective du post-anarchisme et de la pensée politique contemporaine.
* Spiessens, Jeff. ''The Radicalism of Departure. A reassessment of the Hegelianism in Max Stirner's "Der Einzige und sein Eigentum"''. Newcastle upon Tyne : Cambridge Scholars Publishing, 2018. — Étude la plus aboutie du rapport de Stirner à l'hégélianisme. Soutient que ''L'Unique'' met en scène un « départ » de la philosophie hégélienne par les moyens mêmes de la dialectique hégélienne, dont Stirner s'approprie le langage et la logique.
* Stepelevich, Lawrence S. ''Max Stirner on the Path of Doubt''. Lanham : Lexington Books, coll. « Continental Philosophy and the History of Thought », 2020. — Défense de la thèse selon laquelle Stirner serait l'« héritier légitime » de Hegel, occupant le point de clôture du grand cercle dialectique. Inclut en annexe la traduction commentée de plusieurs ''Kleinere Schriften''.
* Laska, Bernd A. ''Ein dauerhafter Dissident. Eine Wirkungsgeschichte des "Einzigen"''. Nuremberg : LSR-Verlag, 1996. — Histoire de la réception de Stirner, particulièrement attentive aux silences et refoulements de la tradition marxiste.
* Kast, Bernd. ''Die Thematik des "Eigners" in der Philosophie Max Stirners''. Bonn : Bouvier, 1979. — Étude allemande systématique sur la notion de propriété chez Stirner ; complète l'édition critique de 2009.
{{DEFAULTSORT:Stirner}}
[[Catégorie:Philosophe]]
b9z3uji9vzhoae4hf2m0xswxd6adqdj
Dictionnaire de philosophie/Scepticisme
0
83927
768696
767501
2026-06-26T04:33:21Z
PandaMystique
119061
Bot : ajout du paramètre lecture=oui au modèle {{DicoPhilo}}
768696
wikitext
text/x-wiki
{{DicoPhilo|Scepticisme|lecture=oui}}
Le scepticisme désigne, au sens philosophique, l'attitude de l'esprit qui, faute d'un critère assuré permettant de trancher entre des thèses opposées, suspend son jugement. Le mot vient du grec ''skepsis'' (σκέψις), qui signifie examen, recherche : le sceptique n'est pas, à l'origine, celui qui nie, mais celui qui cherche encore. Cette nuance est capitale. Dans l'usage courant, « sceptique » qualifie celui qui refuse de croire ; dans la tradition philosophique, il qualifie celui qui refuse de conclure. Entre les deux, toute la différence d'une doctrine négative et d'une méthode d'enquête.
Né en Grèce au IV{{e}} siècle av. J.-C. avec Pyrrhon d'Élis, développé par l'Académie d'Arcésilas et de Carnéade, systématisé par Énésidème et Agrippa, recueilli enfin par Sextus Empiricus dont les œuvres nous le transmettent, le scepticisme antique se présente comme un art de vivre autant que comme une critique de la connaissance. Redécouvert à la Renaissance, il alimente la pensée de Montaigne, provoque le doute méthodique de Descartes, nourrit l'empirisme de Hume et oblige Kant à repenser les fondements du savoir. Aujourd'hui encore, les expériences de pensée qui peuplent l'épistémologie contemporaine, du malin génie au cerveau dans une cuve, prolongent les questions que les Grecs avaient posées. Peu de courants philosophiques auront eu une si longue postérité pour une doctrine qui, à strictement parler, refuse d'en être une.
== Le doute comme expérience première ==
Commençons par une expérience banale. Une tour, vue de loin, paraît ronde ; de près, elle se révèle carrée. Une rame plongée dans l'eau semble brisée ; retirée, elle est droite. Le même vin paraît doux au palais sain, amer au malade. Qui a raison ? Nous répondons spontanément : celui qui voit de près, celui qui regarde la rame hors de l'eau, celui dont les organes fonctionnent normalement. Mais cette réponse suppose un critère qui permette de départager les apparences, et c'est précisément ce critère que le sceptique nous demande de produire. De quel droit décrétons-nous que la perception de l'homme sain est plus fidèle que celle du malade, sinon parce qu'elle est la nôtre, ou celle du plus grand nombre ? La majorité fait-elle la vérité ?
L'expérience du désaccord prolonge celle de l'illusion. Sur la nature du monde, sur les dieux, sur le bien et le mal, les philosophes se contredisent depuis toujours : les uns affirment que tout est matière, les autres que tout est esprit ; les uns placent le bonheur dans le plaisir, les autres dans la vertu. Chaque école produit des arguments, et les arguments s'équilibrent. Les coutumes des peuples ne s'accordent pas davantage : ce qui est pieux ici est sacrilège ailleurs, ce qui est honteux chez les uns est honorable chez les autres. Devant cette ''isosthénie'' (ἰσοσθένεια), terme technique qui désigne l'égale force des raisons opposées, que faire ? Trancher arbitrairement serait déraisonnable. Reste à suspendre le jugement : les Grecs nomment ''épochè'' (ἐποχή) cette suspension, cet arrêt, cette mise entre parenthèses de l'assentiment.
Le scepticisme antique ajoute à ce diagnostic une promesse qui nous surprend : la suspension du jugement procure la tranquillité de l'âme, cette ''ataraxie'' (ἀταραξία) qui signifie littéralement l'absence de trouble. Celui qui croit savoir que la richesse est un bien se tourmente pour l'acquérir et tremble de la perdre ; celui qui suspend son jugement sur la valeur des choses cesse de se tourmenter. Le doute, loin d'être une inquiétude, devient un remède à l'inquiétude. Cette alliance du doute et de la sérénité, si étrangère à notre image moderne du sceptique tourmenté, constitue le cœur du scepticisme grec.
== Pyrrhon : vivre dans l'apparence ==
Pyrrhon d'Élis (v. 360-v. 270 av. J.-C.) n'a rien écrit. Nous le connaissons par son disciple Timon de Phlionte, par les anecdotes de Diogène Laërce et par un témoignage majeur d'Aristoclès, conservé chez Eusèbe<ref>Aristoclès de Messène, cité par Eusèbe de Césarée, ''La Préparation évangélique'', XIV, 18, 1-5, trad. Édouard des Places, Paris, Éditions du Cerf, 1987.</ref>. Peintre dans sa jeunesse, il accompagna Alexandre jusqu'en Inde, où il aurait rencontré des « gymnosophistes », sages nus dont l'impassibilité l'aurait marqué. De retour à Élis, il y mena une vie simple et respectée, au point que ses concitoyens, dit-on, exemptèrent d'impôts les philosophes en son honneur<ref>Diogène Laërce, ''Vies et doctrines des philosophes illustres'', IX, 61-64, trad. sous la direction de Marie-Odile Goulet-Cazé, Paris, Le Livre de Poche, coll. « La Pochothèque », 1999.</ref>.
Selon le résumé d'Aristoclès, Pyrrhon enseignait que les choses sont indifférenciées, sans stabilité et indécidables, de sorte que ni nos sensations ni nos opinions ne disent le vrai ou le faux. Il faut donc dire de chaque chose qu'elle n'est pas plus qu'elle n'est pas, ou qu'elle est et n'est pas, ou qu'elle n'est ni n'est pas. Cette formule du ''ou mallon'' (οὐ μᾶλλον), « pas plus ceci que cela », est la clé du pyrrhonisme : le miel n'est pas plus doux qu'il n'est pas doux ; le feu n'est pas plus brûlant par nature qu'il ne l'est pas. Autrement dit, nous ne pouvons pas dire ce que sont le miel ou le feu en eux-mêmes, mais seulement comment ils nous apparaissent. Marcel Conche a montré la portée de cette formule : si rien n'est plus ceci que cela, la notion même d'être se dissout, et avec elle l'opposition de l'être et de l'apparence<ref>Marcel Conche, ''Pyrrhon ou l'apparence'', Paris, Presses Universitaires de France, 1994.</ref>. Il ne reste que des apparences, qui ne sont plus apparences de quelque chose ni pour quelqu'un : un pur glissement de phénomènes que rien ne fonde et que rien ne contredit.
Arrêtons-nous sur la portée de cette lecture. Pour le sens commun, une apparence est toujours apparence de quelque chose : la tour paraît ronde, mais elle est carrée, et l'apparence se mesure à un être qui la corrige. Pyrrhon, tel que le lit Conche, supprime le terme de comparaison : il n'y a pas de tour en soi derrière la tour vue, pas de fond derrière la surface, et l'apparence cesse d'être trompeuse faute d'une vérité qu'elle pourrait trahir. Cette lecture, qui fait de Pyrrhon un métaphysicien de l'apparence plutôt qu'un sceptique au sens strict, ne fait pas l'unanimité des historiens. Richard Bett soutient lui aussi que le témoignage d'Aristoclès énonce une thèse sur les choses mêmes (elles sont réellement indéterminées), mais il y voit un dogmatisme négatif que les pyrrhoniens postérieurs auront à corriger pour devenir de purs sceptiques<ref>Richard Bett, ''Pyrrho, his Antecedents, and his Legacy'', Oxford, Oxford University Press, 2000.</ref>. D'autres interprètes comprennent au contraire la phrase comme un constat sur nos facultés : non pas « les choses sont indéterminées », mais « nous ne pouvons pas les déterminer ». Brochard, enfin, tenait le fond du pyrrhonisme pour moral : les arguments n'y seraient que l'instrument d'une délivrance, et c'est la figure du sage, plus que la théorie, qui a fait école. Le débat reste ouvert, et il importe au lecteur de savoir qu'il l'est : sur un philosophe qui n'écrivit rien, toute interprétation reconstruit.
De cette dissolution suivent trois fruits, que la tradition résume en trois mots. ''Aphasie'' (ἀφασία), d'abord : non pas le mutisme, mais le renoncement à dire l'être, à prononcer des phrases qui prétendent énoncer ce que les choses sont en elles-mêmes. ''Ataraxie'', ensuite : les apparences n'étant plus converties en thèses rivales sur l'être des choses, rien ne nous trouble plus dès lors que nous cessons de les durcir en êtres. ''Apathie'' (ἀπάθεια) enfin, ou impassibilité : c'est nous qui créons nos tourments en affirmant que telle chose est bonne, telle autre mauvaise, telle autre à craindre. Timon résume la conduite du sage en une phrase restée célèbre : « Que le miel soit doux, je ne l'affirme pas ; qu'il le paraisse, j'en conviens »<ref>Diogène Laërce, ''Vies et doctrines des philosophes illustres'', IX, 105, éd. citée ; nous traduisons.</ref>.
Les anecdotes que rapporte Diogène Laërce mettent en scène cette sagesse avec un humour qui fait partie de la leçon. Sur un navire pris dans la tempête, tandis que les passagers s'affolent, Pyrrhon montre un petit cochon qui continue tranquillement de manger : voilà, dit-il, l'ataraxie que le sage doit conquérir par la raison, et que l'animal possède sans effort, faute d'avoir un rapport à l'être. Anaxarque, son maître, étant tombé dans une mare, Pyrrhon passe son chemin sans lui porter secours ; on l'en blâme, mais Anaxarque lui-même le félicite de son indifférence. Faut-il prendre ces récits à la lettre, comme ceux qui montrent Pyrrhon manquant de tomber dans les précipices faute de se fier à ses sens, ses amis le retenant à chaque pas ? Les historiens en doutent : la légende force le trait pour rendre visible une attitude. Reste que le pyrrhonisme originel est moins une théorie de la connaissance qu'une éthique : un exercice de détachement à l'égard de tout ce que nous tenons pour réel et important.
== L'Académie sceptique : Arcésilas et Carnéade ==
:<small>''Voir aussi :'' [[Dictionnaire de philosophie/Carnéade|Carnéade]]</small>
Le scepticisme connaît un second foyer, inattendu : l'Académie, l'école même de Platon. Vers 268 av. J.-C., Arcésilas de Pitane (v. 316-v. 241 av. J.-C.) en prend la direction et lui imprime un tournant qui durera deux siècles. Comment l'héritier de Platon en est-il venu à professer le doute ? Arcésilas se réclamait de Socrate, ce questionneur qui réfutait toutes les thèses sans en soutenir aucune, et qui avouait ne rien savoir. La méthode des dialogues aporétiques, où chaque définition proposée s'effondre sous l'examen, pouvait passer pour la vraie leçon du platonisme. Certains anciens soupçonnèrent Arcésilas de dissimuler un dogmatisme secret, réservé aux initiés ; Victor Brochard, dans son étude classique, a montré le peu de crédit que méritent ces conjectures<ref>Victor Brochard, ''Les Sceptiques grecs'', Paris, Imprimerie nationale, 1887 ; rééd. Paris, Le Livre de Poche, 2002.</ref>. Notre principale source sur cette période est Cicéron, qui suivit à Rome l'enseignement de Philon de Larisse et exposa les thèses de l'école dans ses ''Académiques'' : sans lui, l'Académie sceptique nous serait à peu près inconnue.
L'adversaire d'Arcésilas est le stoïcisme naissant. Zénon et ses disciples fondent toute connaissance sur la « représentation compréhensive » (''phantasia katalêptikê'') : une impression si claire, si distincte, si fidèlement imprimée par son objet qu'elle ne pourrait provenir d'un objet qui ne serait pas tel qu'elle le représente. À cette impression privilégiée, le sage donne son assentiment ; aux autres, il le refuse. Arcésilas attaque le point faible du dispositif : il n'existe aucune représentation vraie dont on ne puisse trouver une copie fausse rigoureusement indiscernable. Le rêve imite la veille, la folie imite la raison, les jumeaux se confondent. Si aucune marque interne ne distingue l'impression fidèle de l'impression trompeuse, le critère stoïcien s'effondre, et le sage, pour ne jamais opiner à faux, devra suspendre son assentiment sur tout.
Mais alors, comment vivre ? L'objection se présente aussitôt, et elle accompagnera le scepticisme pendant toute son histoire : qui ne croit rien ne peut rien faire, car toute action suppose qu'on tienne quelque chose pour vrai. Arcésilas répond en proposant un critère pratique : le « raisonnable » (''eulogon''), c'est-à-dire ce que l'on peut justifier par de bonnes raisons cohérentes entre elles. Le sage agira selon le raisonnable, sans affirmer pour autant que ses raisons atteignent le vrai.
Carnéade de Cyrène (v. 214-v. 129 av. J.-C.)<ref>Les sources anciennes hésitent sur sa longévité : Diogène Laërce lui donne quatre-vingt-cinq ans, Valère Maxime quatre-vingt-dix, d'où le flottement de sa date de naissance entre 214 et 219 av. J.-C.</ref>, le plus grand nom de cette Académie nouvelle, reprend et affine la solution. L'ambassade qu'il fit à Rome en 155 av. J.-C. illustre sa méthode : il y prononça un jour l'éloge de la justice, et le lendemain sa réfutation, avec une égale puissance de conviction, scandalisant Caton qui obtint son renvoi. La démonstration avait valeur de programme : sur toute question, des arguments d'égale force peuvent être produits en sens contraire. Pour la conduite de la vie, Carnéade élabore une doctrine du « probable » (''pithanon'') : parmi les représentations, certaines paraissent vraies avec plus de force que d'autres ; celles qui, de surcroît, s'accordent avec les représentations voisines et résistent à un examen méthodique méritent une confiance graduée. Le pilote qui prend la mer ne sait pas que la traversée réussira ; il suit la représentation probable, et cela suffit pour agir.
Sextus nous a conservé le détail de cette doctrine, qui distingue des degrés dans la confiance<ref>Sextus Empiricus, ''Adversus Mathematicos'', VII, 166-189.</ref>. Au premier degré, la représentation simplement probable, celle qui paraît vraie avec force. Au deuxième, la représentation probable et « non contredite » : avant de juger que cet homme est bien Socrate, j'examine si la taille, la démarche, la voix, le manteau concordent, car une seule discordance ébranlerait l'ensemble. Au troisième, la représentation « examinée de toutes parts », passée au crible des conditions de l'observation : l'organe est-il sain, la distance convenable, l'air limpide, l'esprit calme ? Un exemple de Sextus rend la gradation sensible. Un homme aperçoit, dans une pièce sombre, un objet enroulé : il le prend pour un serpent et saute par-dessus. Première représentation, probable mais hâtive. Revenant sur ses pas, il observe que la chose ne bouge pas ; il incline à n'y voir qu'une corde. Mais il se souvient que les serpents engourdis par le froid restent immobiles : aussi pousse-t-il l'objet d'un bâton avant de conclure. C'est seulement au terme de cet examen qu'il accorde, faute de mieux, sa confiance à la représentation. Selon les circonstances, ajoute Carnéade, le degré d'examen exigible varie : sur une question sans conséquence, le probable suffit ; quand il y va du bonheur, l'examen complet s'impose. On reconnaît là, bien avant les théories modernes du choix rationnel, l'idée que la rationalité pratique consiste à proportionner l'enquête aux enjeux. Ses disciples se divisèrent sur le sens exact de cette concession : Clitomaque soutenait que le sage suit le probable sans jamais rien tenir pour vrai ; Métrodore et Philon de Larisse, qu'il peut former des opinions en sachant qu'elles restent faillibles. La seconde lecture fait de Carnéade l'ancêtre du probabilisme et, de loin, du faillibilisme contemporain : nous pouvons être rationnels sans être certains.
== Énésidème et les dix tropes ==
Au I{{er}} siècle av. J.-C., l'Académie s'essouffle : Philon de Larisse en atténue le doute, Antiochus d'Ascalon la ramène au dogmatisme. C'est alors qu'Énésidème de Cnossos, jugeant que les Académiciens étaient devenus « des stoïciens en lutte contre des stoïciens », quitte l'école et restaure le pyrrhonisme. Son ouvrage principal, les ''Discours pyrrhoniens'', est perdu ; nous en possédons le résumé qu'en fit, au IX{{e}} siècle, le patriarche Photius<ref>Photius, ''Bibliothèque'', codex 212, texte établi et traduit par René Henry, t. III, Paris, Les Belles Lettres, 1962.</ref>. Quant aux dix « tropes » (τρόποι), c'est-à-dire aux dix modes ou figures d'argument qui conduisent à la suspension du jugement, Sextus Empiricus les expose avec une grande abondance d'exemples, et Diogène Laërce en donne un résumé qui paraît suivre de près le texte original. Énésidème n'a pas inventé tous ces arguments, dont plusieurs circulaient depuis longtemps ; son apport fut de les classer en un répertoire méthodique, une sorte de table des catégories du doute<ref>Sur l'attribution des dix tropes à Énésidème, voir Brochard, ''Les Sceptiques grecs'', livre III, et R. J. Hankinson, « Aenesidemus and the rebirth of Pyrrhonism », dans Richard Bett (dir.), ''The Cambridge Companion to Ancient Scepticism'', Cambridge, Cambridge University Press, 2010.</ref>.
Le principe est constant : le même objet produit des représentations différentes selon le point de vue, et rien ne permet de privilégier l'une d'elles. Varient ainsi : 1) les animaux, dont les organes diffèrent (l'huile d'olive nourrit l'homme et tue les abeilles ; de quel droit déclarer nos perceptions plus conformes aux choses que les leurs ?) ; 2) les hommes entre eux, par le tempérament et la constitution ; 3) les sens d'un même homme, qui se contredisent (la pomme est jaune à l'œil, douce au palais, lisse au toucher : laquelle de ces qualités est la pomme ?) ; 4) les circonstances et les états du sujet (veille ou sommeil, jeunesse ou vieillesse, santé ou maladie, joie ou chagrin) ; 5) les positions, distances et lieux (la tour ronde de loin, carrée de près ; le soleil, petit disque à l'horizon) ; 6) les mélanges, car rien ne nous parvient à l'état pur, mais toujours combiné avec l'air, la lumière, les humeurs de nos organes ; 7) les quantités et compositions (le vin modéré fortifie, le vin excessif abat ; le sable rugueux en grains, doux en tas) ; 8) la relation, trope le plus général : tout apparaît relativement à autre chose, jamais en soi ; 9) la fréquence et la rareté (le soleil quotidien nous laisse indifférents, la comète nous épouvante) ; 10) les coutumes, lois et croyances, dont la diversité interdit de fonder en nature les valeurs (ce que l'un vénère, l'autre l'interdit).
La conclusion de chaque trope obéit à la même formule : nous pouvons dire comment la chose nous apparaît, non ce qu'elle est selon sa nature. Énésidème ajouta huit tropes contre les explications causales, qui visent les physiciens dogmatiques : entre plusieurs explications possibles d'un même phénomène, ils choisissent l'une sans pouvoir exclure les autres, expliquent l'obscur par le plus obscur encore, et bâtissent sur des hypothèses invérifiables. La médecine fournissait l'exemple parfait : devant la même fièvre, observée dans les mêmes conditions, Hérophile, Érasistrate et Asclépiade lisaient trois causes différentes.
== Agrippa : le trilemme de la justification ==
Vers le I{{er}} siècle de notre ère, un sceptique dont nous ne savons presque rien, Agrippa, condense l'arsenal en cinq modes dont la portée excède tout ce qui précède, et que Sextus présente comme l'œuvre des « sceptiques plus récents ». Là où les dix tropes visaient surtout la connaissance sensible, les cinq modes attaquent toute prétention à justifier quoi que ce soit. Ce sont : le désaccord (sur toute question règne un conflit d'opinions indécidable) ; la régression à l'infini (toute preuve repose sur une autre preuve, qui en exige une autre, sans fin) ; la relativité (tout apparaît relativement au sujet et aux circonstances) ; l'hypothèse (pour arrêter la régression, le dogmatique pose un principe sans le prouver, mais une assertion gratuite peut être contrée par l'assertion gratuite opposée) ; le diallèle ou cercle (la preuve s'appuie sur ce qu'elle devait prouver).
Trois de ces modes forment un système que la tradition appelle le trilemme d'Agrippa, et qui est peut-être le schème argumentatif le plus durable que l'Antiquité ait légué à la théorie de la connaissance. Demandons à quelqu'un de justifier une affirmation quelconque. Ou bien il invoque une autre affirmation, qu'il faudra justifier à son tour : régression à l'infini. Ou bien il s'arrête à un principe posé sans preuve : hypothèse arbitraire. Ou bien il revient, par un détour, à l'affirmation initiale : cercle. Dans les trois cas, la justification échoue. Jonathan Barnes a montré que ce filet, dans lequel les sceptiques voulaient prendre tout dogmatique, reprend en le retournant un argument d'Aristote : les ''Seconds Analytiques'' rejetaient déjà la régression infinie et la preuve circulaire, mais pour en conclure qu'il existe des principes connus sans démonstration ; Agrippa garde les prémisses et refuse l'échappatoire<ref>Jonathan Barnes, ''The Toils of Scepticism'', Cambridge, Cambridge University Press, 1990, chap. 5.</ref>. Le débat n'est pas clos : l'épistémologie contemporaine, quand elle oppose fondationnalisme, cohérentisme et infinitisme, distribue encore les réponses possibles au trilemme.
== Sextus Empiricus : la suspension et la tranquillité ==
La seule œuvre sceptique antique conservée de manière substantielle est celle de Sextus Empiricus, médecin et philosophe de la fin du II{{e}} siècle de notre ère ; tout le reste, de Pyrrhon à Énésidème, ne subsiste qu'à l'état de fragments et de témoignages, et le scepticisme académicien n'est guère connu que de seconde main, par Cicéron surtout. Ses ''Esquisses pyrrhoniennes'' et son ''Contre les professeurs'' constituent une somme : exposé de la méthode, réfutation des logiciens, des physiciens et des moralistes, le tout d'une patience d'inventaire qui examine chaque thèse dogmatique pour lui opposer son contraire. Sextus définit le scepticisme comme la capacité de mettre en opposition, de quelque manière que ce soit, les choses qui apparaissent et celles qui sont pensées, de façon que, par l'égale force des choses et des raisons opposées, nous arrivions d'abord à la suspension du jugement, puis à la tranquillité<ref>Sextus Empiricus, ''Esquisses pyrrhoniennes'', I, 8, trad. Pierre Pellegrin, Paris, Éditions du Seuil, coll. « Points Essais », 1997.</ref>. La suspension porte, précise-t-il, sur les choses « non évidentes » (''adêla''), ces natures cachées que les doctrines prétendent atteindre derrière les phénomènes. Les apparences elles-mêmes ne sont pas en cause : que le miel paraisse doux, nul n'en disconvient ; qu'il soit doux par nature, voilà ce qui est en question.
La séquence mérite d'être détaillée, car elle décrit un itinéraire vécu. Le futur sceptique commence comme tout le monde : il cherche la vérité, espérant que sa découverte le rendra tranquille. Mais sur chaque question il rencontre le conflit des apparences et des doctrines ; il cherche un critère pour trancher, et tout critère proposé succombe aux modes ; les thèses opposées se révèlent d'égale force ; il suspend son jugement. Et alors se produit ce que Sextus présente comme une heureuse rencontre : l'ataraxie survient d'elle-même, « comme l'ombre suit le corps ». Il l'illustre par l'anecdote du peintre Apelle<ref>Sextus Empiricus, ''Esquisses pyrrhoniennes'', I, 28-29, éd. citée.</ref> : ne parvenant pas à peindre l'écume d'un cheval, Apelle, de dépit, jeta son éponge contre le tableau, et l'éponge produisit l'écume qu'il cherchait. Ainsi la tranquillité vient à celui qui a renoncé à l'atteindre par la possession du vrai.
Le sceptique de Sextus n'est pourtant pas réduit à l'inertie. Il suit un critère pratique à quatre branches<ref>Sextus Empiricus, ''Esquisses pyrrhoniennes'', I, 23-24, éd. citée.</ref> : la conduite de la nature (sentir et penser, comme tout homme) ; la contrainte des affections (la faim mène à la nourriture, la soif à la boisson, sans qu'aucune croyance soit requise) ; la tradition des lois et des coutumes (il tient, sans dogmatiser, la piété pour un bien et l'impiété pour un mal, et vit selon les usages de sa cité) ; l'apprentissage des arts (il exerce son métier, en l'occurrence la médecine). Le sceptique vit donc comme tout le monde, à une réserve près : il s'en tient aux apparences sans rien affirmer de la nature cachée des choses. « Que le feu brûle, nous le percevons ; qu'il ait par nature la vertu de brûler, nous suspendons notre jugement »<ref>Diogène Laërce, ''Vies et doctrines des philosophes illustres'', IX, 104, éd. citée ; nous traduisons.</ref>. Même son propre discours, le sceptique le tient pour provisoire : ses formules (« pas plus », « je suspends mon jugement ») s'appliquent à elles-mêmes et s'annulent en s'énonçant, comme les purgatifs qui s'évacuent avec les humeurs qu'ils chassent<ref>Sextus Empiricus, ''Esquisses pyrrhoniennes'', I, 206, éd. citée.</ref>.
== Peut-on vivre son scepticisme ? ==
L'objection la plus ancienne et la plus tenace adressée au scepticisme porte un nom grec, ''apraxia'' : l'inaction. Si le sceptique ne croit rien, il ne peut ni choisir, ni préférer, ni agir ; il devrait se laisser tomber dans les précipices et rester au lit faute de savoir s'il fait jour. Hume lui donnera sa forme moderne : le pyrrhonien peut bien triompher dans l'école, sa philosophie s'évanouit dès qu'il en sort, et la nature, plus forte que tous les arguments, le ramène à croire, à manger, à converser comme les autres hommes<ref>David Hume, ''Enquête sur l'entendement humain'', section XII, trad. André Leroy revue par Michelle Beyssade, Paris, Garnier-Flammarion, 1983 ; voir aussi le ''Traité de la nature humaine'', livre I, partie IV, section 7.</ref>.
La réponse sceptique tient dans la distinction de la croyance et de l'apparence : on peut suivre les apparences sans leur donner son assentiment, comme on suit un sentier sans certifier qu'il mène au but. Mais cette réponse est-elle cohérente ? Le débat divise encore les interprètes, et il s'est cristallisé dans une controverse fameuse entre Myles Burnyeat et Michael Frede<ref>Myles Burnyeat et Michael Frede (dir.), ''The Original Sceptics. A Controversy'', Indianapolis, Hackett, 1997.</ref>. Pour Burnyeat, le sceptique de Sextus vise bien toutes les croyances : suivre l'apparence sans croire est son programme, et ce programme, à le prendre au sérieux, est invivable, car dire « le miel me paraît doux mais je ne crois pas qu'il le soit » frôle l'incohérence. Pour Frede au contraire, Sextus ne rejette que les croyances dogmatiques, celles qui prétendent atteindre la nature cachée des choses par la théorie ; les croyances ordinaires de la vie quotidienne restent intactes, et le sceptique vit fort bien. La tradition anglophone a baptisé ces deux lectures scepticisme « rustique » (qui arrache tout) et scepticisme « urbain » (qui n'élague que les prétentions des savants). Le texte de Sextus, il est vrai, fournit des appuis aux deux lectures : il attaque parfois toute croyance, et se présente parfois comme le défenseur de la vie commune contre les extravagances des philosophes.
== Renaissances modernes : Montaigne, Descartes, Hume ==
Le texte de Sextus sort presque entièrement de la circulation pendant le Moyen Âge latin, qui connaît le scepticisme surtout par Cicéron et par la réfutation qu'en donne saint Augustin dans le ''Contra Academicos''. Cette éclipse n'est pourtant pas un silence : Jean de Salisbury, au XII{{e}} siècle, se réclame de la modération académicienne, et les débats du XIV{{e}} siècle sur la certitude, l'expérience et la démonstration, chez Nicolas d'Autrécourt ou Jean Buridan, prolongent plusieurs motifs sceptiques sans recourir aux textes anciens. La renaissance proprement dite date du XVI{{e}} siècle : Henri Estienne publie en 1562 une traduction latine des ''Esquisses pyrrhoniennes'', Gentien Hervet traduit le ''Contre les professeurs'' en 1569. Paradoxe de l'histoire : ces traductions servaient un dessein apologétique. Contre la prétention des réformés à juger l'Écriture par la raison individuelle, le doute pyrrhonien semblait un allié de la foi, puisqu'en ruinant la raison il laissait la croyance s'appuyer sur la seule autorité. Richard Popkin a décrit cette « crise pyrrhonienne » qui traverse la pensée européenne du XVI{{e}} au XVIII{{e}} siècle<ref>Richard H. Popkin, ''Histoire du scepticisme d'Érasme à Spinoza'', trad. Christine Hivet, Paris, Presses Universitaires de France, 1995.</ref>.
Montaigne donne au scepticisme renaissant son chef-d'œuvre. L'« Apologie de Raymond Sebond », le plus long chapitre des ''Essais'', déroule l'ensemble de l'arsenal sceptique : diversité des coutumes, conflits des philosophes, faiblesse des sens, intelligence des bêtes qui rabat l'orgueil humain. Sa devise condense l'attitude en une question, « Que sais-je ? », qu'il dit porter « à la devise d'une balance » ; il fit frapper vers 1576 un jeton orné de ce fléau en équilibre, accompagné du mot grec ''epechô'', je suspends mon jugement<ref>Michel de Montaigne, ''Essais'', II, 12, « Apologie de Raymond Sebond », éd. Pierre Villey, Paris, Presses Universitaires de France, coll. « Quadrige », 2004.</ref>. La forme interrogative est calculée : dire « je ne sais rien » serait encore affirmer, et l'affirmation se réfuterait elle-même ; demander « que sais-je ? » laisse la recherche ouverte. Chez Montaigne, le doute redevient ce qu'il était chez les Grecs : un art de vivre, fait de tolérance envers la diversité humaine, de défiance envers les fanatismes, et d'attention à soi.
Descartes opère un retournement. Il reprend les arguments sceptiques, et les pousse même plus loin que les anciens : aux illusions des sens et à l'argument du rêve, il ajoute la fiction d'un malin génie tout-puissant qui m'abuserait en toutes choses, jusque dans les évidences mathématiques. Les historiens du scepticisme ont relevé ici une nouveauté qui passe facilement inaperçue : les sceptiques anciens ne formulent pas, sous la forme que Descartes lui donnera, le problème de l'existence du monde extérieur ; leurs arguments portent sur notre accès à la nature des choses, non sur la réalité de ce qui nous entoure. La question cartésienne suppose une séparation entre l'intériorité de la conscience et la réalité extérieure que la pensée grecque n'avait pas construite en ces termes. Mais ce doute n'est plus une fin : il est un instrument. Doute méthodique et non sceptique, provisoire et non définitif, il sert à éprouver toutes les opinions pour découvrir ce qui lui résiste. Et quelque chose lui résiste : pour être trompé, encore faut-il que je sois. Le ''cogito'' naît du doute poussé à son comble, et le sceptique se trouve battu avec ses propres armes, ou du moins Descartes le croit : car il reste à remonter du moi au monde, et les sceptiques de profession, Gassendi en tête, objecteront que la sortie du cogito emprunte des ponts (la véracité divine notamment) qui n'échappent pas au cercle.
Entre Descartes et Hume, deux relais méritent mention. Pascal d'abord, qui fait du « pyrrhonisme » l'une des deux voix d'un dialogue sans vainqueur : la raison humilie le dogmatique, qui ne peut prouver ses premiers principes ; la nature humilie le pyrrhonien, qui ne peut s'empêcher de croire. « Nous avons une impuissance de prouver, invincible à tout le dogmatisme. Nous avons une idée de la vérité, invincible à tout le pyrrhonisme »<ref>Blaise Pascal, ''Pensées'', fragment 406 de l'édition Lafuma (395 de l'édition Brunschvicg), dans ''Œuvres complètes'', éd. Louis Lafuma, Paris, Éditions du Seuil, coll. « L'Intégrale », 1963.</ref>. De cette double impuissance, Pascal conclut que l'homme passe l'homme, et que la foi seule dénoue ce que la raison a noué : le scepticisme se trouve mis au service de la religion, usage que les traducteurs de Sextus avaient préparé un siècle plus tôt. Bayle ensuite, dont le ''Dictionnaire historique et critique'' (1697) fit circuler dans toute l'Europe, à l'article « Pyrrhon », les arguments sceptiques aiguisés par les querelles théologiques : c'est dans Bayle, autant que dans les anciens, que Hume apprendra son scepticisme.
Hume représente le sommet du scepticisme moderne, et son tournant. Son analyse de la causalité reprend, sans le savoir peut-être, le geste d'Énésidème : nous n'observons jamais de connexion nécessaire entre la cause et l'effet, seulement des conjonctions répétées ; notre certitude que le soleil se lèvera demain ne repose sur aucun raisonnement, mais sur l'habitude. La raison se révèle incapable de fonder nos croyances les plus élémentaires. Mais Hume tire de là une leçon nouvelle : puisque le doute intégral est invivable et que la nature nous force à croire, le scepticisme excessif (il dit : pyrrhonien) doit céder la place à un scepticisme mitigé, qui borne nos enquêtes aux sujets proportionnés à nos facultés et tempère l'affirmation par la modestie. Le sceptique devient l'auxiliaire de la science : non plus celui qui suspend tout jugement, mais celui qui mesure chaque assentiment à la force des preuves. Kant dira que Hume l'a réveillé de son « sommeil dogmatique »<ref>Emmanuel Kant, ''Prolégomènes à toute métaphysique future qui pourra se présenter comme science'', préface, trad. Louis Guillermit, Paris, Vrin, 1986.</ref>, et jugera scandaleux pour la philosophie qu'on n'ait pas encore de preuve de l'existence du monde extérieur<ref>Emmanuel Kant, ''Critique de la raison pure'', préface de la seconde édition, B XXXIX, note.</ref> : la ''Critique de la raison pure'' peut se lire comme une longue réponse au défi sceptique, qui en accepte une part (nous ne connaissons pas les choses en soi) pour mieux sauver l'autre (la science des phénomènes est fondée).
== Le scepticisme aujourd'hui ==
L'épistémologie contemporaine a hérité du scepticisme moins comme d'une position que comme d'un problème. Presque personne ne se déclare sceptique ; presque tout le monde travaille à lui répondre. Le malin génie de Descartes y reparaît sous une forme nouvelle : l'expérience de pensée du cerveau dans une cuve, mise en forme par Hilary Putnam, imagine un cerveau maintenu en vie dans un laboratoire et stimulé par un ordinateur de manière à vivre une existence ordinaire en tout point semblable à la nôtre. Comment savez-vous que vous n'êtes pas ce cerveau ? Toutes vos expériences seraient exactement les mêmes. Et si vous ne pouvez l'exclure, savez-vous seulement que vous avez deux mains ? Putnam avait pourtant conçu le scénario pour le réfuter, en soutenant qu'un cerveau qui n'aurait jamais fréquenté que des images ne pourrait pas même penser « je suis un cerveau dans une cuve », faute d'avoir jamais été en rapport avec de vraies cuves ; mais c'est le scénario, plus que la réfutation, qui est passé à la postérité<ref>Hilary Putnam, ''Raison, vérité et histoire'', trad. Abel Gerschenfeld, Paris, Éditions de Minuit, 1984, chap. 1.</ref>. Le cinéma a popularisé le scénario, de ''Matrix'' au ''Truman Show'', preuve que l'inquiétude sceptique n'est pas une affaire d'école.
Les réponses contemporaines dessinent un éventail. George Edward Moore opposait au sceptique le bon sens : levant la main, il déclarait « voici une main », jugeant cette certitude plus solide que toutes les prémisses des arguments sceptiques<ref>George Edward Moore, « Proof of an External World » (1939), repris dans ''Philosophical Papers'', Londres, Allen & Unwin, 1959.</ref> ; Wittgenstein, méditant ce geste dans ''De la certitude'', suggéra que nos certitudes de base ne sont pas des savoirs justifiés mais les gonds sur lesquels tournent nos questions, ce qui doit rester fixe pour que douter ait un sens<ref>Ludwig Wittgenstein, ''De la certitude'', trad. Jacques Fauve, Paris, Gallimard, 1976, § 341-343.</ref>. Une autre famille de réponses, ouverte par Fred Dretske et Robert Nozick, examine un rouage logique de l'argument : le principe dit de clôture, selon lequel, si je sais une chose et si je sais qu'elle en implique une autre, je sais aussi cette dernière. Le sceptique l'utilise en sens inverse : je ne sais pas que je ne suis pas un cerveau dans une cuve ; or, avoir deux mains implique de ne pas être un tel cerveau ; donc je ne sais pas que j'ai deux mains. Dretske et Nozick refusent le principe : savoir, expliquent-ils, c'est avoir une croyance qui suit la vérité dans les situations proches de la nôtre, et ma croyance d'avoir deux mains remplit cette condition même si je ne peux exclure le scénario lointain de la cuve. Je peux donc savoir que j'ai deux mains sans savoir que je ne suis pas un cerveau dans une cuve : conclusion étrange, dont le coût a paru excessif à beaucoup, mais qui localise avec précision le point sensible de l'argument<ref>Fred Dretske, « Epistemic Operators », ''The Journal of Philosophy'', vol. 67, 1970, p. 1007-1023 ; Robert Nozick, ''Philosophical Explanations'', Cambridge (Mass.), Harvard University Press, 1981, chap. 3.</ref>.
Les contextualistes, Stewart Cohen et Keith DeRose notamment, observent pour leur part que le mot « savoir » change d'exigence selon les contextes : dans la vie courante, je sais où est ma voiture ; dans le séminaire d'épistémologie, où l'hypothèse de la cuve est sur la table, le niveau d'exigence s'élève et je ne le sais plus ; le sceptique ne gagnerait que dans des contextes artificiellement exigeants<ref>Keith DeRose, ''The Case for Contextualism'', Oxford, Oxford University Press, 2009.</ref>. Les externalistes soutiennent que savoir n'exige pas de pouvoir réfuter le sceptique : il suffit que nos croyances soient produites par des mécanismes fiables, que nous puissions ou non le prouver ; Ernest Sosa a donné à cette orientation sa forme la plus élaborée en rapportant la connaissance aux vertus intellectuelles de celui qui juge, comme on rapporte la réussite d'un tir à l'adresse de l'archer. Barry Stroud a cependant objecté que la plupart de ces répliques manquent leur cible : elles montrent comment nos croyances peuvent satisfaire telle ou telle définition du savoir, sans dissiper l'impression que, considérées du dehors, toutes nos connaissances reposent sur un crédit que rien ne garantit<ref>Barry Stroud, ''The Significance of Philosophical Scepticism'', Oxford, Oxford University Press, 1984.</ref>. Duncan Pritchard, prolongeant Wittgenstein, propose d'admettre que nos certitudes charnières ne relèvent pas du savoir justifié, et de borner en conséquence la portée de la prétention sceptique<ref>Duncan Pritchard, ''Epistemic Angst. Radical Skepticism and the Groundlessness of Our Believing'', Princeton, Princeton University Press, 2015.</ref>. Aucune de ces réponses ne fait l'unanimité, et le trilemme d'Agrippa conserve sa force première contre quiconque prétend justifier les justifications elles-mêmes.
Il faut enfin distinguer le scepticisme philosophique d'un usage voisin du mot. Ce qu'on appelle aujourd'hui « scepticisme scientifique », cette vigilance organisée contre les pseudo-sciences, le paranormal et les remèdes miracles, n'est pas le scepticisme des philosophes : loin de suspendre le jugement, il juge, tranche et réfute, au nom des sciences établies. Il prolonge, dans un tout autre cadre, le scepticisme mitigé de Hume, dont il reprend la maxime : proportionner la croyance aux preuves. Le pyrrhonien lui demanderait ce qui fonde sa confiance dans les sciences établies, et le trilemme d'Agrippa retrouverait son application. La parenté des deux scepticismes est donc réelle, mais lointaine : l'un est un art de suspendre le jugement, l'autre un art de le proportionner, et l'on peut pratiquer le second sans avoir jamais éprouvé le premier.
Reste à dire ce que le scepticisme nous apprend, même à ceux qui ne le suivent pas. Il est d'abord un exercice d'hygiène intellectuelle : nul ne mesure la force d'une thèse s'il n'a pas éprouvé la force de la thèse contraire, et la pratique carnéadienne du discours en sens opposés reste la meilleure école de l'esprit critique. Il est ensuite un aiguillon : presque toutes les grandes théories de la connaissance, d'Aristote à Kant et au-delà, se sont construites pour lui répondre, et c'est en cherchant à colmater les brèches qu'il ouvre que l'épistémologie a précisé ses notions de preuve, de justification et de certitude. Il est enfin, dans sa version antique, une proposition de sagesse qui garde de quoi nous étonner : l'idée que nos tourments viennent moins des choses que des jugements que nous portons sur elles, et qu'un certain art de suspendre le verdict, sur les opinions, sur les valeurs, sur autrui, est une pièce de la vie heureuse. Le sceptique grec ne demande pas de renoncer à chercher : ''skepsis'' veut dire recherche. Sextus ouvre d'ailleurs les ''Esquisses'' sur une tripartition qui résume tout : ceux qui croient avoir trouvé la vérité sont les dogmatiques ; ceux qui la déclarent insaisissable, les académiciens ; ceux qui cherchent encore, les sceptiques<ref>Sextus Empiricus, ''Esquisses pyrrhoniennes'', I, 1-3, éd. citée.</ref>. Le dogmatique est celui qui a cessé de chercher, puisqu'il croit avoir trouvé ; le sceptique, celui qui examine encore.
== Notes et références ==
{{references|colonnes=2}}
== Bibliographie ==
=== Sources antiques ===
* Cicéron, ''Les Académiques'', traduction de José Kany-Turpin, Paris, Garnier-Flammarion, 2010.
* Diogène Laërce, ''Vies et doctrines des philosophes illustres'', livre IX, traduction sous la direction de Marie-Odile Goulet-Cazé, Paris, Le Livre de Poche, coll. « La Pochothèque », 1999.
* Eusèbe de Césarée, ''La Préparation évangélique'', livres XIV-XV (témoignage d'Aristoclès sur Pyrrhon), traduction d'Édouard des Places, Paris, Éditions du Cerf, 1987.
* Photius, ''Bibliothèque'', codex 212 (résumé des ''Discours pyrrhoniens'' d'Énésidème), texte établi et traduit par René Henry, t. III, Paris, Les Belles Lettres, 1962.
* Sextus Empiricus, ''Esquisses pyrrhoniennes'', traduction de Pierre Pellegrin, Paris, Éditions du Seuil, coll. « Points Essais », 1997.
* Sextus Empiricus, ''Contre les professeurs'', traduction sous la direction de Pierre Pellegrin, Paris, Éditions du Seuil, coll. « Points Essais », 2002.
=== Textes classiques ===
* Michel de Montaigne, « Apologie de Raymond Sebond », ''Essais'', II, 12, édition de Pierre Villey, Paris, Presses Universitaires de France, coll. « Quadrige », 2004.
* Blaise Pascal, ''Pensées'', dans ''Œuvres complètes'', édition de Louis Lafuma, Paris, Éditions du Seuil, coll. « L'Intégrale », 1963.
* David Hume, ''Enquête sur l'entendement humain'', traduction d'André Leroy revue par Michelle Beyssade, Paris, Garnier-Flammarion, 1983.
=== Études ===
* Julia Annas et Jonathan Barnes, ''The Modes of Scepticism. Ancient Texts and Modern Interpretations'', Cambridge, Cambridge University Press, 1985.
* Alan Bailey, ''Sextus Empiricus and Pyrrhonean Scepticism'', Oxford, Oxford University Press, 2002.
* Jonathan Barnes, ''The Toils of Scepticism'', Cambridge, Cambridge University Press, 1990.
* Richard Bett, ''Pyrrho, his Antecedents, and his Legacy'', Oxford, Oxford University Press, 2000.
* Richard Bett (dir.), ''The Cambridge Companion to Ancient Scepticism'', Cambridge, Cambridge University Press, 2010.
* Victor Brochard, ''Les Sceptiques grecs'' (1887), rééd. Paris, Le Livre de Poche, 2002.
* Myles Burnyeat et Michael Frede (dir.), ''The Original Sceptics. A Controversy'', Indianapolis, Hackett, 1997.
* Marcel Conche, ''Pyrrhon ou l'apparence'', Paris, Presses Universitaires de France, 1994.
* Pierre Couissin, « Le stoïcisme de la Nouvelle Académie », ''Revue d'histoire de la philosophie'', vol. 3, 1929, p. 241-276.
* Keith DeRose, ''The Case for Contextualism'', Oxford, Oxford University Press, 2009.
* Jean-Paul Dumont, ''Le Scepticisme et le phénomène. Essai sur la signification et les origines du pyrrhonisme'', Paris, Vrin, 1972.
* Anna Maria Ioppolo, ''La testimonianza di Sesto Empirico sull'Accademia scettica'', Naples, Bibliopolis, 2009.
* Anthony A. Long et David N. Sedley, ''Les Philosophes hellénistiques'', traduction de Jacques Brunschwig et Pierre Pellegrin, Paris, Garnier-Flammarion, 2001, 3 vol.
* Richard H. Popkin, ''Histoire du scepticisme d'Érasme à Spinoza'', traduction de Christine Hivet, Paris, Presses Universitaires de France, 1995.
* Duncan Pritchard, ''Epistemic Angst. Radical Skepticism and the Groundlessness of Our Believing'', Princeton, Princeton University Press, 2015.
* Gisela Striker, ''Essays on Hellenistic Epistemology and Ethics'', Cambridge, Cambridge University Press, 1996.
* Barry Stroud, ''The Significance of Philosophical Scepticism'', Oxford, Oxford University Press, 1984.
{{AutoCat}}
qoqut47dqmtibzzu077di80vecy5x9r
Dictionnaire de philosophie/Pyrrhon d'Élis
0
83928
768695
767699
2026-06-26T04:33:11Z
PandaMystique
119061
Bot : ajout du paramètre lecture=oui au modèle {{DicoPhilo}}
768695
wikitext
text/x-wiki
{{DicoPhilo|Pyrrhon d'Élis|lecture=oui}}
[[Fichier:Pyrrho in Thomas Stanley History of Philosophy.jpg|vignette|Pyrrhon d'Élis, gravure tirée de ''The History of Philosophy'' de Thomas Stanley (1655). Aucun portrait antique authentique du philosophe n'est conservé.]]
Pyrrhon d'Élis (v. 365/360 - v. 275/270 av. J.-C.) est un philosophe grec de l'époque hellénistique, traditionnellement considéré comme le fondateur du [[Dictionnaire de philosophie/Scepticisme|scepticisme]] ancien. Sa situation dans l'[[Philosophie/Histoire de la philosophie|histoire de la philosophie]] est paradoxale : il a donné son nom à tout un courant de pensée, le pyrrhonisme, dont les écrivains sceptiques se réclament pendant des siècles, d'Énésidème, qui intitule un de ses ouvrages ''Discours pyrrhoniens'', à Sextus Empiricus, qui nomme encore ''Esquisses pyrrhoniennes'' une de ses œuvres quatre siècles après la mort du maître. Et pourtant Pyrrhon n'a rien écrit, sinon un poème de circonstance en l'honneur d'Alexandre. Il demeure l'un des philosophes les plus mal connus de l'Antiquité, et la question de savoir ce qu'il a réellement pensé, et même s'il fut sceptique au sens où ses lointains héritiers l'entendront, divise aujourd'hui encore les interprètes.
== Une vie entre deux mondes ==
[[Fichier:Elis Theater from SE.jpg|vignette|Le théâtre antique d'Élis, cité natale de Pyrrhon. La cité honora le philosophe d'un sacerdoce et, après sa mort, d'une statue sur l'agora.]]
Fils de Pleistarque, selon Diogène Laërce et la ''Souda'', Pyrrhon naquit entre 365 et 360 av. J.-C. à Élis, ou plus précisément, semble-t-il, à Pétra, un bourg voisin où Pausanias dit avoir vu son tombeau<ref>Diogène Laërce, ''Vies et doctrines des philosophes illustres'', IX, 61 ; ''Souda'', s.v. Πύρρων ; Pausanias, ''Description de la Grèce'', VI, 24, 5.</ref>. Issu d'un milieu modeste, il commença par exercer la peinture, sans grand éclat : on conservait encore à Élis, au {{s|II}} siècle de notre ère, des porteurs de flambeaux de sa main, jugés médiocres. Selon Diogène Laërce, il aurait d'abord écouté un certain Bryson, que le texte rattache à Stilpon par une formule ambiguë : le grec ne permet pas de décider s'il en fait son fils ou son disciple, et la chronologie a conduit les éditeurs, depuis Nietzsche, à soupçonner le passage<ref>Diogène Laërce, ''Vies'', IX, 61 ; ''Souda'', s.v. Πύρρων. Sur les difficultés de ce témoignage, voir Fernanda Decleva Caizzi, ''Pirrone. Testimonianze'', Naples, Bibliopolis, 1981, p. 132-134.</ref>. Il devint ensuite le disciple d'Anaxarque d'Abdère, chaînon entre Démocrite et Pyrrhon dans les successions anciennes, et cette filiation passe pour la plus crédible ; Jacques Brunschwig suggère que ce qui retint Pyrrhon chez Démocrite fut l'attitude morale plus que la physique des atomes<ref>Diogène Laërce, ''Vies'', IX, 61 et IX, 67 ; Jacques Brunschwig, « Introduction au livre IX », dans Diogène Laërce, ''Vies et doctrines des philosophes illustres'', trad. sous la direction de Marie-Odile Goulet-Cazé, Paris, Le Livre de Poche, 1999, p. 1038.</ref>. L'Antiquité a d'ailleurs fait circuler deux généalogies concurrentes : l'une, socratique, rattache Pyrrhon aux successeurs de Phédon d'Élis ; l'autre l'inscrit dans la lignée éléate et démocritéenne<ref>Strabon, ''Géographie'', IX, 1, 8 ; ''Souda'', s.v. Σωκράτης ; Eusèbe de Césarée, ''Préparation évangélique'', XIV, 17, 10.</ref>.
[[Fichier:Alexander the Great Receiving News of the Death by Immolation of the Indian Gymnosophist Calanus - Jean-Baptiste de Champaigne - 1672.jpeg|vignette|Alexandre apprenant la mort du gymnosophiste Calanos, tableau de Jean-Baptiste de Champaigne (1672). Pyrrhon rencontra les « sages nus » de l'Inde lors de l'expédition d'Alexandre.]]
Sa biographie comporte un épisode singulier pour un philosophe grec : en compagnie d'Anaxarque, Pyrrhon suivit l'expédition d'Alexandre en Asie. Il y rencontra les mages de Perse et les gymnosophistes de l'Inde, ces « sages nus » dont l'ascèse et le détachement frappèrent les Grecs. Diogène Laërce affirme que cette rencontre fut à l'origine de sa philosophie<ref>Diogène Laërce, ''Vies'', IX, 61.</ref>. La portée exacte de cette influence orientale reste discutée : depuis Brochard, les historiens ont relevé des parallèles entre l'indifférence pyrrhonienne et certaines attitudes de la pensée indienne, sans qu'on puisse établir une filiation doctrinale précise<ref>Victor Brochard, ''Les Sceptiques grecs'', Paris, Imprimerie nationale, 1887 ; rééd. Le Livre de Poche, 2002, p. 87-89 ; Richard Bett, ''Pyrrho, his Antecedents, and his Legacy'', Oxford, Oxford University Press, 2000, p. 169-178.</ref>. Du moins ce voyage place-t-il Pyrrhon au point de bascule entre deux mondes. Citoyen d'une petite cité du Péloponnèse, vouée au culte de Zeus Olympien et longtemps préservée des guerres, il a vécu une part essentielle de sa vie active dans l'élément contraire, celui de la cour itinérante d'un conquérant. Né avant Chéronée, mort sous les derniers Diadoques, il a vu s'effondrer la cité classique et naître le monde hellénistique. Marcel Conche voit dans sa philosophie une pensée du passage d'un monde à l'autre, contemporaine de cette dissolution générale des repères<ref>Marcel Conche, ''Pyrrhon ou l'apparence'', Paris, PUF, coll. « Perspectives critiques », 1994, Prologue.</ref>.
De retour à Élis après la mort d'Alexandre, Pyrrhon s'y entoura de nombreux disciples, vers 322, avant même la création du Jardin d'Épicure et du Portique de Zénon ; on hésite cependant à parler d'école, faute de doctrine professée et d'institution durable, et c'est rétrospectivement que des philosophes se réclameront de son nom. Il mena une existence simple et retirée, vivant avec sa sœur Philista, qui était sage-femme, vendant à l'occasion volailles et cochons de lait au marché, et ne dédaignant pas les travaux domestiques. Ses concitoyens l'entourèrent d'une estime durable : ils firent de lui un grand prêtre (ἀρχιερεύς) et, en son honneur, accordèrent aux philosophes une atélie, généralement comprise comme une exemption d'impôts ; après sa mort, survenue entre 275 et 270 environ, ils lui élevèrent une statue que Pausanias pouvait encore voir sur l'agora<ref>Diogène Laërce, ''Vies'', IX, 64 ; Pausanias, ''Description de la Grèce'', VI, 24, 5. Le passage de Diogène, issu d'Antigone de Caryste, est jugé moins fiable : la nature du culte (Apollon Akésios, Hadès ou Tychè) et celle de l'atélie restent imprécises.</ref>. Ce détail mérite réflexion : de tels honneurs civiques invitent à traiter avec prudence les anecdotes hostiles ou caricaturales. Ils constituent une pièce du dossier lorsqu'il s'agit d'évaluer les récits qui dépeignent Pyrrhon en homme incapable de vivre, et nous y reviendrons.
== Le problème des sources ==
[[Fichier:Timon in Thomas Stanley History of Philosophy.jpg|vignette|Timon de Phlionte, disciple immédiat de Pyrrhon et son principal témoin, gravure tirée de l'ouvrage de Thomas Stanley (1655).]]
Puisque Pyrrhon n'a laissé aucun écrit philosophique, sa pensée ne nous est accessible que par témoignages interposés, et ces témoignages ne s'accordent pas. Il y a, pour ainsi dire, deux Pyrrhon. Le premier est celui de la tradition sceptique : un penseur dont la position porte sur la connaissance et sur les choses, et que rapportent Aristoclès de Messine, Diogène Laërce et, à distance, Sextus Empiricus. Le second est celui de la tradition académique conservée par Cicéron : un moraliste de l'indifférence, régulièrement associé à Ariston de Chios et à Hérille de Carthage, qui aurait soutenu que rien ne compte hormis la vertu et que tout le reste se vaut<ref>Cicéron, ''De finibus'', II, 13, 43 ; IV, 16, 43 ; ''Premiers Académiques'', II, 42, 130. Sur ce double visage, voir Brochard, ''Les Sceptiques grecs'', 1887, livre I, chap. 3.</ref>.
Au sein de la première tradition, deux documents dominent. Le premier est le chapitre que Diogène Laërce consacre à Pyrrhon au livre IX de ses ''Vies'', dont la partie biographique reprend le récit d'Antigone de Caryste, auteur du {{e|III}} siècle av. J.-C. qui a pu recueillir des souvenirs de première main. Le second, le plus précieux pour la doctrine, est un extrait du ''Sur la philosophie'' d'Aristoclès de Messine, péripatéticien dont les dates sont mal connues mais qu'on ne situe plus aujourd'hui après le I{{er}} siècle de notre ère, conservé par Eusèbe de Césarée dans sa ''Préparation évangélique''<ref>Eusèbe de Césarée, ''Préparation évangélique'', XIV, 18, 1-4. Sur la datation haute d'Aristoclès, voir Simone Follet, notice « Aristoclès de Messine », dans Richard Goulet (dir.), ''Dictionnaire des philosophes antiques'', t. I, Paris, CNRS Éditions, 1989, p. 382-384. Marcel Conche défend en revanche la forme « Aristoclès de Messène », rapportant le nom à la cité du Péloponnèse (''Pyrrhon ou l'apparence'', 1994).</ref>. Or Aristoclès y résume le témoignage de Timon de Phlionte, disciple immédiat de Pyrrhon, auteur des ''Silles'' et des ''Indalmoi'', poèmes satiriques et élégiaques où il moque tous les philosophes à l'exception de son maître, ainsi que d'une œuvre en prose, le ''Python''. Nous lisons donc Pyrrhon à travers trois relais successifs : Timon le rapporte, Aristoclès le résume, Eusèbe le cite. Toute la difficulté de l'interprétation tient à cette chaîne de transmission, où chaque maillon a pu infléchir le sens.
== Le témoignage d'Aristoclès : les trois questions ==
[[Fichier:Eusebius evangelica praeparatione - Jenson 1470 1r.jpg|vignette|Première page de la ''Préparation évangélique'' d'Eusèbe de Césarée dans l'édition vénitienne de Nicolas Jenson (1470). C'est par cet ouvrage que le témoignage d'Aristoclès sur Pyrrhon nous est parvenu.]]
Le passage d'Aristoclès constitue le texte le plus détaillé dont nous disposions sur la pensée de Pyrrhon, et c'est autour de lui que gravite toute la discussion savante. Selon Timon, celui qui veut atteindre le bonheur doit considérer trois questions : premièrement, quelle est la nature des choses ; deuxièmement, quelle disposition nous devons adopter à leur égard ; troisièmement, ce qui en résultera pour qui adopte cette disposition. La démarche est limpide dans sa construction : de ce que sont les choses dépend l'attitude qui convient, et de cette attitude dépend ce que nous pouvons en attendre. Le bonheur vient en conclusion d'une enquête qui commence par une question sur les choses. C'est en cela que Pyrrhon est de son temps : comme Épicure ou Zénon, il cherche une voie vers la vie heureuse ; mais à la différence de ses rivaux dogmatiques, la voie qu'il propose passe par un renoncement.
À la première question, Pyrrhon répond, toujours selon Timon, que les choses sont également ''adiaphora'' (ἀδιάφορα), ''astathmēta'' (ἀστάθμητα) et ''anepikrita'' (ἀνεπίκριτα). Chacun de ces trois adjectifs grecs admet deux lectures, et cette ambiguïté commande tout le reste, jusque dans le choix des traductions. Faut-il comprendre que les choses sont en elles-mêmes « indifférentes, indéterminées et indécises », comme le rendent les partisans d'une lecture portant sur la réalité, ou bien qu'elles sont pour nous « indifférenciables, immesurables et indécidables », comme le suggèrent les suffixes qu'adoptent les tenants d'une lecture portant sur nos facultés ? La première lecture fait parler Pyrrhon des choses, la seconde de notre incapacité à les saisir. Nous verrons que ce choix de traduction engage deux images presque opposées de Pyrrhon.
De cette thèse sur les choses découle une conséquence : ni nos sensations ni nos opinions ne disent le vrai ou le faux. Il ne faut donc pas leur accorder confiance, mais demeurer sans opinion, sans inclination, sans ébranlement, en disant de chaque chose qu'elle « n'est pas plus qu'elle n'est pas, ou bien qu'elle est et n'est pas, ou bien qu'elle n'est ni n'est pas ». Cette formule du ''ou mallon'' (οὐ μᾶλλον), « pas plus », deviendra l'une des devises du scepticisme ancien<ref>Sur les emplois du verbe être dans cette formule, où il faut sans doute sous-entendre un prédicat, voir Jacques Brunschwig, « Pyrrhon », dans Monique Canto-Sperber (dir.), ''Philosophie grecque'', Paris, PUF, 1997, p. 469-470.</ref>. Prenons un exemple simple pour en saisir l'étrangeté. Devant un gâteau, le sens commun dit : il est sucré. Le relativiste corrige : il est sucré pour moi, peut-être fade pour un malade. Pyrrhon va plus loin que l'un et l'autre : le gâteau n'est pas plus sucré que non sucré, et l'on ne sauvera pas l'affirmation en la réfugiant dans le « pour moi ». Le langage ordinaire, qui dit à chaque instant que les choses sont ceci ou cela, se trouve frappé d'une suspicion qui ne souffre aucune exception.
À qui se tient dans cette disposition, Timon promet deux choses : d'abord ce qu'il nomme ''aphasia'' (ἀφασία). Le mot évoque littéralement l'absence de parole, mais il ne peut s'agir ici d'un mutisme, puisque Timon vient précisément de prescrire ce qu'il faut dire de chaque chose ; il faut plutôt y entendre le renoncement à toute assertion définie, qui n'est pas l'attitude prescrite elle-même mais ce qui en résulte, et qui diffère de la non-assertion telle que Sextus la concevra<ref>Sur ce point disputé, voir Jacques Brunschwig, « L'aphasie pyrrhonienne », dans Carlos Lévy et Laurent Pernot (dir.), ''Dire l'évidence'', Paris, L'Harmattan, 1997, p. 297-320.</ref>. Vient ensuite l'imperturbabilité, ''ataraxia'' (ἀταραξία), l'absence de trouble, cette tranquillité de l'âme que toutes les écoles hellénistiques poursuivent par des chemins différents. Le résumé d'Aristoclès ajoute que, « selon Énésidème », il s'ensuit aussi le plaisir : la mention trahit le filtre du pyrrhonisme renaissant à travers lequel le témoignage nous est parvenu. Là où l'épicurien fonde sa sérénité sur une physique des atomes qui dissipe la crainte des dieux et de la mort, là où le stoïcien la fonde sur la connaissance de l'ordre du monde, le pyrrhonien l'obtient en cessant de prétendre savoir. Nos tourments naissent de nos jugements : nous croyons que telle chose est bonne, nous souffrons de ne pas l'avoir ; nous croyons que telle autre est un mal, nous tremblons qu'elle n'arrive. Que ces jugements perdent leur assise, et le trouble perd la sienne. La paix vient non d'une réponse, mais de l'évanouissement de la question.
== Le conflit des interprétations ==
Que signifie au juste la réponse de Pyrrhon à la première question ? Le débat contemporain s'organise autour des deux lectures des trois adjectifs.
La lecture épistémologique, dite aussi subjective, longtemps dominante, comprend que les choses sont indifférenciables et indécidables « pour nous » : nos facultés ne nous donnent pas accès à leur nature. Pyrrhon serait alors le premier maillon d'une chaîne qui conduit sans rupture jusqu'à Sextus Empiricus : un penseur de l'incapacité humaine à trancher, un sceptique au sens plein. Cette lecture a pour elle la continuité de la tradition qui se réclame de Pyrrhon. Elle se heurte pourtant à une difficulté d'ordre logique, soulignée avec insistance par Richard Bett : dans le texte d'Aristoclès, c'est de la thèse sur les choses que l'on déduit que nos sensations et opinions ne sont ni vraies ni fausses. Or, si la thèse initiale affirmait seulement que nous ne pouvons pas connaître la nature des choses, on devrait conclure que nous ignorons si nos sensations sont vraies, et non qu'elles ne sont ni vraies ni fausses. Dire d'une sensation qu'elle n'est ni vraie ni fausse, c'est se prononcer sur son rapport au réel, ce qui suppose qu'on sache quelque chose de ce réel<ref>Bett, ''Pyrrho'', 2000, p. 14-43, et, sur l'amendement du texte grec, p. 25-26. Certains interprètes ont proposé de corriger le texte grec pour rétablir la cohérence de la lecture épistémologique ; Bett montre que l'amendement ne résout pas la difficulté.</ref>.
D'où la lecture métaphysique, dite aussi objective, que Bett défend sous le nom de thèse de l'indétermination (''indeterminacy thesis'') : Pyrrhon aurait soutenu une thèse sur la réalité elle-même, à savoir que les choses sont en leur nature indéterminées, sans propriétés fixes, sans contours assignables. Si tel est le cas, l'inférence devient intelligible : puisque rien n'est déterminément ceci ou cela, aucune sensation, aucune opinion qui attribue aux choses un caractère défini ne peut être dite vraie, ni d'ailleurs fausse. Mais cette lecture a un prix : elle fait de Pyrrhon un dogmatique de l'indétermination, qui affirme quelque chose de la nature des choses, et donc tout autre chose qu'un sceptique au sens de Sextus, lequel s'interdit précisément toute affirmation de ce genre. Le fondateur éponyme du pyrrhonisme n'aurait pas été pyrrhonien<ref>Pour un état des lieux du débat, voir Svavar Hrafn Svavarsson, « Pyrrho and Early Pyrrhonism », dans Richard Bett (dir.), ''The Cambridge Companion to Ancient Scepticism'', Cambridge, Cambridge University Press, 2010, p. 36-57.</ref>.
Une troisième voie, ancienne, mérite d'être rappelée : celle de Brochard, qui jugeait que le Pyrrhon historique fut un sage avant d'être un logicien. Les anciens eux-mêmes retenaient surtout sa manière de vivre ; ses actes, plus que ses paroles, constituaient son enseignement, et ce n'est que plus tard, selon Brochard, qu'on interpréta en un sens logique ce qui avait d'abord une signification morale. Le mot d'ordre du pyrrhonisme primitif n'aurait pas été « que sais-je ? », mais plutôt « tout m'est égal »<ref>Brochard, ''Les Sceptiques grecs'', 1887, livre I, chap. 3. Cette lecture morale trouve un appui dans les témoignages de Cicéron, qui associent Pyrrhon aux « indifférentistes » Ariston de Chios et Hérille de Carthage.</ref>. La formule qui clôt l'étude de 1885 a fait fortune : Pyrrhon « fut avant tout un désabusé : il fut un ascète grec »<ref>Victor Brochard, « Pyrrhon et le scepticisme primitif », ''Revue philosophique'', 1885, p. 532.</ref>.
En France, l'interprétation de Marcel Conche occupe une place à part. Pour Conche, Pyrrhon n'est ni un sceptique de l'inconnaissable ni un moraliste : il est le penseur de l'apparence pure. Ce que Pyrrhon met en question, c'est l'idée même d'être, ce socle que toute la métaphysique présuppose sans l'interroger. Dire que chaque chose « n'est pas plus qu'elle n'est pas », ce n'est pas avouer notre ignorance d'une réalité cachée derrière les phénomènes : c'est dissoudre la notion d'une telle réalité. Il ne reste alors que l'apparence, mais une apparence d'un genre inédit, qui n'est plus apparence « de » quelque chose ni apparence « pour » quelqu'un, puisque la chose et le sujet se résolvent eux-mêmes en apparences. Sur ce point, Conche insiste : le pyrrhonisme n'est ni un relativisme ni un subjectivisme, car ces doctrines maintiennent la scission du sujet et de l'objet, de l'apparaître et de l'être, que Pyrrhon veut justement abolir. Là où Platon allait de l'apparence vers l'être, Pyrrhon fait le chemin inverse : de l'« être », réification illusoire produite par le langage, vers l'apparence universelle. Cette lecture, longtemps isolée, a été reprise par Carlos Lévy<ref>Conche, ''Pyrrhon ou l'apparence'', 1994, chap. VIII et IX ; Carlos Lévy, ''Les scepticismes'', Paris, PUF, coll. « Que sais-je ? », 2008. L'apport de Conche est reconnu au-delà des frontières : Giovanni Reale et Fernanda Decleva Caizzi lui attribuent le mérite d'avoir mis en crise l'interprétation phénoméniste de Pyrrhon, même lorsqu'ils refusent de le suivre dans sa lecture nihiliste.</ref>.
On mesure l'écart entre ces lectures en revenant aux trois adjectifs. « Indifférenciables pour nous » : Pyrrhon est un sceptique. « Indéterminées en elles-mêmes » : il est un métaphysicien négatif. « Sans différence de valeur » : il est un moraliste. Le même texte porte ces trois Pyrrhon, et l'inventaire n'est pas clos : Giovanni Reale a dénombré jusqu'à huit interprétations, de la lecture phénoméniste aux lectures orientaliste ou littéraire<ref>Giovanni Reale, « Ipotesi per una rilettura della filosofia di Pirrone di Elide », dans Gabriele Giannantoni (dir.), ''Lo scetticismo antico'', Naples, Bibliopolis, 1981, t. I, p. 243-336.</ref>. Le lecteur doit donc savoir que tout exposé de cette pensée, y compris celui-ci, repose sur des choix interprétatifs que l'état des sources ne permet pas de clore.
== Les anecdotes : un sage en péril ? ==
[[Fichier:Petrarca-Meister 001.jpg|vignette|Pyrrhon sur le navire pris dans la tempête, gravure sur bois attribuée au Maître de Pétrarque (premier quart du {{e|XVI}} siècle). Le porcelet imperturbable donne au sage l'exemple de l'ataraxie.]]
La tradition biographique a transmis sur Pyrrhon une série d'anecdotes hautes en couleur. Selon Antigone de Caryste, il vivait en conformité avec sa doctrine, n'évitant rien, ne se gardant de rien, affrontant indifféremment chariots, précipices et chiens, sans rien accorder à ses sens ; ses familiers le suivaient pour le préserver du danger<ref>Diogène Laërce, ''Vies'', IX, 62.</ref>. Un jour qu'Anaxarque était tombé dans un marécage, Pyrrhon aurait passé son chemin sans lui porter secours ; et Anaxarque, loin de lui en vouloir, l'aurait félicité de son indifférence. Posidonius rapporte qu'au cours d'une traversée, comme la tempête épouvantait les passagers, Pyrrhon resta calme et leur montra un petit cochon qui continuait de manger sur le pont : voilà, dit-il, l'imperturbabilité dans laquelle le sage doit se tenir<ref>Diogène Laërce, ''Vies'', IX, 63 et IX, 68.</ref>. D'autres récits le montrent au contraire en défaut : effrayé par un chien, il se serait réfugié sur un arbre, concédant qu'il est « difficile de dépouiller l'homme » ; surpris en colère contre sa sœur, il aurait répliqué que ce n'est pas à propos d'une femme qu'il convient de faire montre d'indifférence<ref>Diogène Laërce, ''Vies'', IX, 66 ; Aristoclès, dans Eusèbe, ''Préparation évangélique'', XIV, 18, 26.</ref>.
Que faire de ces récits ? Les prendre au pied de la lettre conduit à une absurdité : un homme qui ne distinguerait pas un précipice d'un chemin ne vivrait pas quatre-vingt-dix ans, et une cité ne ferait pas d'un tel insensé son grand prêtre. Aussi les interprètes ont-ils proposé deux explications. La première y voit une fabrication polémique. Aristote avait objecté aux négateurs du principe de contradiction que leurs actes démentent leurs paroles : pourquoi celui qui tient la chute dans un puits pour indifféremment bonne et mauvaise s'en garde-t-il avec tant de soin<ref>Aristote, ''Métaphysique'', Γ, 4, 1008 b 12-20.</ref> ? L'anecdote des précipices ressemble fort à une mise en scène de cette objection : un récit hostile de ce que ferait quelqu'un qui prendrait Pyrrhon au sérieux, plutôt qu'un compte rendu de ce que faisait Pyrrhon. On a relevé en outre que ces récits se réfutent eux-mêmes : si Pyrrhon se défie de ses propres sens mais s'en remet à ceux de ses amis pour se tirer d'affaire, il montre par là que les sens sont, de fait, dignes de foi. La seconde explication, avancée par Conche, retourne la perspective : il s'agirait d'une pantomime pédagogique. Pyrrhon, ancien peintre, met en scène sa leçon devant ses disciples ; il feint de marcher vers le précipice, on le retient, et chacun comprend que la différence du précipice et du non-précipice, si pressante pour la conduite de la vie, ne dit rien d'un partage dans l'être. Le précipice n'est ici qu'un exemple parmi d'autres possibles : ce que la scène donne à voir, c'est l'apparence universelle, non une invitation à se jeter dans le vide<ref>Conche, ''Pyrrhon ou l'apparence'', 1994, chap. III, p. 135-136 ; sur la lecture polémique, Bett, ''Pyrrho'', 2000, p. 67-69.</ref>. Aucun élément des sources ne permet toutefois de départager ces deux explications, qui demeurent l'une et l'autre conjecturales.
Reste que ces anecdotes, quelle qu'en soit la valeur historique, dessinent le portrait que l'Antiquité s'est fait de Pyrrhon : celui d'un homme d'une égalité d'âme presque inentamable, vénéré de ses disciples à l'égal d'un Socrate, et dont la vie même constituait l'enseignement. Timon le dépeint ''atuphos'' (ἄτυφος), exempt de vanité, indompté par tout ce qui dompte les mortels, ces foules accablées sous le poids des passions, de l'opinion et des conventions<ref>Fragments des ''Silles'' et des ''Indalmoi'' de Timon, présentés et classés par Bett, ''Pyrrho'', 2000, p. 70-71. Bett relève les nombreux parallèles entre ces fragments et les anecdotes, au point qu'on a pu soupçonner les secondes d'être des broderies sur les premiers.</ref>. L'Antiquité elle-même percevait la tension entre l'idéal et l'homme : on discutait, rapporte Diogène, pour savoir si la fin visée par les sceptiques était l'impassibilité (''apatheia'', ἀπάθεια) ou la douceur (''praotès'', πραότης)<ref>Diogène Laërce, ''Vies'', IX, 108.</ref>. Un de ses disciples dira qu'il faut imiter sa manière d'être, quitte à garder ses propres opinions : formule révélatrice, qui fait du pyrrhonisme un style d'existence avant d'en faire une doctrine.
== Postérité ==
[[Fichier:Sextus Empiricus - engraving by G. F. Riedel - 1801.jpg|vignette|Sextus Empiricus, gravure de Gottlieb Friedrich Riedel (1801). Ses ''Esquisses pyrrhoniennes'' fixèrent la forme classique du scepticisme qui se réclame de Pyrrhon.]]
Le destin posthume de Pyrrhon est aussi singulier que sa vie. Son école ne lui survécut guère : après Timon, qui fut son propagandiste plus que son continuateur, le pyrrhonisme s'éteignit pour près de deux siècles, éclipsé par le scepticisme de la Nouvelle Académie, celui d'Arcésilas et de Carnéade, qui combattait le dogmatisme stoïcien avec d'autres armes. C'est au I{{er}} siècle av. J.-C. qu'Énésidème de Cnossos, dont les liens exacts avec l'Académie restent discutés, ranima la tradition en se plaçant sous le patronage de Pyrrhon, dont le nom servit dès lors d'emblème à un scepticisme refondé. Aristoclès note avec ironie que personne ne se souciait plus de Pyrrhon et de Timon quand Énésidème entreprit, « hier ou avant-hier », d'en ranimer le bavardage<ref>Aristoclès, dans Eusèbe, ''Préparation évangélique'', XIV, 18, 29.</ref>. Cette renaissance culmine au {{e|II}} siècle de notre ère avec Sextus Empiricus, dont les ''Esquisses pyrrhoniennes'' fixent la forme classique du scepticisme : suspension du jugement (''epochè'', ἐποχή) fondée sur l'égale force des arguments opposés (''isosthéneia'', ἰσοσθένεια) que produit la mise en regard méthodique des apparences et des raisonnements, tropes d'Énésidème et d'Agrippa, vie sans dogmes réglée sur les phénomènes et les coutumes.
Mais cette consécration est aussi une transformation. L'historiographie contemporaine distingue désormais trois scepticismes : celui de Pyrrhon et de ses disciples directs, celui de la Nouvelle Académie, et le néopyrrhonisme d'Énésidème et de Sextus. Rien ne garantit que le premier ressemble au troisième. La suspension du jugement elle-même, cette ''epochè'' qui est la pièce maîtresse du système de Sextus, n'est pas attestée chez Pyrrhon, et Conche y voit un héritage d'Arcésilas plutôt qu'une notion proprement pyrrhonienne. Pyrrhon fut, selon le mot de Brochard, une sorte de saint sous l'invocation duquel le scepticisme se plaça ; les sceptiques tardifs lui prêtèrent peu à peu des thèses qui n'étaient pas exactement les siennes.
La redécouverte de Sextus Empiricus à la Renaissance fit de « pyrrhonisme » un nom commun de la langue philosophique, désignant le doute poussé à son terme. Montaigne, dont l'« Apologie de Raymond Sebond » nourrit des arguments sceptiques une critique de la présomption humaine, contribua plus que personne à cette fortune ; la « crise pyrrhonienne » des {{e|XVI}} et {{e|XVII}} siècles aiguillonna Descartes, qui voulut vaincre le doute en le poussant à l'extrême, puis Pascal, Bayle et Hume. Par un dernier paradoxe, l'homme qui n'écrivit rien et tint le langage en suspicion se trouve ainsi à l'origine d'une des plus longues traditions de la philosophie occidentale : celle qui, périodiquement, demande à la raison de justifier ses prétentions au savoir.
== Notes et références ==
{{references|colonnes=2}}
== Bibliographie ==
* Diogène Laërce, ''Vies et doctrines des philosophes illustres'', livre IX, 61-108, trad. sous la direction de Marie-Odile Goulet-Cazé, Paris, Le Livre de Poche, coll. « La Pochothèque », 1999.
* Fernanda Decleva Caizzi, ''Pirrone. Testimonianze'', Naples, Bibliopolis, 1981 (recueil de référence : 95 témoignages anciens, traduits et commentés).
* Victor Brochard, ''Les Sceptiques grecs'', Paris, Imprimerie nationale, 1887 ; rééd. présentée par Jacques Brunschwig, Paris, Le Livre de Poche, 2002.
* Léon Robin, ''Pyrrhon et le scepticisme grec'', Paris, PUF, 1944.
* Marcel Conche, ''Pyrrhon ou l'apparence'', Paris, PUF, coll. « Perspectives critiques », 1994 (première édition : Villers-sur-Mer, Éditions de Mégare, 1973).
* Jacques Brunschwig, « Pyrrhon », dans Monique Canto-Sperber (dir.), ''Philosophie grecque'', Paris, PUF, 1997, p. 466-472.
* Richard Bett, ''Pyrrho, his Antecedents, and his Legacy'', Oxford, Oxford University Press, 2000.
* Carlos Lévy, ''Les scepticismes'', Paris, PUF, coll. « Que sais-je ? », 2008.
* Richard Bett (dir.), ''The Cambridge Companion to Ancient Scepticism'', Cambridge, Cambridge University Press, 2010 (en particulier le chapitre de Svavar Hrafn Svavarsson, « Pyrrho and Early Pyrrhonism », p. 36-57).
* Brigitte Pérez, notice « Pyrrhon d'Élis » (P 327), dans Richard Goulet (dir.), ''Dictionnaire des philosophes antiques'', t. V b, Paris, CNRS Éditions, 2012, p. 1749-1771.
{{AutoCat}}
[[Catégorie:Philosophe]]
{{DEFAULTSORT:Pyrrhon}}
8wqx5cwu9zs2j4yixcrpaigge332jly
Dictionnaire de philosophie/Pierre Kropotkine
0
83949
768683
768222
2026-06-26T04:31:11Z
PandaMystique
119061
Bot : ajout du paramètre lecture=oui au modèle {{DicoPhilo}}
768683
wikitext
text/x-wiki
{{DicoPhilo|Pierre Kropotkine|lecture=oui}}
[[Fichier:Atelier Nadar - Pierre Kropotkine.jpg|vignette|upright=1.1|Pierre Kropotkine, photographie de l'atelier Nadar.]]
Prince russe issu de la lignée de Rurik, géographe primé par les sociétés savantes de son temps, officier formé à la cour impériale, Pierre Kropotkine (1842-1921) employa toute son énergie à montrer que les sociétés humaines pouvaient se gouverner sans gouvernants. On le présente souvent comme « le prince anarchiste », formule commode qui dit la rupture sociale mais en masque l'essentiel : Kropotkine fut d'abord celui qui voulut donner à l'[[Dictionnaire de philosophie/Anarchisme|anarchie]] une assise scientifique. Là où d'autres en faisaient une exigence morale ou un cri de révolte, il entreprit de l'enraciner dans la nature elle-même, en s'appuyant sur la biologie, l'histoire et l'observation.
Sa thèse centrale tient en un mot, qu'il emprunte au langage courant pour lui donner un sens technique : l'entraide. Contre l'idée, dominante à la fin du XIX{{e}} siècle, que la nature serait une arène où chaque individu lutte contre tous, il soutient que la coopération à l'intérieur d'une même espèce constitue un facteur de l'évolution, souvent plus important que la concurrence. De cette observation naturaliste, il tire une conséquence politique : si la sociabilité est inscrite dans le vivant, alors la contrainte de l'État n'a rien de nécessaire. Deux livres portent cette double ambition : ''La Conquête du pain'' (1892), où il dessine une société communiste sans pouvoir central, et ''L'Entraide, un facteur de l'évolution'' (1902), où il argumente la biologie de la coopération.
== Repères biographiques ==
[[Fichier:Peter Kropotkin 1864.png|vignette|Kropotkine en 1864, durant ses années sibériennes.]]
Kropotkine naît à Moscou le 9 décembre 1842, dans une famille de la haute aristocratie possédant des serfs. L'enfance se passe au contact de cette servitude domestique, dont il gardera une mémoire précise et indignée : c'est là, plus tard, qu'il situera l'une des sources de sa sensibilité sociale. Entré à près de quinze ans au corps des Pages, l'école la plus fermée de l'Empire, il côtoie le tsar Alexandre II, dont il devient brièvement le page de chambre. Il en sort en 1862 avec les meilleures notes et une décision qui consterne son entourage : au lieu d'une carrière dans la garde, il demande à servir en Sibérie.
Les cinq années sibériennes (1862-1867) forment le tournant. Officier cosaque sur l'Amour, il parcourt à cheval des milliers de kilomètres, lève des cartes, étudie les glaciers et la faune. Deux apprentissages s'y croisent. Le premier est scientifique : ses relevés sur la structure des montagnes d'Asie et sur l'extension des anciennes glaciations lui vaudront une réputation de géographe. Le second est politique, et il le formulera sans détour dans ses souvenirs : l'expérience de l'administration impériale lui apprend l'impuissance du commandement. C'est en Sibérie, écrira-t-il, qu'il perdit toute foi dans la discipline d'État, ayant découvert que rien d'utile ne s'accomplit par l'ordre et la punition, mais seulement par l'accord de volontés convergentes. Un poète exilé, Mikhaïlov, lui met alors entre les mains le ''Système des contradictions économiques'' de Proudhon<ref>Pierre Kropotkine, ''Autour d'une vie. Mémoires''. Sur cet épisode, voir George Woodcock et Ivan Avakumovic, ''The Anarchist Prince'', Londres, 1950, p. 57-58.</ref>.
De retour dans la capitale, il étudie les mathématiques, travaille pour la Société géographique russe et acquiert une notoriété savante. Membre actif du comité chargé de préparer une expédition arctique, il déduit vers 1871, de la dérive des glaces et des courants au large de la Nouvelle-Zemble, l'existence de terres encore inconnues plus au nord ; cette hypothèse parut confirmée deux ans plus tard par une expédition autrichienne conduite par Julius Payer et Carl Weyprecht, qui baptise l'archipel « Terre François-Joseph ». C'est aussi en 1871 que la Société géographique russe lui offre son secrétariat, c'est-à-dire une vie tout entière vouée à la recherche. Il refuse. La crise morale qui motive ce refus éclaire tout le reste : de quel droit, se demande-t-il, jouir des plaisirs élevés du savoir quand tout, autour de lui, n'est que misère et lutte pour un morceau de pain ? La science lui paraît un privilège tant qu'elle n'est pas le bien de tous<ref>Pierre Kropotkine, ''Autour d'une vie. Mémoires'' ; passage cité et commenté par Ángel Cappelletti, ''El pensamiento de Kropotkin''.</ref>.
En 1872, un voyage en Suisse précipite sa conversion. Il y rencontre la Fédération jurassienne, l'aile antiautoritaire de l'Association internationale des travailleurs, et noue des liens avec James Guillaume puis avec l'horloger Adhémar Schwitzguébel, qui l'introduit auprès des ouvriers de Sonvilier. L'indépendance des horlogers du Jura, leur façon de penser par eux-mêmes et de s'organiser sans chefs, l'impressionne vivement : c'est au sortir de ces montagnes, dira-t-il, que ses vues sur le socialisme se fixèrent et qu'il se sut anarchiste. Il rentre en Russie chargé de littérature socialiste, rejoint le cercle Tchaïkovski et porte la propagande chez les ouvriers et les paysans. Arrêté en mars 1874, au lendemain d'une conférence donnée devant la Société géographique sur les formations glaciaires, il passe deux ans au secret dans la forteresse Pierre-et-Paul, où sa santé se délabre. Transféré à l'hôpital militaire, il s'en évade en 1876, et cette évasion devient aussitôt légendaire<ref>Brian Morris, ''Kropotkin'', Oakland, PM Press, 2018, premier chapitre ; Martin A. Miller, ''Kropotkin'', Chicago, 1976, p. 113-124.</ref>.
Commence alors un exil de plus de quarante ans. Il fonde et anime le journal ''Le Révolté'', d'abord à Genève. L'assassinat d'Alexandre II en 1881 entraîne l'expulsion des réfugiés russes ; Kropotkine passe en France, où un procès l'envoie à la prison de Clairvaux de 1883 à 1886. Sa détention émeut le monde savant : le naturaliste Alfred Russel Wallace, le poète Swinburne, l'écrivain et artisan socialiste William Morris et nombre de collaborateurs de l{{'}}''Encyclopædia Britannica'' signent une pétition pour sa libération, que Victor Hugo remet en personne au ministre de la Justice ; Renan et l'Académie des sciences de Paris mettent leurs bibliothèques à sa disposition. Signe d'une rivalité intellectuelle durable, Huxley refuse d'y joindre son nom<ref>Ángel Cappelletti, ''El pensamiento de Kropotkin'', p. 388 ; sur les signataires, le refus de Huxley et la remise de la pétition au ministre par Victor Hugo, George Woodcock et Ivan Avakumovic, ''The Anarchist Prince'', 1950, p. 193-195.</ref>. Libéré, il s'installe en Angleterre, où il écrira ses grands livres.
L'unité du mouvement se brise en 1914. Hostile de longue date au militarisme, Kropotkine n'est pourtant pas pacifiste : il redoute que la victoire de l'Allemagne impériale n'écrase en Europe toute espérance d'émancipation. En 1916, il signe avec Jean Grave, Tcherkesov et quelques autres le « Manifeste des Seize », qui prend parti pour les Alliés. La majorité de ses compagnons, Malatesta, Emma Goldman, Alexandre Berkman, Rudolf Rocker, y voient un reniement et s'éloignent de lui<ref>Martin A. Miller, ''Kropotkin'', Chicago, 1976, p. 230-238 ; Brian Morris, ''Kropotkin'', 2018.</ref>. La révolution de février 1917 ouvre enfin le retour. En juin, après plus de quarante ans d'exil, Kropotkine rentre en Russie ; une foule d'environ soixante mille personnes l'accueille à Petrograd, en pleine nuit. Mais l'espoir tourne court. Kerenski lui propose un ministère, qu'il refuse, comme il refuse tout poste du gouvernement provisoire ; les anarchistes, de leur côté, lui tiennent rigueur de son ralliement à la guerre. Il s'installe à Moscou, puis se retire en 1918 à Dmitrov, petite ville au nord de la capitale, où il travaille avec la coopérative locale et achève son traité d'éthique. En mai 1919, une entrevue avec Lénine, ménagée par Bontch-Brouïevitch et connue surtout par le récit qu'en a laissé ce dernier, met face à face deux conceptions inconciliables de la révolution : Kropotkine plaide pour les coopératives et les syndicats, que Lénine juge « bavardages », tandis que Lénine défend la dictature et la terreur. Il n'offrira au pouvoir, prévient-il, qu'une aide négative : lui signaler ses fautes. Ses dernières interventions publiques seront des protestations, l'une contre la mainmise de l'État sur la presse, l'autre contre la pratique des otages. Il meurt à Dmitrov le 8 février 1921. Ses funérailles rassemblent des dizaines de milliers de personnes et forment le dernier grand rassemblement anarchiste que la Russie soviétique tolérera : des militants obtiennent une libération provisoire de prison pour y assister<ref>Brian Morris, ''Kropotkin'', 2018 ; Martin A. Miller, ''Kropotkin'', 1976, p. 240-247 ; le compte rendu de l'entretien provient de Vladimir Bontch-Brouïevitch, témoin proche du pouvoir soviétique, et la datation des rencontres avec Lénine demeure discutée.</ref>.
== L'entraide comme facteur d'évolution ==
Pour saisir la portée de l'entraide, il faut se replacer dans le climat intellectuel des années 1880. Le darwinisme, vulgarisé et durci, sert alors à justifier la concurrence économique : la nature serait un champ de bataille où survit le plus apte, et la société devrait s'y conformer. En 1888, le biologiste Thomas Huxley publie un essai retentissant, ''The Struggle for Existence in Human Society'', qui décrit le monde vivant comme une lutte de gladiateurs et la vie tribale comme une guerre de chacun contre tous, à la manière de Hobbes. C'est à cette image que Kropotkine veut répondre.
Sa réponse ne sort pas d'un cabinet, mais des forêts de Sibérie. Lecteur de ''L'Origine des espèces'', il y avait cherché en vain la concurrence acharnée entre animaux d'une même espèce que Darwin lui avait appris à attendre. Avec le zoologiste Ivan Poliakov, son compagnon d'exploration dans les régions du Vitim et de l'Amour, il avait parcouru ces terres l'esprit encore plein de Darwin, sans y rencontrer cette guerre intestine. Ce qu'il observait, c'était autre chose : des adaptations contre le froid et les ennemis, des oiseaux et des ruminants s'entraidant lors des migrations, des espèces luttant ensemble contre la disette plutôt que les unes contre les autres. Une conférence du zoologiste russe Karl Kessler, en 1880, lui fournit la formule qu'il fera sienne : l'appui mutuel est une loi de la nature au même titre que la lutte, mais, pour le progrès de l'espèce, il importe davantage. Kropotkine remarque d'ailleurs que les darwiniens russes, moins marqués par Malthus que leurs collègues anglais, avaient depuis longtemps cette intuition<ref>Pierre Kropotkine, ''L'Entraide, un facteur de l'évolution'', premier chapitre ; sur le contexte scientifique, Brian Morris, ''Kropotkin'', 2018.</ref>.
Le livre déploie cette thèse en une vaste fresque, des sociétés animales aux « sauvages », des « barbares » à la cité médiévale, jusqu'aux pratiques d'entraide des sociétés modernes. L'argument biologique est précis : ce ne sont pas les individus les plus rivaux qui survivent, mais les espèces les plus sociables, parce que la coopération conserve mieux la vie et favorise les facultés qui assurent l'avenir du groupe. La conclusion renverse le mot d'ordre que Spencer avait forgé et que le darwinisme social reprit à son compte : « les plus aptes » sont les plus solidaires.
Kropotkine se garde pourtant de l'excès inverse. Il refuse l'idylle de Rousseau autant que la guerre de Huxley : la nature n'est ni paix pure ni carnage. Surtout, il précise que l'entraide n'est qu'un facteur de l'évolution parmi d'autres, et reconnaît la part de l'affirmation individuelle. À un correspondant qui voulait modifier le titre de son livre, il rappelle qu'il n'a jamais prétendu expliquer comment l'entraide agit sur l'évolution, seulement montrer qu'elle y agit<ref>Caroline Cahm, ''Kropotkin and the Rise of Revolutionary Anarchism, 1872-1886'', Cambridge, 1989, p. 5-6.</ref>. Une part de sa biologie a depuis vieilli, en particulier son lamarckisme et sa croyance en l'hérédité des caractères acquis, que la génétique du XX{{e}} siècle, à laquelle travailla notamment Theodosius Dobzhansky, devait écarter. L'intuition centrale, en revanche, a tenu : Ashley Montagu a jugé que ses données et ses analyses résistaient bien à l'examen, et Stephen Jay Gould lui a rendu justice dans un essai au titre éloquent, « Kropotkin Was No Crackpot »<ref>Sur le lamarckisme de Kropotkine et la synthèse génétique ultérieure, comme sur le jugement d'Ashley Montagu, Brian Morris, ''Kropotkin'', 2018 ; Stephen Jay Gould, « Kropotkin Was No Crackpot », repris dans ''Bully for Brontosaurus'', 1991.</ref>.
== Le communisme anarchiste ==
[[Fichier:La conquête du pain.jpg|vignette|Couverture de ''La Conquête du pain'' (1892), où Kropotkine expose le communisme anarchiste.]]
Kropotkine n'a pas inventé l'anarchisme, ni même le communisme anarchiste, dont il fut plutôt le théoricien le plus écouté. Son apport tient à un déplacement précis à l'intérieur du socialisme antiautoritaire. Les héritiers de Bakounine se disaient « collectivistes » : ils voulaient socialiser les moyens de production, mais laissaient subsister une rétribution proportionnée au travail fourni. Au congrès de la Fédération jurassienne tenu à La Chaux-de-Fonds en octobre 1880, il pèse de tout son poids pour faire adopter un autre mot et une autre idée. Le terme de collectivisme, juge-t-il, garde l'odeur du salariat ; il faut lui préférer le communisme, c'est-à-dire la distribution selon les [[Dictionnaire de philosophie/Besoin|besoins]]. Il avait préparé le terrain, écrivant à Reclus et à Cafiero pour s'assurer de leur appui ; mais devant des délégués hésitants, que le mot effrayait, la résolution ne l'emporta que grâce au discours de Carlo Cafiero. La formule elle-même n'était pas neuve : Dumartheray l'avait esquissée dès 1876, et l'idée mûrissait alors dans les groupes italiens et espagnols<ref>Sur le congrès de La Chaux-de-Fonds et le rôle de Cafiero, Caroline Cahm, ''Kropotkin and the Rise of Revolutionary Anarchism, 1872-1886'', Cambridge, 1989, p. 51-64 ; Brian Morris, ''Kropotkin'', 2018.</ref>.
L'argument de fond est une critique de la propriété appuyée sur l'histoire de la richesse. Toute production, soutient Kropotkine, est sociale et héritée : chaque parcelle de sol cultivée en Europe a été arrosée de la sueur de plusieurs générations. Les routes, les machines, le savoir accumulé, rien de tout cela n'est l'œuvre d'un seul homme. Dès lors, comment mesurer la part de chacun ? La question est sans réponse, et c'est là que le salariat se révèle injuste : il prétend évaluer une contribution individuelle qui se perd dans un tissu collectif. « De quel droit, demande-t-il, s'approprier la moindre parcelle de cet immense ensemble et dire : ceci est à moi, non à vous ? »<ref>Pierre Kropotkine, ''La Conquête du pain'', premier chapitre (« Nos richesses »).</ref> Kropotkine en tire la formule qu'il reprend au socialisme français : de chacun selon ses moyens, à chacun selon ses besoins. À ceux qui réclament une part proportionnelle, il oppose l'image simple du tas commun où chacun puise.
''La Conquête du pain'' (1892) tire les conséquences pratiques. Une révolution, pour Kropotkine, doit assurer dès le premier jour le bien-être de tous, faute de quoi elle perdra le soutien populaire. Il ne suffit donc pas de socialiser les usines, comme le veulent les collectivistes et les sociaux-démocrates ; il faut socialiser aussi le logement, les vivres, les vêtements, qui sont pour le travailleur des instruments de production au même titre que l'outil. Abolir la propriété privée des moyens de production tout en la maintenant sur les biens de consommation, c'est, dit-il, asseoir la société sur deux principes contraires : elle retombera dans la propriété ou ira jusqu'au communisme. Contre l'objection de la pénurie, il répond par le rationnement local au début, puis par l'essor d'une production que la science rend possible. Et il écarte une caricature : le communisme qu'il défend n'est pas la grande exploitation autoritaire, la charrue à vapeur passée sur les jardins et les vergers ; on ne touchera pas au lopin du paysan qui le cultive lui-même, avec ses enfants, sans salariés<ref>Pierre Kropotkine, ''La Conquête du pain'', 1892 ; cf. Caroline Cahm, ''Kropotkin and the Rise of Revolutionary Anarchism'', 1989, p. 9.</ref>.
À l'économie politique, dans sa version libérale comme dans certaines lectures marxistes, il oppose encore un renversement de méthode. Les économistes partent de la production telle qu'elle existe, puis cherchent comment écouler les marchandises ; il faut partir des besoins, soutient Kropotkine, car c'est le besoin qui pousse d'abord l'homme à produire. De là sa réfutation de la théorie de la surproduction : les crises ne viennent pas de ce qu'on produit trop, mais de ce que les masses, tenues dans la pauvreté par l'organisation marchande, consomment trop peu. Loin de produire en excès, l'époque produirait plutôt trop peu, si l'on mesurait la production aux besoins réels qu'elle laisse insatisfaits<ref>Pierre Kropotkine, ''La Conquête du pain'' ; George Woodcock et Ivan Avakumovic, ''The Anarchist Prince'', 1950, p. 318-319.</ref>.
== L'intégration du travail et l'éducation intégrale ==
Le même refus de la spécialisation gouverne ''Champs, usines et ateliers'' (1899). Kropotkine y attaque la division du travail qu'Adam Smith avait érigée en principe. Cette division, écrivait-il déjà dans ''La Conquête du pain'', revient à étiqueter et marquer les hommes pour la vie : l'un épissera des cordages dans une fabrique, l'autre poussera d'énormes paniers de charbon au fond d'une mine, sans que nul comprenne jamais la machine, le métier ni la mine dans leur ensemble ; ainsi se perdent le goût du travail et la faculté d'inventer qui, aux débuts de l'industrie moderne, avaient créé les machines dont on s'enorgueillit. À la division, il oppose l'intégration : réunir l'agriculture et l'industrie, le travail du cerveau et celui de la main, décentraliser la production, faire en sorte que chaque région produise et consomme une grande variété de biens grâce à une agriculture intensive et à de petits ateliers<ref>Pierre Kropotkine, ''La Conquête du pain'' et ''Champs, usines et ateliers'' ; pour la formule citée, George Woodcock et Ivan Avakumovic, ''The Anarchist Prince'', 1950, p. 322 et 322-327.</ref>.
Cette intégration appelle une autre école. Kropotkine se méfie de l'« éducation technique » réclamée de son temps, qui ne ferait que pérenniser la coupure entre savants et manœuvres. Il lui préfère l'éducation intégrale : que chaque être humain, sans distinction de naissance, joigne une connaissance réelle des sciences à la maîtrise d'un métier manuel. L'idée n'a rien d'utopique à ses yeux, et il convoque l'histoire des sciences pour le prouver : Galilée taillait ses lunettes, Leibniz inventait des machines, Linné devint botaniste en cultivant le jardin de son père. Le travail manuel, loin de nuire à la pensée abstraite, la nourrit. On reconnaît là une tradition continentale : l'expression même d'éducation intégrale remonte à Fourier, l'idée fut portée par Bakounine et par les pédagogues libertaires Paul Robin et Jean Grave, et Kropotkine, lecteur aussi de William Morris, l'introduit le premier dans le débat éducatif britannique<ref>Pierre Kropotkine, ''Champs, usines et ateliers'' ; Ruth Kinna, ''Kropotkin. Reviewing the Classical Anarchist Tradition'', Édimbourg, 2016, p. 133-134 ; sur la nouveauté de cette position dans les milieux éducatifs britanniques, Brian Morris, ''Kropotkin'', 2018.</ref>.
== Une morale tirée de la nature ==
Le dernier grand chantier de Kropotkine, resté inachevé, est une éthique. Publié après sa mort, son traité (dont nous n'avons que le premier tome) veut fonder la [[Dictionnaire de philosophie/Morale|morale]] sur les résultats des sciences naturelles, sans recourir ni à la religion ni à aucune transcendance. L'entreprise prolonge ''L'Entraide'' : si la sociabilité est un fait de nature, elle peut servir de sol aux sentiments moraux.
Kropotkine décrit alors une série ascendante en trois degrés. À la base, l'entraide, c'est-à-dire l'instinct social que nous partageons avec les animaux grégaires et que Darwin tenait pour plus constant encore que l'instinct de conservation. Au-dessus, la justice, qu'il entend au sens de l'équité, soit la reconnaissance de l'[[Dictionnaire de philosophie/Égalité|égalité]] entre les hommes ; il en crédite Proudhon, qui avait fait de la justice le principe premier de toute morale en la dressant contre l'injustice réelle. Au sommet, ce qu'il nomme la magnanimité, le don de soi sans attente de retour, propre à l'homme et qui mérite seul le nom de morale. La nature, résume-t-il, est le premier instructeur moral de l'homme<ref>Pierre Kropotkine, ''L'Éthique'' ; George Crowder, ''Classical Anarchism. The Political Thought of Godwin, Proudhon, Bakunin, and Kropotkin'', Oxford, 1991, p. 162-163.</ref>.
Cette généalogie naturaliste se construit contre deux adversaires. Contre Kant d'abord. Kropotkine reconnaît au devoir kantien, à l'impératif catégorique, une grandeur certaine ; mais il lui reproche de laisser entière la question de l'origine du sens moral. Pourquoi obéir à la loi morale ? D'où vient ce commandement mystérieux ? En traitant la morale comme un domaine séparé du monde naturel, Kant, selon lui, se condamne à la faire dériver d'une source quasi divine, sans l'enraciner dans ce sentiment d'égalité qui, pour Kropotkine, fait le fond de la justice<ref>Pierre Kropotkine, ''L'Éthique'' ; voir Brian Morris, ''Kropotkin'', 2018, sur la lecture kropotkinienne de Kant.</ref>. Contre l'utilitarisme ensuite : Kropotkine ne le tient pas pour faux, mais pour court. La morale est plus que la pesée prudente des plaisirs ; le seul calcul du plaisir peut même conduire aux pires conduites.
Reste l'influence qu'il revendique, celle de Jean-Marie Guyau et de son ''Esquisse d'une morale sans obligation ni sanction'' (1884). Guyau partait de la vie comme expansion : nous sentons en nous plus de force, plus d'amour, plus de larmes que nous n'en pouvons employer pour nous-mêmes, et de ce trop-plein naît le mouvement vers autrui, comme la plante fleurit sans y être contrainte. Le devoir n'est alors que la conscience d'un pouvoir. Kropotkine adopte cette morale de la surabondance, mais lui ajoute ce que Guyau avait laissé dans l'ombre : la dimension sociale. La morale, conclut-il, est le produit conjoint de l'instinct, du sentiment et de la raison ; elle ne se réduit pas aux faits de la vie, elle s'y appuie<ref>Jean-Marie Guyau, ''Esquisse d'une morale sans obligation ni sanction'', 1884 ; sur l'usage qu'en fait Kropotkine, Ruth Kinna, ''Kropotkin'', 2016, p. 150-151, et Ángel Cappelletti, ''El pensamiento de Kropotkin''.</ref>.
== L'anarchie comme conception scientifique du monde ==
Ce souci de fonder en nature vaut aussi pour la politique. Kropotkine présente l'anarchie non comme une doctrine parmi d'autres, mais comme une vision d'ensemble du monde, ce que les penseurs allemands nomment ''Weltanschauung'', appuyée sur la méthode des sciences de la nature. L'anarchie, écrit-il, ne reconnaît pas d'autre méthode que celle des sciences naturelles. Son objet est de se former une idée de l'univers entier, y compris l'homme et la société, par la voie de l'induction et de la déduction, comme on étudie une fleur ou une ruche. Puisque l'esprit humain et la vie sociale sont des phénomènes naturels, rien ne justifie de changer de méthode en passant de la fleur à l'homme, ou de la colonie de castors à la cité<ref>Pierre Kropotkine, ''La Science moderne et l'anarchie'' ; Richard Morgan, ''The Making of Kropotkin's Anarchist Thought'', Londres, 2020, p. 74-75.</ref>.
Cette position le sépare de deux côtés. D'un côté, des [[Dictionnaire de philosophie/Métaphysique|métaphysiques]] de la religion et de l'idéalisme allemand : il rejette aussi bien le dieu tout-puissant que l'Esprit universel de Hegel. De l'autre, de la [[Dictionnaire de philosophie/Dialectique|dialectique]] marxiste, qu'il tient pour une survivance de cette même métaphysique. Il avait pourtant emprunté à Comte et à Spencer l'idée d'une philosophie synthétique, embrassant d'un même mouvement la nature et la société, sans les suivre lorsqu'ils faisaient de la lutte pour la vie une loi sociale. La science, à ses yeux, ne livre jamais de vérités définitives, seulement une approximation toujours révisable, à la manière dont l'entendait le physiologiste Claude Bernard. Son attachement à la science alla d'ailleurs jusqu'à l'aveuglement : en 1913, il s'en prit vivement à Bergson, coupable selon lui de rabaisser la science en accordant trop à l'intuition dans la découverte<ref>Sur cette attaque de 1913, Caroline Cahm, ''Kropotkin and the Rise of Revolutionary Anarchism'', 1989, p. 2 ; sur l'ambition synthétique héritée de Comte et Spencer, Cahm, ''Kropotkin and the Rise of Revolutionary Anarchism'', p. 3.</ref>.
Sa critique de [[Dictionnaire de philosophie/Karl Marx|Marx]] procède du même principe. ''Le Capital'', concède-t-il, est un admirable pamphlet, mais sa portée scientifique lui paraît nulle : le matérialisme historique est à ses yeux [[Dictionnaire de philosophie/Déterminisme|déterministe]], donc paralysant pour l'action, et fondé sur des prédictions que les faits ont démenties<ref>Lettre de Kropotkine à James Guillaume (1903), citée par Caroline Cahm, ''Kropotkin and the Rise of Revolutionary Anarchism'', 1989, p. 3.</ref>. Il y a là un point que ses propres lecteurs lui ont retourné. À vouloir faire de l'anarchie une science, Kropotkine retombe parfois dans le défaut qu'il dénonçait chez Huxley, Spencer ou Marx : il s'appuie moins sur des lois vérifiables que sur une analogie séduisante entre la nature et la société. L'historien Martin Miller a noté que cet « anarchisme scientifique » repose sur une métaphore puissante, mais fragile<ref>Martin A. Miller, ''Kropotkin'', Chicago, 1976, p. 189.</ref>.
== L'État, la commune et le fédéralisme ==
L'État, pour Kropotkine, ne se confond pas avec la société. C'est une formation historique, née pour protéger les privilèges d'une minorité, et qu'il convient d'étudier froidement, avec la même disposition d'esprit que s'il s'agissait d'une société de fourmis ou d'abeilles<ref>Pierre Kropotkine, « L'État, son rôle historique » et ''La Science moderne et l'anarchie'' ; pour la citation sur la méthode, Richard Morgan, ''The Making of Kropotkin's Anarchist Thought'', 2020, p. 74.</ref>. Cette approche le conduit à relire le Moyen Âge à rebours des Lumières dont il se réclame pourtant. Dans les cités libres, fédérations de quartiers et de guildes confédérées entre elles, il voit la forme la plus aboutie d'organisation sans État : entraide, arbitrage à la place du jugement, prix fixés d'un commun accord, approvisionnement assuré par la commune. Et ces villes savaient s'unir : la ligue lombarde, les ligues rhénane et toscane, les vieux cantons suisses montrent à ses yeux que des communes pouvaient se fédérer librement, par pacte et sans se soumettre à un centre. Jamais, ose-t-il écrire, ni avant ni depuis, l'humanité ne connut un bien-être pour tous comparable à celui des cités du Moyen Âge.
L'État moderne, militaire et monarchique, se serait édifié du XVI{{e}} au XVIII{{e}} siècle sur les ruines de ces communes. La bourgeoisie, ensuite, n'a pas détruit cet appareil : elle en a hérité et l'a transformé à son profit. De là vient la défiance de Kropotkine envers tout « État ouvrier ». Confier à l'État la terre, les mines, les banques et les usines, comme le proposent les socialistes autoritaires, ce serait forger un instrument de tyrannie plus puissant encore, un capitalisme d'État où le pouvoir passe du capitaliste au bureaucrate. Les classes opprimées qui s'emparent de l'État, prévient-il, deviennent à leur tour des classes oppressives<ref>Ángel Cappelletti, ''El pensamiento de Kropotkin'' ; Pierre Kropotkine, ''La Science moderne et l'anarchie''.</ref>. Le progrès est ailleurs : dans la décentralisation, territoriale et fonctionnelle, dans la libre fédération des communes et des groupes, dans une organisation qui monte de la base vers le sommet et de la périphérie vers le centre, au rebours de la hiérarchie existante. La tournure que prit la révolution russe lui parut, jusqu'à sa mort, donner raison à ces avertissements.
== Tensions et postérité ==
L'édifice de Kropotkine n'a pas échappé à la critique, et c'est sur son point d'appui même, la nature, qu'elle a porté. Si la morale et la politique se déduisent de l'ordre naturel, encore faut-il que la nature enseigne quelque chose en matière de valeurs. Or rien n'est moins sûr. George Crowder a montré que tout l'anarchisme classique, Kropotkine compris, repose sur un « naturalisme éthique » : l'idée que la valeur est inscrite dans le tissu du monde et que la science l'y découvrira. Cette idée se heurte à l'objection de Mill, selon laquelle la conformité à la nature n'a aucun rapport avec le bien et le mal. Entre le fait et la valeur, entre ce qui est et ce qui doit être, la science moderne a plutôt creusé un fossé qu'elle ne l'a comblé. La promesse de lire dans le vivant les règles de la conduite humaine paraît, à cette lumière, intenable<ref>George Crowder, ''Classical Anarchism'', 1991, p. 183-184, citant John Stuart Mill, ''La Nature''.</ref>.
Ses défenseurs répondent que l'on simplifie sa position. Kropotkine, fait observer Brian Morris, prétendait fonder la morale sur les faits de la vie, non l'y réduire ; il rejetait la séparation rigide du fait et de la valeur, et tenait la connaissance scientifique pour une série d'approximations, non pour un dogme. Sa naturalisation de l'éthique serait ainsi moins naïve qu'il n'y paraît : elle ne tire pas un devoir d'un fait, elle reconnaît dans la sociabilité le terrain où poussent les sentiments moraux<ref>Brian Morris, ''Kropotkin'', PM Press, 2018.</ref>. Le débat reste ouvert, et il déborde Kropotkine : c'est celui de tout naturalisme moral.
On lui a reproché aussi un optimisme trop confiant, sur la bonté humaine comme sur l'abondance prochaine. Son influence a pourtant traversé le siècle. Les travaux sur la coopération, l'[[Dictionnaire de philosophie/Altruisme|altruisme]] et la théorie des jeux évolutionnaire lui ont donné en partie raison. L'écologie sociale de Murray Bookchin, la géographie attentive aux communautés, les réflexions sur l'autogestion et le municipalisme lui doivent beaucoup. Et le tour autoritaire que prit la révolution russe, qu'il dénonça jusqu'à sa mort, a donné à sa critique de l'État un écho qu'elle n'avait pas de son vivant. Le « prince anarchiste » aura voulu, en somme, ce que peu osent encore : penser ensemble la nature et la liberté.
== Notes et références ==
{{references|colonnes=2}}
== Œuvres principales ==
=== Écrits de Kropotkine ===
* ''Paroles d'un révolté'', recueil d'articles établi par Élisée Reclus, Paris, 1885 ([[s:Paroles d'un révolté|texte sur Wikisource]]).
* ''Aux jeunes gens'', 1880 ([[s:Aux jeunes gens|texte sur Wikisource]]).
* ''La Morale anarchiste'', 1891 ([[s:La Morale anarchiste|texte sur Wikisource]]).
* ''La Conquête du pain'', Paris, 1892 ([[s:La Conquête du pain|texte sur Wikisource]]).
* ''Champs, usines et ateliers'', 1899 ([[s:Champs, usines et ateliers|texte sur Wikisource]]).
* ''Autour d'une vie. Mémoires'', Paris, 1902 (édition originale anglaise : ''Memoirs of a Revolutionist'', 1899).
* ''L'Entraide, un facteur de l'évolution'', 1902 (édition anglaise : ''Mutual Aid: A Factor of Evolution'' ; traduction française, 1906).
* ''La Grande Révolution, 1789-1793'', Paris, 1909 ([[s:La Grande Révolution|texte sur Wikisource]]).
* ''La Science moderne et l'anarchie'', Paris, 1913.
* ''L'Éthique'', publication posthume (édition russe, 1922 ; traduction anglaise, ''Ethics: Origin and Development'', 1924.
=== Études ===
* {{Ouvrage|langue=en|prénom1=Caroline|nom1=Cahm|titre=Kropotkin and the Rise of Revolutionary Anarchism, 1872-1886|lieu=Cambridge|éditeur=Cambridge University Press|année=1989}}
* {{Ouvrage|langue=en|prénom1=George|nom1=Crowder|titre=Classical Anarchism|sous-titre=The Political Thought of Godwin, Proudhon, Bakunin, and Kropotkin|lieu=Oxford|éditeur=Clarendon Press|année=1991}}
* {{Ouvrage|langue=en|prénom1=Ruth|nom1=Kinna|titre=Kropotkin|sous-titre=Reviewing the Classical Anarchist Tradition|lieu=Édimbourg|éditeur=Edinburgh University Press|année=2016}}
* {{Ouvrage|langue=en|prénom1=Martin A.|nom1=Miller|titre=Kropotkin|lieu=Chicago|éditeur=University of Chicago Press|année=1976}}
* {{Ouvrage|langue=en|prénom1=Richard|nom1=Morgan|titre=The Making of Kropotkin's Anarchist Thought|lieu=Londres|éditeur=Routledge|année=2020}}
* {{Ouvrage|langue=en|prénom1=Brian|nom1=Morris|titre=Kropotkin|sous-titre=The Politics of Community|lieu=Oakland|éditeur=PM Press|année=2018}}
* {{Ouvrage|langue=en|prénom1=George|nom1=Woodcock|prénom2=Ivan|nom2=Avakumovic|titre=The Anarchist Prince|sous-titre=A Biographical Study of Peter Kropotkin|lieu=Londres|éditeur=T. V. Boardman|année=1950}}
* {{Ouvrage|langue=es|prénom1=Ángel|nom1=Cappelletti|titre=El pensamiento de Kropotkin|sous-titre=ciencia, ética y anarquía|lieu=Madrid|éditeur=Zero|année=1978}}
* {{Ouvrage|langue=en|prénom1=Pierre|nom1=Kropotkine|responsabilité1=édition de Marshall S. Shatz|titre=The Conquest of Bread and Other Writings|lieu=Cambridge|éditeur=Cambridge University Press|année=1995}}
[[Catégorie:Philosophe]]
{{DEFAULTSORT:Kropotkine, Pierre}}
t25gzqveplstnwupmvefxijxxech81a
Fonctionnement d'un ordinateur/Les circuits incrémenteurs/décrémenteurs
0
83953
768653
768453
2026-06-25T18:08:43Z
Mewtow
31375
/* Le demi-additionneur */
768653
wikitext
text/x-wiki
Le circuit '''incrémenteur''' incrémente un nombre entier. Plus précisément, il prend en entrée un nombre entier, et fournit en entier le nombre suivant, il lui ajoute 1. Une telle opération peut paraitre triviale et peu intéressante. Cependant, elle est très utilisée et est absolument cruciale pour créer des compteurs, des circuits capables de compter ou décompter, qui auront droit à un chapitre dédié. De plus, au-delà de leur utilisation dans les compteurs, les incrémenteurs étaient très utilisés sur les premiers processeurs 8 bits, comme le Z-80, le 6502, les premiers processeurs x86 comme le 8008, le 8086, le 8085, et bien d'autres.
Il existe aussi des ''circuits décrémenteurs'', qui décrémentent un opérande, ainsi que des ''circuits incrémenteur-décrémenteur'', qui peuvent incrémenter ou décrémenter, selon comment on les configure. Je ne vais pas détailler ces circuits plus que ça, car de tels circuits sont assez rares, comparé à un circuit incrémenteur simple.
==Le demi-additionneur==
Le circuit incrémenteur effectue l'opération suivante :
<math>a_7</math> <math>a_6</math> <math>a_5</math> <math>a_4</math> <math>a_3</math> <math>a_2</math> <math>a_1</math> <math>a_0</math>
+ 0 0 0 0 0 0 0 1
Un incrémenteur basique doit donc faire une addition pour chaque colonne, et précisément une addition de deux bits. Il se trouve que la table d'addition est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Un circuit capable d'additionner deux bits est appelé un '''demi-additionneur'''. Il dispose d'une sortie S pour la somme, et C pour la retenue (''carry'' an anglais). Il est très simple à construire avec les techniques vues dans les premiers chapitres. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! !! Retenue sortante !! Bit de somme
|-
||0||0|| ||0||0
|-
||0||1|| ||0||1
|-
||1||0|| ||0||1
|-
||1||1|| ||1||0
|}
On voit immédiatement que la colonne des retenues donne une porte ET, alors que celle du bit de somme est calculé par un XOR.
{| class="flexible"
|[[File:1-bit half-adder.svg|class=transparent|centre|Demi-addtionneur.]]
|[[File:Half-adder.svg|centre|class=transparent|Circuit d'un demi-addtionneur.]]
|}
Il existe beaucoup d'autres implémentations alternatives, qui utilisent moins de portes logiques, calculent la retenue plus rapidement, consomment moins d'énergie, et autres. Et il peu être intéressant d'étudier quelques alternatives, surtout qu'elles serviront plus bas, quand on étudiera le circuit incrémenteur du 8085.
Une source d'économie est liée à la porte XOR. En pratique, une porte XOR est composée en combinant plusieurs portes logiques ET/OU/NOR/NAND ensemble. Et il se trouve qu'il y a des redondances entre celles-ci et les portes utilisées pour calculer la retenue. Par exemple, rappelons qu'une porte XOR peut être construite avec une porte ET et deux portes NOR, comme illustré ci-dessous. Et il se trouve que la porte ET interne calcule la retenue sortante. En supprimant cette redondance, on économise quelques portes logiques. De plus, on se retrouve avec un demi-additionneur qui calcule le bit de somme à partir de la retenue sortante.
[[File:Demi-additionneur avec redondances éliminées.png|centre|vignette|upright=2|Demi-additionneur avec redondances éliminées]]
Le circuit précédent a une interprétation logique. Si vous regardez la table de vérité, vous remarquerez que la somme de deux bits est égale à l'inverse de la retenue sortante, sauf dans le cas où les deux bits additionnés valent zéro. Et le circuit précédent est basé là-dessus. La porte ET calcule la retenue sortante, la première porte NOR détecte sur les deux bits d'entrée valent zéro, et les deux résultats sont combinés pour donner le bit de somme. La combinaison se fait avec la seconde porte NOR.
Une implémentation alternative, qui n'utilise que des portes NOR, est la suivante :
[[File:Demi-additionneur fait avec des portes NOR et NAND.png|centre|vignette|upright=2|Demi-additionneur fait avec des portes NOR et NAND.]]
Une implémentation alternative utilise des portes NAND. Pour rappel, il est possible de créer une porte XOR avec 4 portes NAND. La première d'entre elle fait un NAND entre les deux bits d'entrée, ce qui fait qu'elle calcule l'inverse de la retenue sortante. Le tout est illustré ci-dessous. En théorie, on devrait utiliser une porte NON pour récupérer la retenue correcte. Mais nous verrons plus bas que ce n'est pas une obligation. Nous verrons plus bas un exemple où c'est l'inverse de la retenue qui est utilisée dans les calculs, pour rendre les calculs plus rapides.
[[File:Demi-additionneur fait avec des portes NAND.png|centre|vignette|upright=2|Demi-additionneur fait avec des portes NAND]]
==L'incrémenteur à propagation de retenue==
Maintenant que l'on sait comment additionner deux bits, reprenons l'opération d'incrémentation :
<math>a_7</math> <math>a_6</math> <math>a_5</math> <math>a_4</math> <math>a_3</math> <math>a_2</math> <math>a_1</math> <math>a_0</math>
+ 0 0 0 0 0 0 0 1
------------------------------
Sur la colonne la plus à droite, il doit ajouter un au bit de poids faible. Pour les colonnes suivantes, il faut additionner le bit de l'opérande avec la retenue de la colonne précédente. En clair, on n'additionne que deux bits à chaque colonne : un 1 sur celle tout à droite, la retenue de la colonne précédente pour les autres. Et cela nous donne une idée de comment faire pour créer le circuit incrémenteur.
Il suffit d'utiliser un demi-additionneur par colonne, et de les enchainer les uns à la suite des autres. Chaque demi-additionneur additionne le bit de l'opérande avec la retenue fournie par le demi-additionneur précédent. Le seul qui fait exception est celui pour la colonne de poids faible. Pour celui-là, il doit ajouter 1 au bit de poids faible. Le résultat est appelé un '''incrémenteur à propagation de retenue'''. Il est constitué de demi-additionneurs enchaînés les uns à la suite des autres, du bit de poids faible vers le bit de poids fort.
[[File:Circuit incrémenteur.png|centre|vignette|upright=3|Circuit incrémenteur.]]
Maintenant, regardons le demi-additionneur le plus à droite, celui pour le bit de poids faible. Son entrée de retenue entrante est mise à 1 pour faire l'incrémentation. Quelques incrémenteurs permettent de configurer cette entrée de retenue à 0 ou à 1, ce qui effectue : soit une opération identité (l'opérande est recopié sur la sortie), soit une incrémentation. Un tel circuit est nommé un '''incrémenteur commandable'''. Nous aurons à utiliser une fois ou deux de tels incrémenteurs commandables dans la suite du cours.
L'incrémenteur à propagation de retenue est le plus simple et le plus économe en portes logiques. Mais de tels incrémenteurs sont rarement utilisés. À la place, on leur préfère des incrémenteurs plus rapides, mais qui utilisent plus de portes logiques. De tels incrémenteurs accélèrent le calcul des retenues. En effet, la rapidité d'une incrémentation est limitée par la propagation de la retenue : les retenues commencent à être calculées au bit de poids fort et on doit les calculer une par une, jusqu’à atteindre le bit de poids fort. Et cette "propagation des retenues" prend du temps, d'autant plus de temps que l'opérande est longue. Il y a deux optimisations principales, appelées le ''carry skip'' et l'anticipation de retenue, que nous allons décrire ci-dessous.
==Les incrémenteurs ''carry skip''==
L'optimisation '''''carry skip''''' effectue l'incrémentation, non pas bit par bit, mais par paquets de deux bits. Le résultat est que l'incrémentation est deux fois plus rapide, ou presque. Le circuit incrémenteur est donc composé en enchainant non pas des demi-additionneurs, mais des '''incrémenteurs 2 bits''' qui incrémentent un opérande de deux bits.
[[File:Circuit incrémenteur optimisé, faisant l'incrémentation par paquet de deux bits.png|centre|vignette|upright=2.5|Circuit incrémenteur optimisé, faisant l'incrémentation par paquet de deux bits]]
Reste à concevoir l'incrémenteur 2 bits.
===Les optimisations au niveau des transistors et l'exemple de l'incrémenteur du 8085 d'Intel===
Une première solution part de deux demi-additionneurs et effectue quelques simplifications au niveau des transistors et des portes logiques. Par exemple, l'Intel 8085 a utilisé un circuit optimisé, fabriqué avec des portes NAND et NOR. La raison est qu'avec les technologies de transistors CMOS actuelles, les portes NAND et NOR utilisent moins de transistors que les portes ET et OU. Et cette possibilité a été utilisée pour effectuer des simplifications assez mineures, mais toujours bonnes à prendre. Mais nous ne pouvons pas en parler en détail ici, car nous n'avons pas encore parlé en détail des transistors. Cependant, un exemple bien précis nous est accesible : celui de l’incrémenteur du processeur 8085 d'Intel.
Le processeur 8085 d'Intel est un processeur 8 bits très ancien. Il contenait un incrémenteur 16 bits, qui était utilisé pour calculer des adresses mémoire. Et cet incrémenteur était un incrémenteur à propagation de retenue optimisé. Il utilisait lui aussi des incrémenteurs 2 bits un peu modifiés.
L'idée était que les retenues sortant des colonnes paires étaient inversées, les colonnes impaires faisaient les calculs à partir de cette retenue inversée. Les demi-additionneurs des colonnes impaires n'étaient donc pas les mêmes que ceux des colonnes paires. Leurs tables de vérité sont différentes, leurs circuits aussi, et aucun des deux ne ressemble à un demi-additionneur normal. En réalité, ils sont fabriqués à partir des implémentations vues plus haut, qui utilisent uniquement des portes NAND ou uniquement des portes NOR.
Pour comprendre l'intérêt de faire ainsi, nous devons préciser une chose importante : avec les technologies CMOS utilisées pour les processeurs depuis les années 70, les portes logiques les plus simples sont les portes NON, NOR et NAND. Les portes ET/OU sont fabriquées en combinant des portes NOR/NAND avec une porte NON, ce qui prend plus de transistors.
Un demi-additionneur est donc fabriqué comme illustré ci-dessous, en logique CMOS. Seules les portes pour le calcul de la retenue sont indiquées, la porte XOR pour le bit de somme est fabriquée à partir de plusieurs portes logiques.
[[File:Demi-additionneur en CMOS.png|centre|vignette|upright=2|Demi-additionneur en CMOS]]
Si on enchaine deux demi-additionneurs, cela donne ceci :
[[File:Brique de base de l'incrémenteur du 8085.png|centre|vignette|upright=2|Brique de base de l'incrémenteur du 8085]]
La retenue doit donc traverser quatre portes logiques, dont deux portes NON. En inversant la retenue, les portes en jaune dans le schéma précédent se simplifient. Il suffit d'utiliser la loi de de Morgan pour trouver un circuit équivalent aux portes jaunes, qui n'est autre qu'une porte NOR couplée à une porte NON. Un autre moyen d'obtenir le même résultat est de prendre un demi-additionneur fabriqué avec uniquement des portes NOR, et de retirer la porte NON adéquate. Il faut ensuite corriger l'entrée de retenue de la porte XOR, pour qu'elle fasse l'addition avec la retenue correctement. Le résultat est illustré ci-dessous.
[[File:Incrémenteur 2 bits de l'incrémenteur du 8085.png|centre|vignette|upright=2|Incrémenteur 2 bits de l'incrémenteur du 8085]]
Le circuit utilise le même nombre de portes logiques, il n'économise pas de transistors. Par contre, la retenue n'a plus qu'à traverser deux portes logiques, au lieu de quatre. La propagation de la retenue est donc plus rapide, l'incrémentation est donc plus rapide.
===Les incrémenteurs 2 bits ''carry skip''===
Une solution alternative fabrique des incrémenteurs 2 bits qui calculent séparément les deux bits du résultat et la retenue sortante. La retenue sortante est calculée à partir des bits de l’opérande, sans propager des retenues. Ainsi, la retenue sortante est connue en avance, sans passer par deux demi-additionneurs. Pour créer un tel incrémenteur, le mieux est de partir de sa table de vérité.
{|class="wikitable"
|-
! Opérande !! Retenue entrante !! !! Retenue sortante !! Résultat
|-
| 00 || 0 || || 0 || 00
|-
| 01 || 0 || || 0 || 01
|-
| 10 || 0 || || 0 || 10
|-
| 11 || 0 || || 0 || 11
|-
| colspan="5" |
|-
| 00 || 1 || || 0 || 01
|-
| 01 || 1 || || 0 || 10
|-
| 10 || 1 || || 0 || 11
|-
| 11 || 1 || || 1 || 00
|}
Elle nous dit que la retenue sortante ne vaut 1 que dans un seul cas : les deux bits d'opérande valent 1, la retenue entrante vaut 1. Elle se calcule alors avec une porte ET à trois entrées. Pour les deux bits du résultat, les équations logiques ne donnent pas un résultat satisfaisant. La solution la plus simple est d'utiliser deux demi-additionneurs et de retirer les portes logiques superflues. Le résultat est alors un '''incrémenteur 2 bits ''carry skip''''', où la retenue sortante est calculée séparément du reste.
[[File:Incrémenteur carry skip 2 bits.png|centre|vignette|upright=2.5|Incrémenteur ''carry skip'' 2 bits]]
==Les incrémenteurs à anticipation de retenue==
L''''anticipation de retenue''' calcule la retenue d'une colonne sans attendre les retenues des colonnes précédentes. Dans le cas idéal, toutes les retenues sont calculées en parallèle, en même temps, et sont ensuite envoyées aux demi-additionneurs. Il s'agit d'une optimisation qui est utilisée pour l'incrémentation, l'addition et d'autres opérations similaires. Pour l'incrémentation, déterminer la retenue ne demande pas de calculs complexes, contrairement à l'addition.
[[File:Incrémenteur à anticipation de retenues, 8 bits.png|centre|vignette|upright=2.5|Incrémenteur à anticipation de retenues, 8 bits.]]
Pour le comprendre, on peut regarder comment la retenue sortant d'un demi-additionneur est formée. Elle est calculée en faisant un ET logique entre la retenue entrante et le bit d'opérande. Une retenue est donc un ET logique entre toutes les retenues précédentes. Un incrémenteur à anticipation de retenue utilise donc une porte ET à plusieurs entrées pour calculer une retenue.
[[File:Anticipation de retenue pour un bit du résultat, incrémenteur.png|centre|vignette|upright=2.5|Anticipation de retenue pour un bit du résultat, incrémenteur]]
En théorie, on peut utiliser une porte ET à plusieurs entrées pour chaque bit de l'opérande. Cependant, cela entrainera un cout en transistors très important. Pour éviter de gaspiller trop de portes logiques, une solution est de mélanger anticipation de retenues et propagation de retenue. Par exemple, pour un incrémenteur 32 bits, on peut découper l'opérande en 4 octets : on anticipe les retenues pour chaque octet, mais l'incrémentation de chaque octet se fait avec propagation de retenue et/ou ''carry skip''.
[[File:Incrémenteur à anticipation de retenues.png|centre|vignette|upright=3|Incrémenteur hybride utilisant partiellement l'anticipation de retenues.]]
Quelques processeurs utilisaient l'anticipation de retenues. Par exemple, le processeur Z-80 de Zilog utilisait un incrémenteur pour des nombres de 16 bits, ce qui demandait des performances assez élevées. Et cet incrémenteur utilisait à la fois anticipation de retenues et ''carry skip''. Il était découpé en quatre blocs avant anticipation de retenues entre eux : un bloc regroupant les 7 bits de poids faible, suivi par un bloc de 5 bits, lui-même suivi par un bloc de 3 bits, terminé par un dernier bit isolé. A l'intérieur de ces blocs, les bits sont regroupés en paires utilisant le ''carry skip''. Pour ceux qui veulent en savoir plus sur cet incrémenteur, voici un lien sur le sujet :
* [https://www.righto.com/2013/11/the-z-80s-16-bit-incrementdecrement.html The Z-80's 16-bit increment/decrement circuit reverse engineered].
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de sélection
| prevText=Les circuits de sélection
| next=Les bascules : des mémoires de 1 bit
| nextText=Les bascules : des mémoires de 1 bit
}}
</noinclude>
ji1sxw3gzkp9x62fd7ni332r48wedwl
768654
768653
2026-06-25T18:10:22Z
Mewtow
31375
/* Le demi-additionneur */
768654
wikitext
text/x-wiki
Le circuit '''incrémenteur''' incrémente un nombre entier. Plus précisément, il prend en entrée un nombre entier, et fournit en entier le nombre suivant, il lui ajoute 1. Une telle opération peut paraitre triviale et peu intéressante. Cependant, elle est très utilisée et est absolument cruciale pour créer des compteurs, des circuits capables de compter ou décompter, qui auront droit à un chapitre dédié. De plus, au-delà de leur utilisation dans les compteurs, les incrémenteurs étaient très utilisés sur les premiers processeurs 8 bits, comme le Z-80, le 6502, les premiers processeurs x86 comme le 8008, le 8086, le 8085, et bien d'autres.
Il existe aussi des ''circuits décrémenteurs'', qui décrémentent un opérande, ainsi que des ''circuits incrémenteur-décrémenteur'', qui peuvent incrémenter ou décrémenter, selon comment on les configure. Je ne vais pas détailler ces circuits plus que ça, car de tels circuits sont assez rares, comparé à un circuit incrémenteur simple.
==Le demi-additionneur==
Le circuit incrémenteur effectue l'opération suivante :
<math>a_7</math> <math>a_6</math> <math>a_5</math> <math>a_4</math> <math>a_3</math> <math>a_2</math> <math>a_1</math> <math>a_0</math>
+ 0 0 0 0 0 0 0 1
Un incrémenteur basique doit donc faire une addition pour chaque colonne, et précisément une addition de deux bits. Il se trouve que la table d'addition est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Un circuit capable d'additionner deux bits est appelé un '''demi-additionneur'''. Il dispose d'une sortie S pour la somme, et C pour la retenue (''carry'' an anglais). Il est très simple à construire avec les techniques vues dans les premiers chapitres. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! !! Retenue sortante !! Bit de somme
|-
||0||0|| ||0||0
|-
||0||1|| ||0||1
|-
||1||0|| ||0||1
|-
||1||1|| ||1||0
|}
On voit immédiatement que la colonne des retenues donne une porte ET, alors que celle du bit de somme est calculé par un XOR.
{| class="flexible"
|[[File:1-bit half-adder.svg|class=transparent|centre|Demi-addtionneur.]]
|[[File:Half-adder.svg|centre|class=transparent|Circuit d'un demi-addtionneur.]]
|}
Il existe beaucoup d'autres implémentations alternatives, qui utilisent moins de portes logiques, calculent la retenue plus rapidement, consomment moins d'énergie, et autres. Et il peu être intéressant d'étudier quelques alternatives, surtout qu'elles serviront plus bas, quand on étudiera le circuit incrémenteur du 8085.
Une source d'économie est liée à la porte XOR. En pratique, une porte XOR est composée en combinant plusieurs portes logiques ET/OU/NOR/NAND ensemble. Et il se trouve qu'il y a des redondances entre celles-ci et les portes utilisées pour calculer la retenue. Par exemple, rappelons qu'une porte XOR peut être construite avec une porte ET et deux portes NOR, comme illustré ci-dessous. Et il se trouve que la porte ET interne calcule la retenue sortante. En supprimant cette redondance, on économise quelques portes logiques. De plus, on se retrouve avec un demi-additionneur qui calcule le bit de somme à partir de la retenue sortante.
[[File:Demi-additionneur avec redondances éliminées.png|centre|vignette|upright=2|Demi-additionneur avec redondances éliminées]]
Le circuit précédent a une interprétation logique. Si vous regardez la table de vérité, vous remarquerez que la somme de deux bits est égale à l'inverse de la retenue sortante, sauf dans le cas où les deux bits additionnés valent zéro. Et le circuit précédent est basé là-dessus. La porte ET calcule la retenue sortante, la première porte NOR détecte sur les deux bits d'entrée valent zéro, et les deux résultats sont combinés pour donner le bit de somme. La combinaison se fait avec la seconde porte NOR.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! !! Retenue sortante !! Bit de somme
|- class="f_vert"
||0||0|| ||0||0
|- class="f_rouge"
||0||1|| ||0||1
|- class="f_rouge"
||1||0|| ||0||1
|- class="f_rouge"
||1||1|| ||1||0
|}
Une implémentation alternative, qui n'utilise que des portes NOR, est la suivante :
[[File:Demi-additionneur fait avec des portes NOR et NAND.png|centre|vignette|upright=2|Demi-additionneur fait avec des portes NOR et NAND.]]
Une implémentation alternative utilise des portes NAND. Pour rappel, il est possible de créer une porte XOR avec 4 portes NAND. La première d'entre elle fait un NAND entre les deux bits d'entrée, ce qui fait qu'elle calcule l'inverse de la retenue sortante. Le tout est illustré ci-dessous. En théorie, on devrait utiliser une porte NON pour récupérer la retenue correcte. Mais nous verrons plus bas que ce n'est pas une obligation. Nous verrons plus bas un exemple où c'est l'inverse de la retenue qui est utilisée dans les calculs, pour rendre les calculs plus rapides.
[[File:Demi-additionneur fait avec des portes NAND.png|centre|vignette|upright=2|Demi-additionneur fait avec des portes NAND]]
==L'incrémenteur à propagation de retenue==
Maintenant que l'on sait comment additionner deux bits, reprenons l'opération d'incrémentation :
<math>a_7</math> <math>a_6</math> <math>a_5</math> <math>a_4</math> <math>a_3</math> <math>a_2</math> <math>a_1</math> <math>a_0</math>
+ 0 0 0 0 0 0 0 1
------------------------------
Sur la colonne la plus à droite, il doit ajouter un au bit de poids faible. Pour les colonnes suivantes, il faut additionner le bit de l'opérande avec la retenue de la colonne précédente. En clair, on n'additionne que deux bits à chaque colonne : un 1 sur celle tout à droite, la retenue de la colonne précédente pour les autres. Et cela nous donne une idée de comment faire pour créer le circuit incrémenteur.
Il suffit d'utiliser un demi-additionneur par colonne, et de les enchainer les uns à la suite des autres. Chaque demi-additionneur additionne le bit de l'opérande avec la retenue fournie par le demi-additionneur précédent. Le seul qui fait exception est celui pour la colonne de poids faible. Pour celui-là, il doit ajouter 1 au bit de poids faible. Le résultat est appelé un '''incrémenteur à propagation de retenue'''. Il est constitué de demi-additionneurs enchaînés les uns à la suite des autres, du bit de poids faible vers le bit de poids fort.
[[File:Circuit incrémenteur.png|centre|vignette|upright=3|Circuit incrémenteur.]]
Maintenant, regardons le demi-additionneur le plus à droite, celui pour le bit de poids faible. Son entrée de retenue entrante est mise à 1 pour faire l'incrémentation. Quelques incrémenteurs permettent de configurer cette entrée de retenue à 0 ou à 1, ce qui effectue : soit une opération identité (l'opérande est recopié sur la sortie), soit une incrémentation. Un tel circuit est nommé un '''incrémenteur commandable'''. Nous aurons à utiliser une fois ou deux de tels incrémenteurs commandables dans la suite du cours.
L'incrémenteur à propagation de retenue est le plus simple et le plus économe en portes logiques. Mais de tels incrémenteurs sont rarement utilisés. À la place, on leur préfère des incrémenteurs plus rapides, mais qui utilisent plus de portes logiques. De tels incrémenteurs accélèrent le calcul des retenues. En effet, la rapidité d'une incrémentation est limitée par la propagation de la retenue : les retenues commencent à être calculées au bit de poids fort et on doit les calculer une par une, jusqu’à atteindre le bit de poids fort. Et cette "propagation des retenues" prend du temps, d'autant plus de temps que l'opérande est longue. Il y a deux optimisations principales, appelées le ''carry skip'' et l'anticipation de retenue, que nous allons décrire ci-dessous.
==Les incrémenteurs ''carry skip''==
L'optimisation '''''carry skip''''' effectue l'incrémentation, non pas bit par bit, mais par paquets de deux bits. Le résultat est que l'incrémentation est deux fois plus rapide, ou presque. Le circuit incrémenteur est donc composé en enchainant non pas des demi-additionneurs, mais des '''incrémenteurs 2 bits''' qui incrémentent un opérande de deux bits.
[[File:Circuit incrémenteur optimisé, faisant l'incrémentation par paquet de deux bits.png|centre|vignette|upright=2.5|Circuit incrémenteur optimisé, faisant l'incrémentation par paquet de deux bits]]
Reste à concevoir l'incrémenteur 2 bits.
===Les optimisations au niveau des transistors et l'exemple de l'incrémenteur du 8085 d'Intel===
Une première solution part de deux demi-additionneurs et effectue quelques simplifications au niveau des transistors et des portes logiques. Par exemple, l'Intel 8085 a utilisé un circuit optimisé, fabriqué avec des portes NAND et NOR. La raison est qu'avec les technologies de transistors CMOS actuelles, les portes NAND et NOR utilisent moins de transistors que les portes ET et OU. Et cette possibilité a été utilisée pour effectuer des simplifications assez mineures, mais toujours bonnes à prendre. Mais nous ne pouvons pas en parler en détail ici, car nous n'avons pas encore parlé en détail des transistors. Cependant, un exemple bien précis nous est accesible : celui de l’incrémenteur du processeur 8085 d'Intel.
Le processeur 8085 d'Intel est un processeur 8 bits très ancien. Il contenait un incrémenteur 16 bits, qui était utilisé pour calculer des adresses mémoire. Et cet incrémenteur était un incrémenteur à propagation de retenue optimisé. Il utilisait lui aussi des incrémenteurs 2 bits un peu modifiés.
L'idée était que les retenues sortant des colonnes paires étaient inversées, les colonnes impaires faisaient les calculs à partir de cette retenue inversée. Les demi-additionneurs des colonnes impaires n'étaient donc pas les mêmes que ceux des colonnes paires. Leurs tables de vérité sont différentes, leurs circuits aussi, et aucun des deux ne ressemble à un demi-additionneur normal. En réalité, ils sont fabriqués à partir des implémentations vues plus haut, qui utilisent uniquement des portes NAND ou uniquement des portes NOR.
Pour comprendre l'intérêt de faire ainsi, nous devons préciser une chose importante : avec les technologies CMOS utilisées pour les processeurs depuis les années 70, les portes logiques les plus simples sont les portes NON, NOR et NAND. Les portes ET/OU sont fabriquées en combinant des portes NOR/NAND avec une porte NON, ce qui prend plus de transistors.
Un demi-additionneur est donc fabriqué comme illustré ci-dessous, en logique CMOS. Seules les portes pour le calcul de la retenue sont indiquées, la porte XOR pour le bit de somme est fabriquée à partir de plusieurs portes logiques.
[[File:Demi-additionneur en CMOS.png|centre|vignette|upright=2|Demi-additionneur en CMOS]]
Si on enchaine deux demi-additionneurs, cela donne ceci :
[[File:Brique de base de l'incrémenteur du 8085.png|centre|vignette|upright=2|Brique de base de l'incrémenteur du 8085]]
La retenue doit donc traverser quatre portes logiques, dont deux portes NON. En inversant la retenue, les portes en jaune dans le schéma précédent se simplifient. Il suffit d'utiliser la loi de de Morgan pour trouver un circuit équivalent aux portes jaunes, qui n'est autre qu'une porte NOR couplée à une porte NON. Un autre moyen d'obtenir le même résultat est de prendre un demi-additionneur fabriqué avec uniquement des portes NOR, et de retirer la porte NON adéquate. Il faut ensuite corriger l'entrée de retenue de la porte XOR, pour qu'elle fasse l'addition avec la retenue correctement. Le résultat est illustré ci-dessous.
[[File:Incrémenteur 2 bits de l'incrémenteur du 8085.png|centre|vignette|upright=2|Incrémenteur 2 bits de l'incrémenteur du 8085]]
Le circuit utilise le même nombre de portes logiques, il n'économise pas de transistors. Par contre, la retenue n'a plus qu'à traverser deux portes logiques, au lieu de quatre. La propagation de la retenue est donc plus rapide, l'incrémentation est donc plus rapide.
===Les incrémenteurs 2 bits ''carry skip''===
Une solution alternative fabrique des incrémenteurs 2 bits qui calculent séparément les deux bits du résultat et la retenue sortante. La retenue sortante est calculée à partir des bits de l’opérande, sans propager des retenues. Ainsi, la retenue sortante est connue en avance, sans passer par deux demi-additionneurs. Pour créer un tel incrémenteur, le mieux est de partir de sa table de vérité.
{|class="wikitable"
|-
! Opérande !! Retenue entrante !! !! Retenue sortante !! Résultat
|-
| 00 || 0 || || 0 || 00
|-
| 01 || 0 || || 0 || 01
|-
| 10 || 0 || || 0 || 10
|-
| 11 || 0 || || 0 || 11
|-
| colspan="5" |
|-
| 00 || 1 || || 0 || 01
|-
| 01 || 1 || || 0 || 10
|-
| 10 || 1 || || 0 || 11
|-
| 11 || 1 || || 1 || 00
|}
Elle nous dit que la retenue sortante ne vaut 1 que dans un seul cas : les deux bits d'opérande valent 1, la retenue entrante vaut 1. Elle se calcule alors avec une porte ET à trois entrées. Pour les deux bits du résultat, les équations logiques ne donnent pas un résultat satisfaisant. La solution la plus simple est d'utiliser deux demi-additionneurs et de retirer les portes logiques superflues. Le résultat est alors un '''incrémenteur 2 bits ''carry skip''''', où la retenue sortante est calculée séparément du reste.
[[File:Incrémenteur carry skip 2 bits.png|centre|vignette|upright=2.5|Incrémenteur ''carry skip'' 2 bits]]
==Les incrémenteurs à anticipation de retenue==
L''''anticipation de retenue''' calcule la retenue d'une colonne sans attendre les retenues des colonnes précédentes. Dans le cas idéal, toutes les retenues sont calculées en parallèle, en même temps, et sont ensuite envoyées aux demi-additionneurs. Il s'agit d'une optimisation qui est utilisée pour l'incrémentation, l'addition et d'autres opérations similaires. Pour l'incrémentation, déterminer la retenue ne demande pas de calculs complexes, contrairement à l'addition.
[[File:Incrémenteur à anticipation de retenues, 8 bits.png|centre|vignette|upright=2.5|Incrémenteur à anticipation de retenues, 8 bits.]]
Pour le comprendre, on peut regarder comment la retenue sortant d'un demi-additionneur est formée. Elle est calculée en faisant un ET logique entre la retenue entrante et le bit d'opérande. Une retenue est donc un ET logique entre toutes les retenues précédentes. Un incrémenteur à anticipation de retenue utilise donc une porte ET à plusieurs entrées pour calculer une retenue.
[[File:Anticipation de retenue pour un bit du résultat, incrémenteur.png|centre|vignette|upright=2.5|Anticipation de retenue pour un bit du résultat, incrémenteur]]
En théorie, on peut utiliser une porte ET à plusieurs entrées pour chaque bit de l'opérande. Cependant, cela entrainera un cout en transistors très important. Pour éviter de gaspiller trop de portes logiques, une solution est de mélanger anticipation de retenues et propagation de retenue. Par exemple, pour un incrémenteur 32 bits, on peut découper l'opérande en 4 octets : on anticipe les retenues pour chaque octet, mais l'incrémentation de chaque octet se fait avec propagation de retenue et/ou ''carry skip''.
[[File:Incrémenteur à anticipation de retenues.png|centre|vignette|upright=3|Incrémenteur hybride utilisant partiellement l'anticipation de retenues.]]
Quelques processeurs utilisaient l'anticipation de retenues. Par exemple, le processeur Z-80 de Zilog utilisait un incrémenteur pour des nombres de 16 bits, ce qui demandait des performances assez élevées. Et cet incrémenteur utilisait à la fois anticipation de retenues et ''carry skip''. Il était découpé en quatre blocs avant anticipation de retenues entre eux : un bloc regroupant les 7 bits de poids faible, suivi par un bloc de 5 bits, lui-même suivi par un bloc de 3 bits, terminé par un dernier bit isolé. A l'intérieur de ces blocs, les bits sont regroupés en paires utilisant le ''carry skip''. Pour ceux qui veulent en savoir plus sur cet incrémenteur, voici un lien sur le sujet :
* [https://www.righto.com/2013/11/the-z-80s-16-bit-incrementdecrement.html The Z-80's 16-bit increment/decrement circuit reverse engineered].
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de sélection
| prevText=Les circuits de sélection
| next=Les bascules : des mémoires de 1 bit
| nextText=Les bascules : des mémoires de 1 bit
}}
</noinclude>
kc1q84cxy0m91fnqf3rjtfuy7rz3dic
768655
768654
2026-06-25T18:13:04Z
Mewtow
31375
/* Le demi-additionneur */
768655
wikitext
text/x-wiki
Le circuit '''incrémenteur''' incrémente un nombre entier. Plus précisément, il prend en entrée un nombre entier, et fournit en entier le nombre suivant, il lui ajoute 1. Une telle opération peut paraitre triviale et peu intéressante. Cependant, elle est très utilisée et est absolument cruciale pour créer des compteurs, des circuits capables de compter ou décompter, qui auront droit à un chapitre dédié. De plus, au-delà de leur utilisation dans les compteurs, les incrémenteurs étaient très utilisés sur les premiers processeurs 8 bits, comme le Z-80, le 6502, les premiers processeurs x86 comme le 8008, le 8086, le 8085, et bien d'autres.
Il existe aussi des ''circuits décrémenteurs'', qui décrémentent un opérande, ainsi que des ''circuits incrémenteur-décrémenteur'', qui peuvent incrémenter ou décrémenter, selon comment on les configure. Je ne vais pas détailler ces circuits plus que ça, car de tels circuits sont assez rares, comparé à un circuit incrémenteur simple.
==Le demi-additionneur==
Le circuit incrémenteur effectue l'opération suivante :
<math>a_7</math> <math>a_6</math> <math>a_5</math> <math>a_4</math> <math>a_3</math> <math>a_2</math> <math>a_1</math> <math>a_0</math>
+ 0 0 0 0 0 0 0 1
Un incrémenteur basique doit donc faire une addition pour chaque colonne, et précisément une addition de deux bits. Il se trouve que la table d'addition est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Un circuit capable d'additionner deux bits est appelé un '''demi-additionneur'''. Il dispose d'une sortie S pour la somme, et C pour la retenue (''carry'' an anglais). Il est très simple à construire avec les techniques vues dans les premiers chapitres. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! !! Retenue sortante !! Bit de somme
|-
||0||0|| ||0||0
|-
||0||1|| ||0||1
|-
||1||0|| ||0||1
|-
||1||1|| ||1||0
|}
On voit immédiatement que la colonne des retenues donne une porte ET, alors que celle du bit de somme est calculé par un XOR.
{| class="flexible"
|[[File:1-bit half-adder.svg|class=transparent|centre|Demi-addtionneur.]]
|[[File:Half-adder.svg|centre|class=transparent|Circuit d'un demi-addtionneur.]]
|}
Il existe beaucoup d'autres implémentations alternatives, qui utilisent moins de portes logiques, calculent la retenue plus rapidement, consomment moins d'énergie, et autres. Et il peu être intéressant d'étudier quelques alternatives, surtout qu'elles serviront plus bas, quand on étudiera le circuit incrémenteur du 8085.
Une source d'économie est liée à la porte XOR. En pratique, une porte XOR est composée en combinant plusieurs portes logiques ET/OU/NOR/NAND ensemble. Et il se trouve qu'il y a des redondances entre celles-ci et les portes utilisées pour calculer la retenue. Par exemple, rappelons qu'une porte XOR peut être construite avec une porte ET et deux portes NOR, comme illustré ci-dessous. Et il se trouve que la porte ET interne calcule la retenue sortante. En supprimant cette redondance, on économise quelques portes logiques. De plus, on se retrouve avec un demi-additionneur qui calcule le bit de somme à partir de la retenue sortante.
[[File:Demi-additionneur avec redondances éliminées.png|centre|vignette|upright=2|Demi-additionneur avec redondances éliminées]]
Le circuit précédent a une interprétation logique. Si vous regardez la table de vérité, vous remarquerez que la somme de deux bits est égale à l'inverse de la retenue sortante, sauf dans le cas où les deux bits additionnés valent zéro. Et le circuit précédent est basé là-dessus.
L'idée est de calculer l'inverse du bit de somme, avant de l'inverser avec une porte NON. L'inverse du bit de somme vaut 1, soit quand la retenue est à 1, soit quand les deux bits additionnés sont à 0. La porte ET calcule la retenue sortante, la première porte NOR détecte sur les deux bits d'entrée valent zéro. Une porte OU combine les deux résultat pour obtenir l'inverse du bit d'entrée, puis une porte NOn inverse le tout pour obtenir le bit de somme adéquat. Les deux portes sont fusionnées : c'est la seconde porte NOR.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! !! Retenue sortante !! Bit de somme
|- class="f_vert"
||0||0|| ||0||0
|- class="f_rouge"
||0||1|| ||0||1
|- class="f_rouge"
||1||0|| ||0||1
|- class="f_rouge"
||1||1|| ||1||0
|}
Une implémentation alternative, qui n'utilise que des portes NOR, est la suivante :
[[File:Demi-additionneur fait avec des portes NOR et NAND.png|centre|vignette|upright=2|Demi-additionneur fait avec des portes NOR et NAND.]]
Une implémentation alternative utilise des portes NAND. Pour rappel, il est possible de créer une porte XOR avec 4 portes NAND. La première d'entre elle fait un NAND entre les deux bits d'entrée, ce qui fait qu'elle calcule l'inverse de la retenue sortante. Le tout est illustré ci-dessous. En théorie, on devrait utiliser une porte NON pour récupérer la retenue correcte. Mais nous verrons plus bas que ce n'est pas une obligation. Nous verrons plus bas un exemple où c'est l'inverse de la retenue qui est utilisée dans les calculs, pour rendre les calculs plus rapides.
[[File:Demi-additionneur fait avec des portes NAND.png|centre|vignette|upright=2|Demi-additionneur fait avec des portes NAND]]
==L'incrémenteur à propagation de retenue==
Maintenant que l'on sait comment additionner deux bits, reprenons l'opération d'incrémentation :
<math>a_7</math> <math>a_6</math> <math>a_5</math> <math>a_4</math> <math>a_3</math> <math>a_2</math> <math>a_1</math> <math>a_0</math>
+ 0 0 0 0 0 0 0 1
------------------------------
Sur la colonne la plus à droite, il doit ajouter un au bit de poids faible. Pour les colonnes suivantes, il faut additionner le bit de l'opérande avec la retenue de la colonne précédente. En clair, on n'additionne que deux bits à chaque colonne : un 1 sur celle tout à droite, la retenue de la colonne précédente pour les autres. Et cela nous donne une idée de comment faire pour créer le circuit incrémenteur.
Il suffit d'utiliser un demi-additionneur par colonne, et de les enchainer les uns à la suite des autres. Chaque demi-additionneur additionne le bit de l'opérande avec la retenue fournie par le demi-additionneur précédent. Le seul qui fait exception est celui pour la colonne de poids faible. Pour celui-là, il doit ajouter 1 au bit de poids faible. Le résultat est appelé un '''incrémenteur à propagation de retenue'''. Il est constitué de demi-additionneurs enchaînés les uns à la suite des autres, du bit de poids faible vers le bit de poids fort.
[[File:Circuit incrémenteur.png|centre|vignette|upright=3|Circuit incrémenteur.]]
Maintenant, regardons le demi-additionneur le plus à droite, celui pour le bit de poids faible. Son entrée de retenue entrante est mise à 1 pour faire l'incrémentation. Quelques incrémenteurs permettent de configurer cette entrée de retenue à 0 ou à 1, ce qui effectue : soit une opération identité (l'opérande est recopié sur la sortie), soit une incrémentation. Un tel circuit est nommé un '''incrémenteur commandable'''. Nous aurons à utiliser une fois ou deux de tels incrémenteurs commandables dans la suite du cours.
L'incrémenteur à propagation de retenue est le plus simple et le plus économe en portes logiques. Mais de tels incrémenteurs sont rarement utilisés. À la place, on leur préfère des incrémenteurs plus rapides, mais qui utilisent plus de portes logiques. De tels incrémenteurs accélèrent le calcul des retenues. En effet, la rapidité d'une incrémentation est limitée par la propagation de la retenue : les retenues commencent à être calculées au bit de poids fort et on doit les calculer une par une, jusqu’à atteindre le bit de poids fort. Et cette "propagation des retenues" prend du temps, d'autant plus de temps que l'opérande est longue. Il y a deux optimisations principales, appelées le ''carry skip'' et l'anticipation de retenue, que nous allons décrire ci-dessous.
==Les incrémenteurs ''carry skip''==
L'optimisation '''''carry skip''''' effectue l'incrémentation, non pas bit par bit, mais par paquets de deux bits. Le résultat est que l'incrémentation est deux fois plus rapide, ou presque. Le circuit incrémenteur est donc composé en enchainant non pas des demi-additionneurs, mais des '''incrémenteurs 2 bits''' qui incrémentent un opérande de deux bits.
[[File:Circuit incrémenteur optimisé, faisant l'incrémentation par paquet de deux bits.png|centre|vignette|upright=2.5|Circuit incrémenteur optimisé, faisant l'incrémentation par paquet de deux bits]]
Reste à concevoir l'incrémenteur 2 bits.
===Les optimisations au niveau des transistors et l'exemple de l'incrémenteur du 8085 d'Intel===
Une première solution part de deux demi-additionneurs et effectue quelques simplifications au niveau des transistors et des portes logiques. Par exemple, l'Intel 8085 a utilisé un circuit optimisé, fabriqué avec des portes NAND et NOR. La raison est qu'avec les technologies de transistors CMOS actuelles, les portes NAND et NOR utilisent moins de transistors que les portes ET et OU. Et cette possibilité a été utilisée pour effectuer des simplifications assez mineures, mais toujours bonnes à prendre. Mais nous ne pouvons pas en parler en détail ici, car nous n'avons pas encore parlé en détail des transistors. Cependant, un exemple bien précis nous est accesible : celui de l’incrémenteur du processeur 8085 d'Intel.
Le processeur 8085 d'Intel est un processeur 8 bits très ancien. Il contenait un incrémenteur 16 bits, qui était utilisé pour calculer des adresses mémoire. Et cet incrémenteur était un incrémenteur à propagation de retenue optimisé. Il utilisait lui aussi des incrémenteurs 2 bits un peu modifiés.
L'idée était que les retenues sortant des colonnes paires étaient inversées, les colonnes impaires faisaient les calculs à partir de cette retenue inversée. Les demi-additionneurs des colonnes impaires n'étaient donc pas les mêmes que ceux des colonnes paires. Leurs tables de vérité sont différentes, leurs circuits aussi, et aucun des deux ne ressemble à un demi-additionneur normal. En réalité, ils sont fabriqués à partir des implémentations vues plus haut, qui utilisent uniquement des portes NAND ou uniquement des portes NOR.
Pour comprendre l'intérêt de faire ainsi, nous devons préciser une chose importante : avec les technologies CMOS utilisées pour les processeurs depuis les années 70, les portes logiques les plus simples sont les portes NON, NOR et NAND. Les portes ET/OU sont fabriquées en combinant des portes NOR/NAND avec une porte NON, ce qui prend plus de transistors.
Un demi-additionneur est donc fabriqué comme illustré ci-dessous, en logique CMOS. Seules les portes pour le calcul de la retenue sont indiquées, la porte XOR pour le bit de somme est fabriquée à partir de plusieurs portes logiques.
[[File:Demi-additionneur en CMOS.png|centre|vignette|upright=2|Demi-additionneur en CMOS]]
Si on enchaine deux demi-additionneurs, cela donne ceci :
[[File:Brique de base de l'incrémenteur du 8085.png|centre|vignette|upright=2|Brique de base de l'incrémenteur du 8085]]
La retenue doit donc traverser quatre portes logiques, dont deux portes NON. En inversant la retenue, les portes en jaune dans le schéma précédent se simplifient. Il suffit d'utiliser la loi de de Morgan pour trouver un circuit équivalent aux portes jaunes, qui n'est autre qu'une porte NOR couplée à une porte NON. Un autre moyen d'obtenir le même résultat est de prendre un demi-additionneur fabriqué avec uniquement des portes NOR, et de retirer la porte NON adéquate. Il faut ensuite corriger l'entrée de retenue de la porte XOR, pour qu'elle fasse l'addition avec la retenue correctement. Le résultat est illustré ci-dessous.
[[File:Incrémenteur 2 bits de l'incrémenteur du 8085.png|centre|vignette|upright=2|Incrémenteur 2 bits de l'incrémenteur du 8085]]
Le circuit utilise le même nombre de portes logiques, il n'économise pas de transistors. Par contre, la retenue n'a plus qu'à traverser deux portes logiques, au lieu de quatre. La propagation de la retenue est donc plus rapide, l'incrémentation est donc plus rapide.
===Les incrémenteurs 2 bits ''carry skip''===
Une solution alternative fabrique des incrémenteurs 2 bits qui calculent séparément les deux bits du résultat et la retenue sortante. La retenue sortante est calculée à partir des bits de l’opérande, sans propager des retenues. Ainsi, la retenue sortante est connue en avance, sans passer par deux demi-additionneurs. Pour créer un tel incrémenteur, le mieux est de partir de sa table de vérité.
{|class="wikitable"
|-
! Opérande !! Retenue entrante !! !! Retenue sortante !! Résultat
|-
| 00 || 0 || || 0 || 00
|-
| 01 || 0 || || 0 || 01
|-
| 10 || 0 || || 0 || 10
|-
| 11 || 0 || || 0 || 11
|-
| colspan="5" |
|-
| 00 || 1 || || 0 || 01
|-
| 01 || 1 || || 0 || 10
|-
| 10 || 1 || || 0 || 11
|-
| 11 || 1 || || 1 || 00
|}
Elle nous dit que la retenue sortante ne vaut 1 que dans un seul cas : les deux bits d'opérande valent 1, la retenue entrante vaut 1. Elle se calcule alors avec une porte ET à trois entrées. Pour les deux bits du résultat, les équations logiques ne donnent pas un résultat satisfaisant. La solution la plus simple est d'utiliser deux demi-additionneurs et de retirer les portes logiques superflues. Le résultat est alors un '''incrémenteur 2 bits ''carry skip''''', où la retenue sortante est calculée séparément du reste.
[[File:Incrémenteur carry skip 2 bits.png|centre|vignette|upright=2.5|Incrémenteur ''carry skip'' 2 bits]]
==Les incrémenteurs à anticipation de retenue==
L''''anticipation de retenue''' calcule la retenue d'une colonne sans attendre les retenues des colonnes précédentes. Dans le cas idéal, toutes les retenues sont calculées en parallèle, en même temps, et sont ensuite envoyées aux demi-additionneurs. Il s'agit d'une optimisation qui est utilisée pour l'incrémentation, l'addition et d'autres opérations similaires. Pour l'incrémentation, déterminer la retenue ne demande pas de calculs complexes, contrairement à l'addition.
[[File:Incrémenteur à anticipation de retenues, 8 bits.png|centre|vignette|upright=2.5|Incrémenteur à anticipation de retenues, 8 bits.]]
Pour le comprendre, on peut regarder comment la retenue sortant d'un demi-additionneur est formée. Elle est calculée en faisant un ET logique entre la retenue entrante et le bit d'opérande. Une retenue est donc un ET logique entre toutes les retenues précédentes. Un incrémenteur à anticipation de retenue utilise donc une porte ET à plusieurs entrées pour calculer une retenue.
[[File:Anticipation de retenue pour un bit du résultat, incrémenteur.png|centre|vignette|upright=2.5|Anticipation de retenue pour un bit du résultat, incrémenteur]]
En théorie, on peut utiliser une porte ET à plusieurs entrées pour chaque bit de l'opérande. Cependant, cela entrainera un cout en transistors très important. Pour éviter de gaspiller trop de portes logiques, une solution est de mélanger anticipation de retenues et propagation de retenue. Par exemple, pour un incrémenteur 32 bits, on peut découper l'opérande en 4 octets : on anticipe les retenues pour chaque octet, mais l'incrémentation de chaque octet se fait avec propagation de retenue et/ou ''carry skip''.
[[File:Incrémenteur à anticipation de retenues.png|centre|vignette|upright=3|Incrémenteur hybride utilisant partiellement l'anticipation de retenues.]]
Quelques processeurs utilisaient l'anticipation de retenues. Par exemple, le processeur Z-80 de Zilog utilisait un incrémenteur pour des nombres de 16 bits, ce qui demandait des performances assez élevées. Et cet incrémenteur utilisait à la fois anticipation de retenues et ''carry skip''. Il était découpé en quatre blocs avant anticipation de retenues entre eux : un bloc regroupant les 7 bits de poids faible, suivi par un bloc de 5 bits, lui-même suivi par un bloc de 3 bits, terminé par un dernier bit isolé. A l'intérieur de ces blocs, les bits sont regroupés en paires utilisant le ''carry skip''. Pour ceux qui veulent en savoir plus sur cet incrémenteur, voici un lien sur le sujet :
* [https://www.righto.com/2013/11/the-z-80s-16-bit-incrementdecrement.html The Z-80's 16-bit increment/decrement circuit reverse engineered].
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de sélection
| prevText=Les circuits de sélection
| next=Les bascules : des mémoires de 1 bit
| nextText=Les bascules : des mémoires de 1 bit
}}
</noinclude>
9i6owp6yb35idzk2dhlh1slg69u4s45
768656
768655
2026-06-25T18:15:05Z
Mewtow
31375
/* Le demi-additionneur */
768656
wikitext
text/x-wiki
Le circuit '''incrémenteur''' incrémente un nombre entier. Plus précisément, il prend en entrée un nombre entier, et fournit en entier le nombre suivant, il lui ajoute 1. Une telle opération peut paraitre triviale et peu intéressante. Cependant, elle est très utilisée et est absolument cruciale pour créer des compteurs, des circuits capables de compter ou décompter, qui auront droit à un chapitre dédié. De plus, au-delà de leur utilisation dans les compteurs, les incrémenteurs étaient très utilisés sur les premiers processeurs 8 bits, comme le Z-80, le 6502, les premiers processeurs x86 comme le 8008, le 8086, le 8085, et bien d'autres.
Il existe aussi des ''circuits décrémenteurs'', qui décrémentent un opérande, ainsi que des ''circuits incrémenteur-décrémenteur'', qui peuvent incrémenter ou décrémenter, selon comment on les configure. Je ne vais pas détailler ces circuits plus que ça, car de tels circuits sont assez rares, comparé à un circuit incrémenteur simple.
==Le demi-additionneur==
Le circuit incrémenteur effectue l'opération suivante :
<math>a_7</math> <math>a_6</math> <math>a_5</math> <math>a_4</math> <math>a_3</math> <math>a_2</math> <math>a_1</math> <math>a_0</math>
+ 0 0 0 0 0 0 0 1
Un incrémenteur basique doit donc faire une addition pour chaque colonne, et précisément une addition de deux bits. Il se trouve que la table d'addition est très simple en binaire. Jugez plutôt :
* 0 + 0 = 0, retenue = 0 ;
* 0 + 1 = 1, retenue = 0 ;
* 1 + 0 = 1, retenue = 0 ;
* 1 + 1 = 0, retenue = 1.
Un circuit capable d'additionner deux bits est appelé un '''demi-additionneur'''. Il dispose d'une sortie S pour la somme, et C pour la retenue (''carry'' an anglais). Il est très simple à construire avec les techniques vues dans les premiers chapitres. Voici sa table de vérité :
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! !! Retenue sortante !! Bit de somme
|-
||0||0|| ||0||0
|-
||0||1|| ||0||1
|-
||1||0|| ||0||1
|-
||1||1|| ||1||0
|}
On voit immédiatement que la colonne des retenues donne une porte ET, alors que celle du bit de somme est calculé par un XOR.
{| class="flexible"
|[[File:1-bit half-adder.svg|class=transparent|centre|Demi-addtionneur.]]
|[[File:Half-adder.svg|centre|class=transparent|Circuit d'un demi-addtionneur.]]
|}
Il existe beaucoup d'autres implémentations alternatives, qui utilisent moins de portes logiques, calculent la retenue plus rapidement, consomment moins d'énergie, et autres. Et il peu être intéressant d'étudier quelques alternatives, surtout qu'elles serviront plus bas, quand on étudiera le circuit incrémenteur du 8085.
Une source d'économie est liée à la porte XOR. En pratique, une porte XOR est composée en combinant plusieurs portes logiques ET/OU/NOR/NAND ensemble. Et il se trouve qu'il y a des redondances entre celles-ci et les portes utilisées pour calculer la retenue. Par exemple, rappelons qu'une porte XOR peut être construite avec une porte ET et deux portes NOR, comme illustré ci-dessous. Et il se trouve que la porte ET interne calcule la retenue sortante. En supprimant cette redondance, on économise quelques portes logiques. De plus, on se retrouve avec un demi-additionneur qui calcule le bit de somme à partir de la retenue sortante.
[[File:Demi-additionneur avec redondances éliminées.png|centre|vignette|upright=2|Demi-additionneur avec redondances éliminées]]
Le circuit précédent a une interprétation logique. Si vous regardez la table de vérité, vous remarquerez que la somme de deux bits est égale à l'inverse de la retenue sortante, sauf dans le cas où les deux bits additionnés valent zéro. Et le circuit précédent est basé là-dessus.
{|class="wikitable"
|-
! Retenue entrante !! Opérande 1 !! !! Retenue sortante !! Bit de somme
|- class="f_vert"
||0||0|| ||0||0
|- class="f_rouge"
||0||1|| ||0||1
|- class="f_rouge"
||1||0|| ||0||1
|- class="f_rouge"
||1||1|| ||1||0
|}
L'idée est de calculer l'inverse du bit de somme, avant de l'inverser avec une porte NON. L'inverse du bit de somme vaut 1, soit quand la retenue est à 1, soit quand les deux bits additionnés sont à 0. La porte ET calcule la retenue sortante, la première porte NOR détecte sur les deux bits d'entrée valent zéro. Une porte OU combine les deux résultat pour obtenir l'inverse du bit d'entrée, puis une porte NOn inverse le tout pour obtenir le bit de somme adéquat. Les deux portes sont fusionnées : c'est la seconde porte NOR.
Une implémentation alternative, qui n'utilise que des portes NOR, est la suivante :
[[File:Demi-additionneur fait avec des portes NOR et NAND.png|centre|vignette|upright=2|Demi-additionneur fait avec des portes NOR et NAND.]]
Une implémentation alternative utilise des portes NAND. Pour rappel, il est possible de créer une porte XOR avec 4 portes NAND. La première d'entre elle fait un NAND entre les deux bits d'entrée, ce qui fait qu'elle calcule l'inverse de la retenue sortante. Le tout est illustré ci-dessous. En théorie, on devrait utiliser une porte NON pour récupérer la retenue correcte. Mais nous verrons plus bas que ce n'est pas une obligation. Nous verrons plus bas un exemple où c'est l'inverse de la retenue qui est utilisée dans les calculs, pour rendre les calculs plus rapides.
[[File:Demi-additionneur fait avec des portes NAND.png|centre|vignette|upright=2|Demi-additionneur fait avec des portes NAND]]
==L'incrémenteur à propagation de retenue==
Maintenant que l'on sait comment additionner deux bits, reprenons l'opération d'incrémentation :
<math>a_7</math> <math>a_6</math> <math>a_5</math> <math>a_4</math> <math>a_3</math> <math>a_2</math> <math>a_1</math> <math>a_0</math>
+ 0 0 0 0 0 0 0 1
------------------------------
Sur la colonne la plus à droite, il doit ajouter un au bit de poids faible. Pour les colonnes suivantes, il faut additionner le bit de l'opérande avec la retenue de la colonne précédente. En clair, on n'additionne que deux bits à chaque colonne : un 1 sur celle tout à droite, la retenue de la colonne précédente pour les autres. Et cela nous donne une idée de comment faire pour créer le circuit incrémenteur.
Il suffit d'utiliser un demi-additionneur par colonne, et de les enchainer les uns à la suite des autres. Chaque demi-additionneur additionne le bit de l'opérande avec la retenue fournie par le demi-additionneur précédent. Le seul qui fait exception est celui pour la colonne de poids faible. Pour celui-là, il doit ajouter 1 au bit de poids faible. Le résultat est appelé un '''incrémenteur à propagation de retenue'''. Il est constitué de demi-additionneurs enchaînés les uns à la suite des autres, du bit de poids faible vers le bit de poids fort.
[[File:Circuit incrémenteur.png|centre|vignette|upright=3|Circuit incrémenteur.]]
Maintenant, regardons le demi-additionneur le plus à droite, celui pour le bit de poids faible. Son entrée de retenue entrante est mise à 1 pour faire l'incrémentation. Quelques incrémenteurs permettent de configurer cette entrée de retenue à 0 ou à 1, ce qui effectue : soit une opération identité (l'opérande est recopié sur la sortie), soit une incrémentation. Un tel circuit est nommé un '''incrémenteur commandable'''. Nous aurons à utiliser une fois ou deux de tels incrémenteurs commandables dans la suite du cours.
L'incrémenteur à propagation de retenue est le plus simple et le plus économe en portes logiques. Mais de tels incrémenteurs sont rarement utilisés. À la place, on leur préfère des incrémenteurs plus rapides, mais qui utilisent plus de portes logiques. De tels incrémenteurs accélèrent le calcul des retenues. En effet, la rapidité d'une incrémentation est limitée par la propagation de la retenue : les retenues commencent à être calculées au bit de poids fort et on doit les calculer une par une, jusqu’à atteindre le bit de poids fort. Et cette "propagation des retenues" prend du temps, d'autant plus de temps que l'opérande est longue. Il y a deux optimisations principales, appelées le ''carry skip'' et l'anticipation de retenue, que nous allons décrire ci-dessous.
==Les incrémenteurs ''carry skip''==
L'optimisation '''''carry skip''''' effectue l'incrémentation, non pas bit par bit, mais par paquets de deux bits. Le résultat est que l'incrémentation est deux fois plus rapide, ou presque. Le circuit incrémenteur est donc composé en enchainant non pas des demi-additionneurs, mais des '''incrémenteurs 2 bits''' qui incrémentent un opérande de deux bits.
[[File:Circuit incrémenteur optimisé, faisant l'incrémentation par paquet de deux bits.png|centre|vignette|upright=2.5|Circuit incrémenteur optimisé, faisant l'incrémentation par paquet de deux bits]]
Reste à concevoir l'incrémenteur 2 bits.
===Les optimisations au niveau des transistors et l'exemple de l'incrémenteur du 8085 d'Intel===
Une première solution part de deux demi-additionneurs et effectue quelques simplifications au niveau des transistors et des portes logiques. Par exemple, l'Intel 8085 a utilisé un circuit optimisé, fabriqué avec des portes NAND et NOR. La raison est qu'avec les technologies de transistors CMOS actuelles, les portes NAND et NOR utilisent moins de transistors que les portes ET et OU. Et cette possibilité a été utilisée pour effectuer des simplifications assez mineures, mais toujours bonnes à prendre. Mais nous ne pouvons pas en parler en détail ici, car nous n'avons pas encore parlé en détail des transistors. Cependant, un exemple bien précis nous est accesible : celui de l’incrémenteur du processeur 8085 d'Intel.
Le processeur 8085 d'Intel est un processeur 8 bits très ancien. Il contenait un incrémenteur 16 bits, qui était utilisé pour calculer des adresses mémoire. Et cet incrémenteur était un incrémenteur à propagation de retenue optimisé. Il utilisait lui aussi des incrémenteurs 2 bits un peu modifiés.
L'idée était que les retenues sortant des colonnes paires étaient inversées, les colonnes impaires faisaient les calculs à partir de cette retenue inversée. Les demi-additionneurs des colonnes impaires n'étaient donc pas les mêmes que ceux des colonnes paires. Leurs tables de vérité sont différentes, leurs circuits aussi, et aucun des deux ne ressemble à un demi-additionneur normal. En réalité, ils sont fabriqués à partir des implémentations vues plus haut, qui utilisent uniquement des portes NAND ou uniquement des portes NOR.
Pour comprendre l'intérêt de faire ainsi, nous devons préciser une chose importante : avec les technologies CMOS utilisées pour les processeurs depuis les années 70, les portes logiques les plus simples sont les portes NON, NOR et NAND. Les portes ET/OU sont fabriquées en combinant des portes NOR/NAND avec une porte NON, ce qui prend plus de transistors.
Un demi-additionneur est donc fabriqué comme illustré ci-dessous, en logique CMOS. Seules les portes pour le calcul de la retenue sont indiquées, la porte XOR pour le bit de somme est fabriquée à partir de plusieurs portes logiques.
[[File:Demi-additionneur en CMOS.png|centre|vignette|upright=2|Demi-additionneur en CMOS]]
Si on enchaine deux demi-additionneurs, cela donne ceci :
[[File:Brique de base de l'incrémenteur du 8085.png|centre|vignette|upright=2|Brique de base de l'incrémenteur du 8085]]
La retenue doit donc traverser quatre portes logiques, dont deux portes NON. En inversant la retenue, les portes en jaune dans le schéma précédent se simplifient. Il suffit d'utiliser la loi de de Morgan pour trouver un circuit équivalent aux portes jaunes, qui n'est autre qu'une porte NOR couplée à une porte NON. Un autre moyen d'obtenir le même résultat est de prendre un demi-additionneur fabriqué avec uniquement des portes NOR, et de retirer la porte NON adéquate. Il faut ensuite corriger l'entrée de retenue de la porte XOR, pour qu'elle fasse l'addition avec la retenue correctement. Le résultat est illustré ci-dessous.
[[File:Incrémenteur 2 bits de l'incrémenteur du 8085.png|centre|vignette|upright=2|Incrémenteur 2 bits de l'incrémenteur du 8085]]
Le circuit utilise le même nombre de portes logiques, il n'économise pas de transistors. Par contre, la retenue n'a plus qu'à traverser deux portes logiques, au lieu de quatre. La propagation de la retenue est donc plus rapide, l'incrémentation est donc plus rapide.
===Les incrémenteurs 2 bits ''carry skip''===
Une solution alternative fabrique des incrémenteurs 2 bits qui calculent séparément les deux bits du résultat et la retenue sortante. La retenue sortante est calculée à partir des bits de l’opérande, sans propager des retenues. Ainsi, la retenue sortante est connue en avance, sans passer par deux demi-additionneurs. Pour créer un tel incrémenteur, le mieux est de partir de sa table de vérité.
{|class="wikitable"
|-
! Opérande !! Retenue entrante !! !! Retenue sortante !! Résultat
|-
| 00 || 0 || || 0 || 00
|-
| 01 || 0 || || 0 || 01
|-
| 10 || 0 || || 0 || 10
|-
| 11 || 0 || || 0 || 11
|-
| colspan="5" |
|-
| 00 || 1 || || 0 || 01
|-
| 01 || 1 || || 0 || 10
|-
| 10 || 1 || || 0 || 11
|-
| 11 || 1 || || 1 || 00
|}
Elle nous dit que la retenue sortante ne vaut 1 que dans un seul cas : les deux bits d'opérande valent 1, la retenue entrante vaut 1. Elle se calcule alors avec une porte ET à trois entrées. Pour les deux bits du résultat, les équations logiques ne donnent pas un résultat satisfaisant. La solution la plus simple est d'utiliser deux demi-additionneurs et de retirer les portes logiques superflues. Le résultat est alors un '''incrémenteur 2 bits ''carry skip''''', où la retenue sortante est calculée séparément du reste.
[[File:Incrémenteur carry skip 2 bits.png|centre|vignette|upright=2.5|Incrémenteur ''carry skip'' 2 bits]]
==Les incrémenteurs à anticipation de retenue==
L''''anticipation de retenue''' calcule la retenue d'une colonne sans attendre les retenues des colonnes précédentes. Dans le cas idéal, toutes les retenues sont calculées en parallèle, en même temps, et sont ensuite envoyées aux demi-additionneurs. Il s'agit d'une optimisation qui est utilisée pour l'incrémentation, l'addition et d'autres opérations similaires. Pour l'incrémentation, déterminer la retenue ne demande pas de calculs complexes, contrairement à l'addition.
[[File:Incrémenteur à anticipation de retenues, 8 bits.png|centre|vignette|upright=2.5|Incrémenteur à anticipation de retenues, 8 bits.]]
Pour le comprendre, on peut regarder comment la retenue sortant d'un demi-additionneur est formée. Elle est calculée en faisant un ET logique entre la retenue entrante et le bit d'opérande. Une retenue est donc un ET logique entre toutes les retenues précédentes. Un incrémenteur à anticipation de retenue utilise donc une porte ET à plusieurs entrées pour calculer une retenue.
[[File:Anticipation de retenue pour un bit du résultat, incrémenteur.png|centre|vignette|upright=2.5|Anticipation de retenue pour un bit du résultat, incrémenteur]]
En théorie, on peut utiliser une porte ET à plusieurs entrées pour chaque bit de l'opérande. Cependant, cela entrainera un cout en transistors très important. Pour éviter de gaspiller trop de portes logiques, une solution est de mélanger anticipation de retenues et propagation de retenue. Par exemple, pour un incrémenteur 32 bits, on peut découper l'opérande en 4 octets : on anticipe les retenues pour chaque octet, mais l'incrémentation de chaque octet se fait avec propagation de retenue et/ou ''carry skip''.
[[File:Incrémenteur à anticipation de retenues.png|centre|vignette|upright=3|Incrémenteur hybride utilisant partiellement l'anticipation de retenues.]]
Quelques processeurs utilisaient l'anticipation de retenues. Par exemple, le processeur Z-80 de Zilog utilisait un incrémenteur pour des nombres de 16 bits, ce qui demandait des performances assez élevées. Et cet incrémenteur utilisait à la fois anticipation de retenues et ''carry skip''. Il était découpé en quatre blocs avant anticipation de retenues entre eux : un bloc regroupant les 7 bits de poids faible, suivi par un bloc de 5 bits, lui-même suivi par un bloc de 3 bits, terminé par un dernier bit isolé. A l'intérieur de ces blocs, les bits sont regroupés en paires utilisant le ''carry skip''. Pour ceux qui veulent en savoir plus sur cet incrémenteur, voici un lien sur le sujet :
* [https://www.righto.com/2013/11/the-z-80s-16-bit-incrementdecrement.html The Z-80's 16-bit increment/decrement circuit reverse engineered].
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les circuits de sélection
| prevText=Les circuits de sélection
| next=Les bascules : des mémoires de 1 bit
| nextText=Les bascules : des mémoires de 1 bit
}}
</noinclude>
aev8jc15u3dwlzprwdeawxe0q8tcojw
Fonctionnement d'un ordinateur/Les registres à décalage à rétroaction linéaire
0
83969
768621
2026-06-25T15:22:06Z
Mewtow
31375
Mewtow a déplacé la page [[Fonctionnement d'un ordinateur/Les registres à décalage à rétroaction linéaire]] vers [[Fonctionnement d'un ordinateur/Les registres à décalage et les LSFR]]
768621
wikitext
text/x-wiki
#REDIRECTION [[Fonctionnement d'un ordinateur/Les registres à décalage et les LSFR]]
9mya19sph4tg0vghx96wpjh9ul1xy1l
Catégorie:Recherche CDU 64 - Économie domestique, science ménagère
14
83970
768649
2026-06-25T17:45:35Z
DavidL
1746
Page créée avec « [[Catégorie:Recherche CDU 6 – Sciences appliquées, techniques]] »
768649
wikitext
text/x-wiki
[[Catégorie:Recherche CDU 6 – Sciences appliquées, techniques]]
bcaik4uydidulbypgpfqt0lrikinirg