Schaltkreise, der zwei Binärzahlen addieren können, finden Sie heutzutage wahrscheinlich in
jedem
besseren Haushaltsgerät (und wenn Sie ein gegen Covid-19 geimpfter Verschwörungstheoretiker
sind, dann
höchstwahrscheinlich auch in Ihrem Blut). Grund genug, sich diese genauer anzuschauen.
Genau wie bei der Addition von Dezimalzahlen gibt es pro Stelle ein Ergebnis und einen
Übertrag (Englisch carry); beispielsweise ergibt
"sechs plus acht plus sieben = eins, zwei gemerkt", und die 2 muss dann zu den links daneben
stehenden Ziffern addiert werden. Binär ist alles viel einfacher, weil man sich nur 0 oder 1
merken muss.
Das Carry wird dann der eins weiter links stehenden Stelle zur Addition weitergegeben.
Aber halt! Das heißt doch, dass wir in der nächsten Stelle drei Bits addieren
müssen:
das von , das von , und das eingehende Carry . Daraus berechnet
sich das Output-Bit und das ausgehende Carry , das nach links weitergegeben
wird.
Unsere Tabelle ist also etwas größer:
Glücklicherweise muss man sich diese Tabelle nicht merken, es gilt nämlich
Die Funktion Maj haben wir schon oben kennengelernt; sie gibt 1 aus, wenn mindestens zwei
ihrer drei Input-Bits 1 sind. Wir kürzen Sie im folgenden mit ab. Hier ist nun
unser -Bit-Addierer:
Wir numerieren unsere Variablen von , damit wir die
repräsentierte natürliche Zahl bequem als
schreiben können. Beachten Sie auch die
unvermittelt rechts stehende 0. Streng genommen lässt unsere Definition
von Schaltkreisen so etwas nicht zu; allerdings ist es recht einfach,
die Definition entsprechend zu verallgemeinern, oder aber den obigen
Schaltkreis zu vereinfachen: würde dann zum Beispiel einfach zu
werden).
Wie groß ist dieser Schaltkreis? Wenn wir jedes - und -Gate als ein
Gate
zählen, so haben wir insgesamt innere Gates und Input-Gates.
Wenn wir darauf bestehen, - und -Gates aus AND/OR/NOT zu basteln,
haben wir mehr, allerdings in jedem Fall viele.
Übungsaufgabe 2.3.1
Bauen Sie einen Schaltkreis oneBitAdder: für
folgende Funktionalität:
Versuchen Sie, so wenig Gates wie möglich zu verwenden. Entscheiden Sie sich
im Voraus, ob Sie beliebigen Fan-in erlauben wollen oder auf Fan-in 2 bestehen.
Eine Größe von ist gut; besseres können wir nicht wirklich erwarten, schließlich
muss der Schaltkreis ja allein schon Input-Gates haben. Wie steht es mit der Tiefe?
Wenn wir jedes und als Gate
betrachten, dann hat der Pfad von zu die Länge (beachten Sie:
das ganz links stehende ist streng genommen bereits das Output-Gate; die ausgehende
Kante ist also vielmehr ein Output-Label als eine ausgehende Kante und daher bei der
Tiefenbestimmung nicht mitgezählt). Der Addier-Schaltkreis hat also Tiefe .
Eine Tiefe von ist in der Regel nicht gut. Idealerweise streben
wir eine Tiefe von an (oder wenn wir besonders ambitionert sind und
beliebig großen Fan-in zulassen, sogar konstante Tiefe ).
Carry Look-Ahead
Tiefenmässig hat uns der obige Addierer nicht befriedigt. Das Problem ist, dass das Carry
im schlimmsten Fall von ganz rechts nach ganz links durchrasseln muss,
zum Beispiel wenn man berechnet. So einen Addierer nennt man
ripple-carry adder.
Geringere Tiefe erreicht man mit Carry Lookahead. Hier versuchen wir,
im Voraus bereits das Carry auf Position zu berechnen, ohne erst das auf Position
abzuwarten. Es lohnt, ein paar Dinge formaler zu definieren:
ist das Carry, das von Position an Position übergeben wird;
ist das -te Bit der Summe (von rechts nach links zählend, bei 0 beginnend).
Es gilt also und
und das Ergebnis ist dann .
Bauen wir uns doch mal einen Schaltkreis, der berechnet; wir können
zum Beispiel den obigen Ripple-Carry-Adder nehmen und alles löschen, was nicht
zur Berechnung von beiträgt:
Betrachten Sie . Wenn dies ist, so ist
;
wenn , so ist . In beiden Fällen spielt
alles, was vor kommt (rechts davon) spielt keine Rolle. Wenn nun
ist, dann schaltet das -Gate einfach unverändert durch.
Dies motiviert die folgenden Definitionen:
Jetzt können wir wie folgt berechnen: an Stelle wird ein Carry
ausgegeben, wenn es eine Stelle gibt, wo gilt, das
Carry also erzeugt wird (carry generate), und dann
für alle das Carry zumindest weitergereicht wird,
also gilt (carry propagate):
Und voilà: dies ist ein Schaltkreis für und hat Tiefe 2 (wenn wir und als
Inputgates betrachten).
Wie groß ist er? Er hat OR-gates und
AND-gates, insgesamt also Gates.
Wir konstruieren nun für jedes einen Schaltkreis und setzen
dann einen Schaltkreis für oben drauf.
Insgesamt haben wir Tiefe , wenn wir beliebigen Fan-in zulassen;
wenn wir Fan-in 2 wollen, bekommen wir Tiefe .
Die Anzahl der Gates ist in jedem Fall , genauer gesagt
etwa . Dies scheint
recht verschwenderisch. Für wie bei zeitgenössischen Rechnern
ergibt dies 41664. Das ist nicht unerträglich groß, aber auch nicht besonders handlich.
Carry-Lookahead mit Gates und Tiefe
Effizientere Lösungen beginnen oft mit einer guten Definition. In diesem Falle ist das die
Verallgemeinerung von Carry Generate und Carry Propagate
von Indizes auf Intervalle. Für ein Interval
definieren wir Funktionen
und in den Variablen
wie folgt:
soll 1 sein , wenn an Stelle ein Carry nach
geschickt wird,
selbst wenn von nach kein Carry reinkommt; wenn also ist,
selbst wenn ist;
soll 1 sein, wenn an Stelle ein Carry nach rausgeschickt wird,
falls eines von nach reingeschickt wird.
Definition (Carry propagate / generate für Intervalle) 2.3.1
Sei ein Intervall natürlicher Zahlen. Die Werte
und sind wie folgt definiert:
Wenn für alle das Paar den Wert oder
hat,
dann ist und .
Ansonsten sei .
Falls , dann sind .
Falls , dann sind .
Insbesondere für ein-elementige Intervalle gilt
und .
Hier sind Boolesche Formeln (in DNF) für und :
und hier ein Bild zur Illustration:
Im Extremfall gilt , also wie
die "alten" individuellen .
Diese Definition ist nützlich, da erstens
gilt, wir also die Carrys direkt ablesen, zweitens
wir die bequem rekursiv berechnen können:
Beobachtung 2.3.2
Wenn wir schreiben für , dann gilt
Überzeugen Sie sich von der Richtigkeit entweder durch Nachdenken (z.B. "damit im Interval
ein
Carry erzeugt wird, muss es entweder im höheren Interval erzeugt werden, oder im
niedrigeren,
und dann im
höheren weitergegeben werden") oder indem Sie explizit die Definitionsformel
von nehmen und in der unteren Hälfte "ausklammern".
Die Idee ist jetzt, dass wir mit Hilfe dieser Formel die Funktionen für alle
Intervalle berechnen und dann mittels
unmittelbar die Binärdarstellung der Summe
ausgeben
können.
Das Problem: es gibt zu viele Intervalle, nämlich viele.
Der Trick: wir beschränken uns auf besonders schöne Intervalle, nämlich
Binärintervalle.
Definition 2.3.3
Ein Binärintervall ist eine Menge natürlicher Zahlen der Form
Es wird eventuell klarer, wenn wir uns die Binärdarstellung anschauen: ist ein
Binärintervall, wenn und folgende Binärdarstellung haben:
und , wobei selbst aus mehreren Bits bestehen
kann.
Beispielsweise ist ein Binärintervall mit und ; in der
Binärdarstellung
ist das . Wir können es also auch mit
in Sternchennotation mit [11**] notieren.
Übungsaufgabe 2.3.2
Seit . Wie viele Binärintervalle gibt es in
?
Lemma 2.3.4
Es gibt einen Schaltkreis mit Input-Gates mit
Größe und Tiefe und
Output-Gates
für jedes Binärinterval .
Beweis.
Ein Binärintervall, zum Beispiel können wir
als Vereinigung von zwei kleineren Binärintervallen schreiben, hier
. In unserer Sternchen-Notation:
Ganz allgemein gilt und somit,
mit Hilfe von Beobachtung 1.10:
Wir basteln uns also am Besten ein "-Gate" mit vier Inputs und zwei Outputs:
Mit Hilfe dieses Gates können wir
und
zu kombinieren. Wir fangen
nun mit Binärintervallen der Größe an und arbeiten uns hoch.
Der Übersichtlichkeit halber schreiben wir für das Paar
. Für schaut das so aus:
Jede Kante steht hier für zwei gebündelte Kanten, nämlich
und .
Und dies ist unser Schaltkreis. Beachten Sie, dass
die untersten keine Input-Gates sind, sondern
wiederum aus berechnet werden mittels
bzw. . Der
Schaltkreis hat also Input-Gates und pro
Binärinterval zwei Output-Gates: .
Jedes im obigen Bild ist also gleichzeitig ein Output-Gate
(auch wenn es für weiter oben "wiederverwendet" wird; nirgendwo
steht geschrieben, dass ein Output-Gate keine ausgehenden Kanten
haben darf). Sie sehen, dass maximal viele
-Gates hintereinander kommen. Die -Gate-Tiefe ist
also . Jedes -Gate hat an sich Tiefe
2 (siehe oben), und mit den untersten kommt noch einmal
eine Schicht dazu. Die Gesamttiefe des oben abgebildeten Schaltkreises
ist also
Um die Größe zu bestimmen,
beachten Sie, dass der Schaltkreis wie ein Binärbaum aus
-Gates aussieht. Für -stellige Zahlen
gibt es also viele -Gates, von denen jedes vier
"normale" Gates hat. Pro unterstem kommen noch einmal
zwei hinzu: ein -Gate für und ein
-Gate für . Insgesamt also
wie behauptet.
Eine anschließende Bemerkung: der Schaltkreis
besteht ausschließlich aus AND- und OR-Gates; er enthält
keine NOT-Gates und ist somit ein monotoner Schaltkreis.
Lemma 2.3.5
Es gibt einen Schaltkreis der Größe und
Tiefe , der die für Binärintervalle
als Inputs nimmt und daraus berechnet für jedes
, also insgesamt Output-Gates.
Beweis.
Die Idee ist, dass wir jedes als Vereinigung
von wenigen Binärintervallen schreiben können, zum Beispiel
Wie kann man das formalisieren?
1. Fall. Wenn die Form hat, dann ist
bereits ein Binärintervall; dies passiert, wenn
die Binärdarstellung von die Form hat (wobei
sein kann, in welchem Fall ist).
2. Fall. Wenn nicht die Form hat,
dann hat die Binärdarstellung von nicht die Form ; sie
enthält also auch nicht-führende Nullen (beachten Sie, dass und
die
gleiche natürliche Zahl darstellen, nämlich 7).
Die Zahl hat also die Form für und .
Definieren
wir . Dann gilt
. Es gilt also
es handelt sich bei also um ein Binärintervall.
Eine äquivalente Definition von
wäre: das kleinste , so dass ein Binärintervall ist.
Wir zerlegen nun
und berechnen so:
Der Wert ist bereits ein Binärintervall, kann also
direkt aus dem vorherigen Schaltkreis abgelesen werden; der andere
Input, , muss rekursiv berechnet werden. Beachten Sie,
dass gilt, die Anzahl der schließenden 1en in der
Binärdarstellung
also von auf gewachsen ist. Hier ist ein Beispiel
für , in Dezimal- und Binärdarstellung:
Beachten wir die Folge , also immer die Endpunkte der betreffenden
Intervalle; wir haben gesehen, dass die Anzahl der schließenden 1en von Element zu Element
in dieser Folge strikt zunimmt (im Allgemeinen
folgt auf Element das Element .
Die Anzahl schließender 1en ist mindestens 0 (trivialierweise) und höchstens ;
ein genauerer Blick zeigt, dass sie sogar höchstens ist: wäre sie genau ,
dann hätten wir es mit der Zahl zu tun; da aber
die Folge der Zahlen abnimmt, müsste bereits gelten, und wir
wären in Fall 1, bzw. bereits fertig, weil bereits in Binärintervall wäre.
Der Wert von , der Anzahl schließender 1en, kann also höchstens
mal zunehmen, und somit brauchen wir höchstens
viele -Gates. Das Maximum wird erreicht, wenn :
Tiefe. Jedes -Gate hat Tiefe 2,
und somit erreichen solche Gates hintereinander
zusammen eine Tiefe von .
Größe. Zählen wir die Anzahl von -Gates in unserem
Schaltkreis. Wir haben bereits gesehen, dass die Berechnung von höchstens
viele -Gates braucht. Es scheint also, als bräuchten wir für alle
Werte
von insgesamt viele. Wir könnten vielleicht noch
eine
genauere Schranke beweisen, weil nicht jedes den Wert erreicht, allerdings
würde auch eine genauere Rechnung ) ergeben; das ist nicht
schlecht,
allerdings asymptotisch deutlich schlechter als der Ripple-Through-Addierer, und eben
auch nicht das, was wir im Lemma behaupten.
Nein, es ist in der Tat viel einfacher. Schauen Sie, wir berechnen oben
zum Beispiel , in dem wir
(was bereits als Input bereitgestellt wird, weil ein
Binärintervall ist)
mit kombinieren.
Nun muss aber nicht für "bezahlen", weil wir es für
eh berechnen müssen! In anderen Worten, jedes
ist ja nicht nur Zwischenergebnis, sondern selbst Output-Gate, und
somit ist die Anzahl der -Gates gleich der Anzahl der Output-Gates, nämlich genau
.
Sogar weniger, weil wir gar kein Gate brauchen, wenn bereits ein Binärinterval
ist.
Hier ist die Konstruktion für :
Wir benötigen also (weniger als) viele -Gates; jedes davon
besteht wiederum aus 4 Basis-Gates , und somit
brauchen wir maximal Gates. Wenn wir ganz genau hinsehen,
können wir es mit Gates schaffen: da ,
brauchen wir ja gar nicht. Auch, um
aus und zu berechnen,
brauchen wir nicht:
Es reichen also in der Tat Basis-Gates aus.
Wir kombinieren wir nun die beiden Lemmas und erhalten einen Schaltkreis der Tiefe und
Größe , mit als Inputvariablen und Carrys
als Output-Variablen. Jetzt können wir schließlich
berechnen. Dafür brauchen wir noch einen XOR-Schaltkreis mit 3 Input-Gates. Meine
Konstruktion
hierfür
hat 12 Gates (davon 3 NOT-Gates) und Tiefe 4 (wenn wir die NOT-Gates nicht mitzählen.)
Insgesamt gibt das also
Gates
und Tiefe
Übungsaufgabe 2.3.3
Bauen Sie einen Schaltkreis für die Boolesche Funktion
"less-than":
der also als -stellige
Binärzahlen interpretiert und ausgibt, ob
die erste kleiner ist als die zweite.
Ihr Schaltkreis sollte im Idealfall Größe und
Tiefe und Fan-in 2 haben.