Home | Artikel | VCFe Rechner Grundlagen

VCFe Vortrag vom 2006.05.01 - Rechnen mit Bauklötzchen - Rechnergrundlagen und deren Implementationen


Inhalt

Einführung

Dieser Vortrag beschreibt die Grundlagen, wie ein binärer Digitalrechner funktioniert, und wie man diesen in völlig verschiedenen Formen von Hardware herstellen kann. Abgedeckt werden einerseits die Funktionsweise von Bits und Logik, über Rechnen in Bits, bis hin zum Prozessor, anderseits Implementationen in Mechanik, Elektromechanik, Elektronik und integrierten Schaltungen.

Dabei soll gezeigt werden, dass Bits eine grundlegende mathematische Sache sind und keine elektrische, und dass sie auch ohne Strom leben können.

Es wird angenommen, dass der Zuhörer die Hardware Seite von Rechnern bisher nicht gross kennt, ausser Platinen oder gar nur Geräte zusammenzustecken und drauf Software zu installieren, also keine Kenntnis vorhanden ist, was drinnen in den Teilen auf den Platinen passiert.

Es wird lediglich vorausgesetzt, dass der Zuhörer die Grundlagen von programmieren kennt (und sei dies nur Scripte) und Konzepte wie Variablen, Ausdrücke, Zuweisungen, Schleifen und Unterprogramme kennt.

Dieser Vortrag ist eine stark abgewandelte (einiges gekürzt und mit anderem erweitert) und überarbeitete Form eines früheren Vortrages zum Thema Offene Hardware herstellen.

Dieser Vortrag wurde später, für eine Wiederholung am 2007.01.12 in der LUGS leicht überarbeitet (Tippfehler, Formulierungen verbessert und expandiert, z.T. verständlichere Erklärungen, Formatierung, mehr Links addiert).

Binäre Mathematik

Bevor man den Rechner anschaut, sollte man zuerst mal wissen, was der eigentlich anstellen soll. Also zuerst mal eine Einführung in Rechnen in Bits.

Man hat bereits im 17. Jahrhundert Rechenmaschinen gebaut. Blaise Pascal (1623-62), Sohn eines Steuerbeamten, hat damals einen mechanischen Addierer für seinen Vater gebaut. Ältere Zuhörer werden sich noch an die mechanischen Registrierkassen errinnern, die früher in den Läden benutzt wurden, die "300 70 5 + <ratter> <ratter>" Dinger. Dort war die selbige Mechanik drin, nur mit Elektromotor statt Kurbel.

Achtung: Der Abakus ist viel älter (Jahrtausende), ist aber kein Rechner, sondern nur ein Speicher. Gerechnet wird im Kopf, der Abacus ist nur ein externer eine Zahl grosser (Zwischenergebnis) Speicher der das Gedächtnis entlastet, und so schneller Rechnen erlaubt. Das selbige Verfahren wie beim schriftlichen Rechnen, aber ohne Papierverbrauch, weil löschbar.

Problem all dieser Rechner war, dass sie kompliziert waren, weil sie dezimal rechneten. Alleine eine 1-stellige Addition von 2 Ziffern hat 10 mögliche Ziffern bei jedem Operanden, und damit 10*10=100 mögliche Kombinationen. Wenn man für Ergebnisse grösser 9 auch noch einen Übertrag will, gibt das eine zweite Einrichtung mit 100 Kombinationen. Will man mehrziffrige Zahlen Addieren hat man wegen dem Übertrag der 0 oder 1 sein kann, ab der zweiten Ziffer sogar 10*10*2=200 Möglichkeiten, mal 2 Einrichtungen. Multiplikation ergibt sogar 10*10*10=1000 Kombinationen, mal 2 Einrichtungen, weil man 10 mögliche Überträge hat.

Auch der Versuch von Charles Babbage (1791-1871) Mitte 1800ern einen Computer zu bauen, um Tabellen von Navigationsdaten automatisch und damit fehlerfrei zu rechnen, und ohne Person dazwischen fehlerfrei auszudrucken, ist letztlich daran gescheitert, dass dies mechanissh zu kompliziert war. Trotz Finanzierung durch die britische Marine, die gestrandete und gesunkene Schiffe wegen Rechen- und Druckfehler in Tabellenbüchern einsparen wollte.

Der Computer wurde realistisch möglich, als man auf binäre Zahlen umstellte, also Zahlen mit nur 2 Ziffern. Das vereinfachte die Rechner gewaltig (nur noch 2 Einrichtungen mit je 2*2*2=8 Kombinationen, sowohl für Addition wie auch für Multiplikation). Die einzelnen Ziffern von binären Zahlen werden Bits (Binariy digITS) genannt.

      Beispiel binäres Rechnen mit vorzeichenlosen 8bit Zahlen
      (Bereich von 0..2^n-1, für n=8: 0..2^8-1=0..255)

        7654 3210   Nummer n des Bits, n=7..0

        1           Wert des Bits, für n=7..0:, 2^n=128..1
        2631
        8426 8421
        ---- ----
        0100 1100   Operand1   | =      64      +8+4     =  76
      + 0110 0111   Operand2   | =      64+32     +4+2+1 = 103
      ( .  . .      Übertrag)  |
        ---- ----              |                           ---
        1011 0011   Ergebnis   | =  128   +32+16    +2+1 = 179
    

Weil Binärzahlen sehr schnell sehr lang werden, fasst man oft 4 Bits zusammen, zu Hexadezimalen Zahlen, nach folgender Methode:

         0000 -> 0   0001 -> 1   0010 -> 2   0011 -> 3
         0100 -> 4   0101 -> 5   0110 -> 6   0111 -> 7
         1000 -> 8   1001 -> 9   1010 -> A   1011 -> B
         1100 -> C   1101 -> D   1110 -> E   1111 -> F

         0000 0000 -> 00   0000 1111 -> 0F
         0001 0000 -> 10   0001 1111 -> 1F
         0010 0000 -> 20   ...
         ...               1111 1111 -> FF
    

Früher wurden auch Oktale Zahlen benutzt, bei denen man 3 Bits zusammenfasste, mit den Ziffern 0..7. Das braucht keine Ziffern A..F, aber reduziert die Stellen nur um 3 statt um 4. Oktal designte Rechner hatten oft durch 3 teilbare Wortbreiten. Demensprechend kamen hexadezimal designte Rechner auf, nachdem auf 2^n Bits Wortbreite standardisiert wurde, die nicht durch 3 teilbar sind.

Vorzeichen und damit negative Zahlen könnte man damit machen, indem man eine Zahl und ein separates Vorzeichen Bit verwendet. Aber dies ist zum Rechnen aufwendig (abhängig von Vorzeichen Addition durch Subtraktion ersetzen).

Zum Glüeck gibt es eine einfachere Lösung: Zählt man 0,1,2,...255,0,... überläuft unsere 8bit Zahl von 255 auf 0, weil der Computer nur eine endliche Menge Zahlen fassen kann. Wenn man nun von 0 einen Schritt zurückgeht, was -1 geben sollte, landet man auf 255. Man definiert nun einfach, dass 255 = -1 ist, 254 = -2, und so weiter. Damit kollidieren die 0..n positiven und -1..-n negativen Zahlen irgendwo in der Mitte. Man spaltet dabei der ganzen Zahlenbereich von 0..(2^n)-1 (hier bei 8bit 0..255) in 2 Hälften, von denen eine zum negativen Bereich -2^(n-1)..(2^(n-1))-1 (hier -128..127) uminterpretiert wird, und der verbleibende Rest von n-1 Bits (hier 7bit) als positiven Bereich 0..(2^(n-1))-1 (hier 0..127) erhalten bleibt, womit auch noch das linke Bit zum Vorzeichen wird, mit 0=positiv und 1=negativ:

       Binär         Dezimal ohne / mit Vorzeichen
       .... ....
       0000 0001                1    +1
       0000 0000                0     0      A 255 -> 0
       1111 1111              255    -1      | = Überlauf ohne Vorzeichen
       1111 1110              254    -2          nicht 256, da nur 8bit Bereich
       .... ....
       1000 0001              129  -127
       1000 0000              128  -128      A +127 -> -128
       0111 1111              127  +127      | = Überlauf mit Vorzeichen
       0111 1110              126  +126          nicht 128, da nur 7bit Bereich
       .... ....
       0000 0010                2    +2
       0000 0001                1    +1
       0000 0000                0    +0      | 0 -> 255
       1111 1111              255    -1      V = Unterlauf ohn Vorzeichen
       .... ....                                 nicht -1, da voll 8bit Bereich
       ^----------- Vorzeichen falls eines definiert wird, 0 = +, 1 = -
    

Dieses Verfahren wird 2er Komplement genannt. Weil die Null damit eine positive Zahl an Platz verbraucht, hat es immer eine negative Zahl mehr, hier die -128.

Früher wurden zum Teil noch Rechner benutzt, wo es eine -0 gab (alles 1er), und damit einen symmetrischen Zahlenbereich, das nannte man 1er Komplement. Diese Rechner waren mathematisch schöner, aber komplexer zum herstellen und sind somit verschwunden.

Boolsche Logik

Computer bestehen aus vielen kleinen simplen Bauteilen, den Logik Elementen. Von denen muss man mehrere oder gar viele zusammensetzen um zu rechnen. Um die Zusammenhänge simpel zu beschreiben, ohne dauernd Skizzen zu machen, wird eine Notation namens Boolsche Logik verwendet. Diese ist eine sehr simple Form der Mathematik mit nur 2 Ziffern, 0 und 1, wie es Bits auch sind. Aber statt so komplizierten Sachen wie Addition und Multiplikation sind hier auch nur simple Sachen definiert wie:
UND (englisch AND), Zeichen: &
Wenn alle Operanden (a&b&...) 1 sind, ist das Ergebnis 1 (sonst 0). Dies ist zufällig identisch mit Multiplikation ohne Übertrag. Man merke: UND ist trotz naheliegendem Namen keine Addition, und hat daher ein & (Und) und nicht ein + (Plus). UND kann aber auch als "wenn a lass b durch" angeschaut werden
ODER (englisch OR), Zeichen: |
Wenn auch nur ein Operand (a|b|...) 1 ist, ist das Ergebnis 1. Dies entspricht eher einer etwas queren "Addition", wo alles über 0 gleich 1 ist (wir haben ja nur eine Stelle und nur 2 Ziffern!). ODER kann aber auch als "sammle Bits" angeschaut werden
EXODER (englisch XOR), Zeichen: ^
Wenn a oder b gleich 1, aber nicht beide(!), dann ist das Ergebnis 1. Dies is zufällig die hintere Stelle einer echten Addition, dezimal 2 ist ja binär 10. EXODER kann aber auch z.B. 2 Bits vergleichen (1 bei ungleich). EXODER kann aber auch als "wenn a invertiere b" angeschaut werden

Als Tabelle der Grundoperationen sieht zusammengefasst so aus:

      a b   & | ^ 
      ---   -----
      0 0   0 0 0
      0 1   0 1 1
      1 0   0 1 1
      1 1   1 1 0
    

Schliesslich gibt es noch die NICHT (englisch NOT) Zeichen: ~ Funktion, mit nur einem Operand. Dessen Ergebnis ist NICHT (= Gegenteil vom) Operand, also: ~0=1 und ~1=0. Man nennt diese Operation Inversion. Sie ist vergleichbar mit einer Negation (Minus). 2 mal Inversion (NOT NOT) heben sich auf, wie 2 mal Negation (Minus Minus gibt Plus).

Die Kombination (a & b) | (c & d) ist übrigens so häufig, dass man diese auch ohne Klammern standardmässig so berechnet, wie man auch bei einem (a * b) + (c * d) man zuerst beide Multiplikationen ausrechnet. Ebenso kommt Inversion vor allem anderen, wie Negation. Ebenso sind UND und ODER kommutativ wie Multiplikation und Addition.

Diese Boolsche Logik ist übrigens ein klassischer Fall von etwas das lange vor seiner Nützlichkeit entdeckt wurde, George Boole (1815-64) war Mathematiker im 19. Jahrhundert. Das war, wie man unschwer sieht, im 19. Jahrhundert nur eine mathematische Kuriosität, komplett nutzlos. Heute kann man damit Rechner Logik beschrieben, genauso wie man mit Formeln Programme schreiben kann.

Digital Rechnen mit Boole

Mit dieser Booleschen Logik kann man nun tatsächlich auch die richtige Additionen rechnen, wenn man mehrere dieser einfachen Elemente zusammensetzt.

Wenn man die eine einziffrige Addition sum=a+b anschaut, so sieht man, dass die hintere Stelle davon ein EXODER ist. Und der Übertrag ist ein UND. Dieses EXODER/UND Paar nennt man einen Halb-Addierer, weil er nur a+b, aber nicht +Übertrag rechnen kann:

                sum  sum
       a   b    dez  bin   üb summe
       0 + 0  = 0  = 00  = 0  0
       0 + 1  = 1  = 01  = 0  1
       1 + 0  = 1  = 01  = 0  1
       1 + 1  = 2  = 10  = 1  0
                 a UND b _/    \_ a EXODER b
    

Aufwendiger wird es wenn man dann die zweite oder folgenden Ziffern rechnen will. Da muss man noch den Übertrag (der bisher immer 0 war) berücksichtigen, für einen Voll-Addierer sum=a+b+üb. Der Aufwand steigt auf 2 2-Eingang EXODER, 3 2-Eingang UND, sowie 1 3-Eingang ODER:

                           sum  sum
               a   b   üb   dez  bin   üb summe
               0 + 0 + 0  = 0  = 00  = 0  0
               0 + 1 + 0  = 1  = 01  = 0  1
               1 + 0 + 0  = 1  = 01  = 0  1
               1 + 1 + 0  = 2  = 10  = 1  0
               0 + 0 + 1  = 1  = 01  = 0  1
               0 + 1 + 1  = 2  = 10  = 1  0
               1 + 0 + 1  = 2  = 10  = 1  0
               1 + 1 + 1  = 3  = 11  = 1  1
      üb = (a UND b) ODER (a UND üb) _/    \_ sum = (a EXODER b) EXODER üb
                     ODER (b UND üb)
    

Die hintere Stelle ist schlicht a und b addieren (was nie mehr als 2 geben kann), und dann noch den Übertrag hinzuzählen (das geht nie über 3). Für den Übertrag braucht man Tests auf "über 1", was der Fall ist wenn irgend eine der 3 Kombinationen 2 gibt (die UND laufen als "beide 1 Tester", das ODER läuft als "über 1 Sammler"), für alle 3 Eingaben sind dann schlicht alle 3 UND aktiv, das ODER kümmert das nicht.

Dies alles muss man nun für die Anzahl Bits wiederholen. Das kostet ordentlich grossen Aufwand (Bits mal 6 Elemente). Und das ist immer noch ein ineffizienter Addierer. Es muss für jedes Bit zuerst das Übertrag-aus des Vorgängers bis zu Bit seinem Übertrag-ein kommen, das nennt man kaskadierten Übertrag (ripple carry). Und das ist langsam, weil man pro Bit die UND+ODER Durchlaufzeit abwarten muss. Man kann dies beschleunigen (mit einem look-head carry), aber das kostet weiteren Aufwand.

Und dann sollte ja noch die Subtraktion rein. Die kann man zum Glück recht einfach rechnen, indem man diff=a-b als diff=a+(-b) rechnet. Also den bestehenden Addierer um einen schaltbaren Negierer beim zweiten Operand erweitert. Der Aufwand dafür ist hauptsächlich ein steuerbarer Invertierer pro Bit (ein drittes EXODER). Dazu noch bei der ersten Stelle den Übertrag auf 1 zwingen. Oder aber den Ersatz des ersten EXODER durch eine kompliziertere Schaltung (ALU, siehe weiter unten), die aber massiv weiteren Aufwand verlangt.

Multiplikation oder gar Division brauchen noch viel mehr Aufwand. Für 32bit*32bit muss man z.B. 32 separate Additionen zu je 32bit ausführen. Entweder hintereinander in der selbigen Hardware (kostet 32 Taktzyklen), oder für jede Addition eine Wiederholung der obigen Hardware (kostet 32 mal Hardware).

Man sieht auch langsam, wohin all die Millionen Transistoren der modernen Chips hingehen. Bzw warum man früher massive Gestelle voller vieler Schaltungen benötigte (und warum Computer früher so teuer waren). Wie man sieht, wurde es aus gutem Grund erst so um 1990 (486er, 1.08 Millionen Transistoren) im PC zum Standard, die ganze Multiplikation alles aufs mal zu rechnen. Bei 8bit Prozessoren ist bis heute nur möglicherweise Multiplikation (z.B. 6809 oder AVR) und sehr selten Division (z.B. 8051) im Chip drin. Selbst manche 32bit RISC Prozessoren haben keine Division drin (z.B. ARM). Frühe Sparcs hatten beides nicht.

Schalten mit Boole

Neben Rechnen will ich hier noch ein zweites häufiges Beispiel für digitale Logik bringen. Ein Computer muss neben rechnen vor allem eins können: Daten auswählen zum damit rechnen. Auch so ein "Schalter" lässt sich mit Boolscher Logik machen.

Da UND ein "Durchlass" und ODER ein "Sammler" sein kann, kann man diese dazu verwenden. Dazu bekommt jeder wählbare Eingang ein UND, der neben den Daten einen "Steuereingang" hat. Die Ausgänge aller UND werden von einem breiten ODER gesammelt.

Dies ist ein häufiger Sonderfall von (a & b) | (c & d), wo a und c die Eingänge e1 und e2 sind, und b und d die Steuereingänge s1 und s2 sind.

Sehr oft will man, dass ein Ausgang gleich einen oder den anderen Eingang ist. Um zu wählen welchen, will man nur ein Auswahl-Signal. Wenn dieses 0 ist soll der erste Eingang, bei 1 der zweite Eingang herauskommen:

      e0 e1 wahl  aus
      0  0   0   = 0 (= e0)
      0  1   0   = 0 (= e0)
      1  0   0   = 1 (= e0)
      1  1   0   = 1 (= e0)
      0  0   1   = 0 (= e1)
      0  1   1   = 1 (= e1)
      1  0   1   = 0 (= e1)
      1  1   1   = 1 (= e1)
                    \_ (e0 UND (NICHT wahl)) ODER (e1 UND wahl)
    

Für e1 ist das UND ein normales "lass e1 durch wenn wahl=1". Für e0 will man nun durchlassen wenn wahl=0, also kehrt man wahl um, mit NICHT, um seinem UND auch ein 1 zu bieten. Man nennt so eine Kombination einen Multiplexer, hier genauer ein 2:1 Multiplexer.

Man kann auch 4 Eingänge e0..e3 mit 4 UND und entweder 4 Steuereingänge oder 2 Auswahleingänge (w0..w1) mit 4 Inverter Kombinationen ausführen. Das ist dann ein 4:1 Multiplexer. Mit 8 Steuereingängen oder 3 Auswahl w0..w2 kann man auch einen 8:1 mit e0..e7 haben, usw. Mit n Auswahl kann man immer (2^n):1 Signale multiplexen, weil man aus n Auswahlsignalen 2^n Steuereingänge durch NICHT und UND generieren kann. Die n Auswahl Signale kann man als n-bit breite Zahl auffassen, mit Wert 0..(2^n)-1, die man Adresse nennt.

Logiktabellen und Arithmetisch-Logische Einheit (ALU)

Oben bei der Addition haben wir gesehen, dass man 2 EXODER hintereinander braucht, bei schaltbarer Addition/Subtraktion sogar 3. Der Aufwand steigt. Sobald wir nun aber Multiplexer mit mehreren Auswahl Signalen haben, können wir einen anderen Ansatz wählen, die Logiktabellen gewünschter komplexer mehrteiliger Funktionen direkt als solche auszuführen.

Die oben aufgeführte Funktion für sum=a+b+üb (hinterste Spalte) von Digital Rechnen mit Boole weiter oben hat als Ergebnis 01101001, bzw die für den Übertrag hat 00010111. Wir können nun diese beiden Funktionen statt als 2 EXODER, bzw 3 UND plus 1 ODER auch einfach als 2 8:1 Multiplexer ausführen, mit a,b,üb als Auswahl Signalen und die beiden obigen Tabellen als deren 8 Eingänge.

Für die zusätzliche Subtraktion können wir einfach die Auswahl Add/Sub als vierten Auswahl Eingang zweier nun 16:1 grossen Multiplexer, mit je einer 16 grossen Tabelle verwenden.

Dieses Verfahren ist einfach zu entwerfen (keine Zerlegung der Logik in Grundfunktionen), und kann beliebig komplexe Funktionen, auch mit einigen oder vielen Eingängen durchführen. Es hat auch eine konstante recht schnelle Geschwindigkeit (nur die Durchlaufzeit von 1 mal NICHT+UND+ODER, identisch einem einzelnen Multiplexer oder EXODER). Es wird daher sehr gerne in der Ablaufsteuerung von Prozessoren eingesetzt, wo komplexe Funktionen dominieren. Z.B. wurde der Befehlsdekodierer des 1952/53er Röhrenrechners Whirlwind durch 120 Tabellen zu je 40 (5 Eingänge zu 2^5=32 Operationen + 8 Zusatzsignale) Bits implementiert.

Multiplexer Tabellen haben auch einen gravierenden Nachteil, sie sind teurer an Bauelementverbrauch als simple Logik. Man kann es aber etwas billiger haben, wenn man bei jedem Tabellenelement das 0 ist das zugehörige UND aus dem Multiplexer weglässt (und das ODER schmaler macht), und bei mehreren Tabellen (z.B. sum und üb) von den selbigen Eingängen (a und b und üb) die Inverter der Auswahl->Steuereingang Logik teilt.

Neben dieser Methode mit konstanten Tabellen, kann man aber auch mit variablen Tabellen arbeiten. Dabei werden die Tabellenwerte selber wiederum generiert. Ein einfacher 4:1 Multiplexer, mit einer 4 Bits grossen Tabelle als ihre 4 Eingangsbits e0..e3 vorgegeben, und die 2 zu kombinierende Datenbits a und b als Auswahlleitungen w0..w1, kann (wenn man an die 4 Eingansbits geeignete Tabellen anlegt) jede logische Kombination erzeugen:

      e0..3           e0..3         jeweils a=1,b=1 a=0,b=1 a=1,b=0, a=0,b=0
      0000 -> 0       1111 -> NICHT-0 = 1
      1010 -> a       0101 -> NICHT-a
      1100 -> b       0011 -> NICHT-b
      1000 -> UND     0111 -> NICHT-UND
      1110 -> ODER    0001 -> NICHT-ODER
      0110 -> EXODER  1001 -> NICHT-EXODER
    

Sowas ergibt dann eine programmierbare Logikeinheit mit 2^4=16 Funktionen. Die DEC PDP-10 Rechner (mitte 1960er) haben z.B. beliebige Logikfunktionen in einem Program ermöglicht, indem die Hardware einfach pro Datenbit (36 davon) je einen solchen 4:1 Multiplexer hatte, und die gewunschte Funktion einfach als 4bit Tabelle in das Befehlswort codiert wurde. Der Befehl "Setze alle Bits der Variable b" wurde ausgeführt als "nehme Variable a und b" und gehe durch die Tabelle die immer 1 macht (1111) und speichere das Resultat in b".

Wenn man bereits eine solche Logikeinheit hat, und anderseits schnelle Übertragrechnung will (pro Bit nur das UND+ODER abwarten) von expliziter Logik, kann man auch beide Ansätze kombinieren. Der erste EXODER der Addition (und das zusätzliche der Subtraktion, und die ADD/Sub Auswahl) rechnet man per Logikeinheit (EXODER oder NICHT-EXODER). Die Übertrag Generierung und das zweite EXODER dagegen geschieht traditionell. Wenn man auch noch die Übertrag Schaltung abschaltbar macht (ein UND als Schalter in der Leitung von Übertrag zum EXODER) bekommt man dann eine 2*16=32 Funktionen Arithmetisch-Logische Einheit (ALU), z.B. den 74181 4bit ALU TTL Chip.

Aber auch die alles-als-Tabelle Version kann man hier machen. Die 2*4bit Daten-Eingabe, 1bit Übertrag-Eingabe und 5bit Funktionswahl (=14 Eingänge) sowie 4bit Daten-Ausgabe, 1bit Übertrag-Ausgabe (= 5 Ausgänge) lassen sich als einen Satz von 5 2^14(=16384bit) grosse Tabellen beschreiben, was in einen 27128 16kx8bit EPROM Speicherchip hinein passt. Wenn man die Funktionssammlung reduziert (man braucht zumeist nicht alle 32), kann man auch mit 1/2 (2764 8kx8bit) oder 1/4 (2732 4kx8) der Tabellengrösse auskommen.

Sequenzielle Logik

Bisher können wir rechnen, aber mit was rechnen wir? Irgendwo müssen unsere Zahlen herkommen. Schon eine Kettenrechnung (z.B. die obige Multiplikation in 32 Schritten) verlangt nach Zwischenergebnissen, die hinten abgenommen werden und vorne wieder später reingefüttert müssen. Also müssen wir Bits (zwischen)speichern können.

Sobald wir Bits speichern können, können wir unsere Logik über mehrere Schritte laufen lassen. Dabei entsteht eine Abfolge (= Sequenz) von Speicherzuständen. Daher nennt man dies sequenzielle Logik. Logik ohne Speicher nennt man kombinatorische Logik, weil dort nur eine Kombination der aktuellen Eingänge möglich ist.

Wie speichert man nun Bits? Der einfachste Fall von Sequenziell ist, dass ein NICHT sich selber ansteuert. Folge ist: 0->1, das 1 dann nach vorne, worauf dann 1->0 passiert, wieder nach vorne, 0->1 und 1->0 und so weiter, unendlich. Damit versucht das NICHT dauernd sich selber umzuschalten, und ist unstabil. Das nennt man einen Oszillator. Der schwingt sehr schnell, so schnell die Schaltung schalten kann. Für einen langsameren Oszillator, der der restlichen Schaltung Zeit lässt, bremst man den Oszillator aus, mit einem Quarz in der Rückleitung (das Signal legt dann eine Strecke mit Schallgeschwindigkeit im Festkörper statt Lichtgeschwindigkeit im Leiter zurück).

Ein Kreis von 2 NICHT hat genau die gegenteilige Wirkung: 0->1->0, bzw 1->0->1, und ist damit bockstabil, ausser er wird von aussen gestört und somit umgeschaltet. Das nennt man daher ein Flipflop (FF). Damit können wir einen beliebigen Zustand, 0 oder 1, anlegen und er wird festgehalten, bis wir ihn überschreiben.

Zum so ein FF umschalten, muss man ihm von aussen "stören", wozu man das Bit "verteilt" zuführen muss, je nach 0 oder 1 muss dafür das eine oder das andere NICHT "angeschubst" werden, daher wird das auch ein Reset/Set FF (RS-FF) genannt. Technisch muss man 2 UND+NICHT oder 2 ODER+NICHT nehmen, damit man mit den UND oder ODER ihren zweiten Eingängen 2 "Störleitungen" hat.

Ein RS-FF zu benutzen ist mühsam. Zum dies einfacher machen gibt es verschiedenste "Verteiler" Schaltungen, die ich hier zum Zeit sparen weglasse. Das Resultat davon ist jedenfalls das heute üblich benutzte Data FF (D-FF), das 2 Eingänge hat: D für Daten, und Clk (= Clock, = Takt) zum es "anschubsen", als "speicher jetzt das passende" Signal. Dieses Signal ist ähnlich wie das Auswahl Signal beim Multiplexer, kein Datensignal sondern Steuerung.

Wenn man nun eine Zahl, die ja aus mehreren Bits besteht, speichern will, dann braucht man pro Bit ein D-FF. So eine Sammlung von D-FFs mit einem gemeinsamen Clk Signal nennt man ein Register. In so ein Register passt eine einzelne Variable. Und damit haben wir ein Konzept das jeder Programmierer kennt. Rechnen tut man ja indem man varible3 = variable1 OP variable2; macht, oder Varianten davon (Konstanten sind wie Variablen, aber aus dem Program zu lesen, statt berechnet). Im Rechner ist das nun 2 Register (für variable1 und 2) auswählen und zur ALU schicken, ALU Funktion auswählen, und beim Ergebnis Register (für variable3) einen Clock Puls anlegen.

Speicher

Will man mehrere Variablen haben (das braucht ja jedes normale Program), so muss man eine ganze Sammlung Register haben, inklusive einem grossen n:1 Multiplexer zum aus ihnen auswählen, und einer Schaltung zum nur die Clk Leitungen des richtigen Registers zu Triggern (ein Demultiplexer). Das ganze ergibt dann zusammen einen Speicher. Neben dem, dass der Speicher Daten in Bitgruppen speichert, braucht er auch eine weitere Bitgruppe von n Bits um den Multiplexer und den Demultiplexer anzusteuern. Diese Bitgruppe nennt man auch hier die Speicheradresse.

Weil Speicher mit einem x:1 Multiplexer ausgewählt wird, der mit n Bits an Auswahl Signalen angesteuert wird (also ein (2^n):1 Multiplexer), haben Speicher auch immer ein 2^n Bits Grösse, und das ergibt die bekannte Grössenserie 2^10=1024=1k, 2^20=1048576=1M, 2^30=1073741824=1G. Aus diesem Grund werden Speichergrössen in 2^n Serien hergestellt, während alle anderen Grössen (Taktfrequenzen, Bitraten) in den üblichen 10^n Serien laufen. Daher weiss man auch immer genau ob eine "k" oder "M" Zahl mit 1000 oder 1024 verstanden werden muss, je nachdem ob es ein Takt oder eine Adresse ist.

Jede Variable entspricht einer Speicherstelle. Variablennamen sind nichts anders als symbolische Namen für die Adressnummer der Speicherstelle dieser Variable, also benamste Konstanten. Array Variablen sind eine Serie von Speicherstellen, deren Name die Adresse der ersten Speicherstelle (= Basisadresse) entspricht.

Da Adressen durch Bits representiert sind, kann man Adressen auch im Speicher als Daten abspeichern. Es ist dann möglich eine Adresse aus einer Variablen zu holen, in ein Register zu laden, und mit dessen Inhalt den Speicher erneut zu adressieren, und mit den Daten die dann ausgewählt sind zu arbeiten. Sowas nennt man indirekte Adressierung. Die dafür benutzten Variablen sind die allseits bekannten und oft nicht wirklich verstandenen Pointer.

Selbstverständlich kann man die Adresse bevor man sie benutzt auch rechnerisch umformen, z.B. für einen Arrayzugriff array[n] zur Basisadresse n dazu addieren. Das nennt man indizierte Adressierung. Ebenso kann man einen Pointer auf die Array Basisadresse setzen, und nach jedem Benutzen um 1 Element erhöhen, z.B. mit pointer++, das nennt man dass post-incrementierte Adressierung. Moderne Rechner machen diese Addition während sie auf Antwort vom Speicher warten, mit der ohnehin noch unbenutzten Addiereinheit.

Die Fähigkeit beliebige Speicherstellen in beliebiger Reihenfolge zu benutzen ergab auch den Namen Random Access Memory (RAM), im Gegensatz zu Disk basiertem Speicher.

Prozessor und Computer

Nun können wir rechnen, und Daten herumschieben und speichern. Konstanten können wir aus dem Program seinem Speicherplatz holen. Aber was ist den nun ein Program nun selber? Jeder weiss dass ein Program als binäres File auftritt, also aus Bits besteht, die in den Speicher geladen werden. Aber was sind das für Bits? Was bedeuten sie?

Wie wir schon wissen rechnet der Addierer oder die ALU als kombinatorische Logik dauernd mit, egal was vorne ansteht, egal ob das Ergebnis benutzt wird. Man muss nur das Clk Signal des richtigen wartenden Registers anstossen. Anderseits wissen wir dass man Addition/Subtraktion/etc muss auswählen können. Ebenso muss man x verschiedene Multiplexer steuern für Eingabedaten auswählen. Und schliesslich Register und Speicher mit den richtigen Demultiplexer und Clk Signalen versorgen.

Für jede einzelne Rechenoperation braucht es dazu eine Sequenz von solchen Steuersignalen. Verschiedene solcher Sequenzen sind in der Ablauflogik im Befehlsdecoder das Prozessors eingebaut. Ein Program solle nun dann eine Sequenz nach der anderen von diesen auswählen. Dazu besteht es aus einer Liste von Nummern, die je eine Sequenz auswählen, den Befehlen oder Instruktionen.

Ein Ausschnitt C Code als Beispiel:


      ...
      int b=10, c=5;           /* nur statische Variablen weil einfacher */
      int proc(int a) {        /* nur ein Parameter damit einfacher */
        b = a+b*c-30;
        return (b*4-1); }
      ...
      int d;
      ...
      d=proc(5);
      ...
    

Nun übersetzen (compilieren) wir das zu Assembler, in diesem Beispiel für einen Z80 Prozessor, das Resultat ist für jeden Prozessor anders. Man sieht dass es mehrere Befehle für einen Ausdruck braucht:


              ...
      b:      DW   10          ; 2 Bytes Platz reservieren, mit 10 füllen
      c:      DW   5           ; int wird hier 16bit definiert, Z80 üblich
      proc:   PUSH HL          ; Parameter a (in HL Register) wegspeichern
                               ;   für später
              LD   HL,(b)      ; Arbeits Register HL = b
              LD   DE,(c)      ; Hilfs Register DE = c
              CALL mult16      ; hoffentlich vorhandene 16bit*16bit Routine
              POP  DE          ; a wieder zurückholen, nach DE
              ADD  HL,DE       ; HL = a+b*c
              LD   DE,30       ; 30 holen für Subtraktion
              XOR  A           ; C Flag löschen damit SBC richtig geht
              SBC  HL,DE       ; HL = a+b*c-30
                               ; man könnte auch LD DE,-30 und ADD HL,DE
                               ;   nehmen aber ich wollte den Mangel an
                               ;   SUB HL,DE zeigen und den SBC Flick
                               ;   so etwas passiert oft in Assembler
              LD   (b),HL      ; Speicherplatz b = HL
              ADD  HL,HL       ; HL = HL*2
              ADD  HL,HL       ; HL = HL*2, ergibt *4
              DEC  HL          ; HL -= 1
              RET              ; HL enthält den Rückgabewert
                               ;   übliche Konvention dazu
              ...
      d:      DW               ; ohne Vorgabe wird mit 0 gefüllt
              ...
              LD   HL,5        ; erster Parameter in HL übergeben
                               ;   oft werden einfach alle Parameter per Stack
              CALL proc        ; proc aufrufen
              LD   (d),HL      ; und Ergebnis wegspeichern nach d
              ...
    

Übersetzen (Assemblieren) wir das weiter zu Binärcode (das was im binären Programfile drin ist). Man sieht das es teils mehrere Bytes (bei Z80 1..5 möglich) Code pro Befehl braucht:


      Adr   Bytes   (alles in Hexadezimal, 0 = binär 0000, F = 1111)
      ....              ;     ...
      1234: 0A 00       ; b   DW 10       ; 000A = 10, merken: b = 1234
      1236: 05 00       ; c   DW 5        ; 0005 = 5,  merken: c = 1236
      1238: E5          ; procPUSH HL     ;            merken: proc = 1238
      1239: 2A 34 12    ;     LD   HL,(b) ; 2A = LD HL,() 34 12 = 1234 von oben
      123C: ED 58 36 12 ;     LD   DE,(c) ; ED = erweiterter Befehl, ED Tabelle
                                          ;   solche Unregelmässigkeiten
                                          ;   sind typisch für
                                          ;   abwärtskompatible CPUs
      1240: CD xx xx    ;     CALL mult16 ; CD = call, xx xx = wo mult16 ist
      1243: D1          ;     POP  DE
      1244: 19          ;     ADD  HL,DE
      1245: 11 1E 00    ;     LD   DE,30  ; 001E = 30
      1248: AF          ;     XOR  A
      1249: ED 52       ;     SBC  HL,DE  ; wieder nur dank ED Tabelle
      124B: 22 34 12    ;     LD   (b),HL
      124E: 29          ;     ADD  HL,HL
      124F: 29          ;     ADD  HL,HL
      1251: 2B          ;     DEC  HL
      1252: C9          ;     RET
      ....              ;     ...
      2345: 00 00       ; d   DW           ; automatisch auf 0, merke d = 2345
      ....              ;     ...
      3456: 21 05 00    ;     LD   HL,5    ; *** Program startet hier ***
      3459: CD 38 12    ;     CALL proc    ; 1238 von oben, dem PUSH HL
      345C: 22 45 23    ;     LD   (d),HL
      ....              ;     ...
    

Um dies auszuführen wird nun so vorgegangen: Mit einem speziellen Adressregister namens Programzähler (PC), das momentan z.B. die Adresse 3456 drin hat, wird auf den Speicher zugegriffen. Es kommt der Inhalt 21 (der Befehl LD HL,Konstante) zurück. Dem PC wird 1 addiert, damit er für das nächste Byte vom Program bereit it, normales post-increment. Wobei anzumerken ist, dass post-increment eigentlich für dies hier erfunden wurde, und erst später für Arrays genutzt wurde.

Der Prozessor führt dann die für LD HL,Konstante einverdrahtete Sequenz aus, also mit dem PC nochmals 2 mal Zugriff (und danach jeweils PC + 1) und die Daten (05 00) in Register L und H (in der Reihenfolge) zu verfrachten. Dann ist der Befehl fertig.

Also nächsten Befehl holen (und PC + 1), was CD (CALL) beinhaltet. Das wird ausgeführt mit PC nochmals 2 mal Zugriff (und PC + 1), dann resultierenden PC (345C) wegspeichern damit der RET später funktioniert. Dann das geholte 1238 in den PC laden, der nächste Befehl ist das E5 (PUSH HL) in 1238. CALL macht also nichts anders als PUSH PC und LD PC,Konstante, und RET ist dann nichts anderes als POP PC.

So geht das weiter, Befehl für Befehl, solange Strom da ist, anfangend nach dem Einschalten oder Reset (welche beide PC=0000 machen). Immer Befehl holen und auswerten und ausführen, ohne Unterbruch. Daher braucht man auch ein ROM/PROM/EPROM/Flash Speicher mit einem Bootloader oder BIOS drin, damit der Prozessor sofort etwas zum machen vorgesetzt bekommt, sonst ist sofortiger Absturz vorprogrammiert (bzw vorverdrahtet).

Wer mal ein beliebiges C Program in den Assembler seines PCs (vermutlich ein 80386 Nachfolger) sehen will, sollte die -S Option vom gcc anschauen. Das artet sehr schnell in sehr viele Befehle aus, und daher auch so grosse Programfiles.

Mechanische Digitallogik

Nun wissen wir dass man mit Bits rechnen kann. Jetzt brauchen wir aber auch einen realen Rechner, der das für uns macht, sonst war alles nur schöne Theorie. Natürlich gibt es verschiedene Methoden dies nun umzusetzen. Bits sind eben keine Elektronik sondern Mathematik, die beliebig berechnet werden kann, sogar als Übung von Hand mit Stift auf Papier.

Bits kann man nicht nur wie heute üblich mit Elektronik machen, auch mit Relais (Elektromechanik) oder gar purer Mechanik oder Hydraulik oder gar Seilzügen geht dies. Man braucht bloss etwas womit man eine grosse Anzahl Elemente herstellen kann, die genug klein, billig und zuverlässig sein müssen. Die einfachste und sichtbarste bekannte Technologie, und damit auch primitivste mögliche Implementation ist in Mechanik. Diese folgt daher hier als erstes.

Für jede Implementation müssen wir als erstes eine Methode festlegen, wie wir die beiden Bit Zustände 0 und 1 representieren wollen. Erst danach können wir daran gehen, Strukturen zu entwerfen, die gesteuert durch Eingangsbits neue Ausgangsbits erzeugen. Als mechanisches Besipiel werd ich hier Schieber nehmen, also längliche steife Teile die ihrer Läengsache entlang verscheibbar sind. Dabei soll eine Endposition 0 und die andere 1 markieren.

Die Implementation von ODER erweist sich als trivial. Man braucht nur einen Ausgangsschieber, mit einer Stossfläche, die von den Stossflächen alles Eingangsschieber gestossen werden kann. Im gewissermassen ist der Ausgangsschieber ein "Sammler" der Eingagsschieber, und ODER ist ja die "sammeln" operation. Solange nur ein Schieber e0..e2 nach rechts (=1) geschoben ist, ist auch aus nach rechts verschoben:

          0<->1  User stösst e0 Schieber nach rechts
              ..
       e0     |`--------------..--.  aus  Schieber von e0 ODER e1 ODER e2
              |.--------------'|  |            .---------.
          ..  `'               |  |  0<->1    .| .-----. |---.
       e1 |`--------------.    |  `----------' | | /|  | |/\ | Anzeigefenster
          |.--------------'    |  .----------. | |  |  | |\/ | zeigt  1  an
          ::                   |  ||\\\\\\|   `| `-----' |---'
       e2 |`--------------.    |  |  Feder     `---------'
          |.--------------'    `--'  die  aus  Schieber nach links drückt
          `'
           ^ Druckknöpfe für User Eingabe
    

Dagegen ist die Implementation von UND einiges komplizierter. Schliesslich soll hier der aus Schieber nur dann bewegt werden wenn alle e* Schieber gedrückt werden. Dies kann man bewerkstelligen, indem man einen der e* Scheiber nimmt, und ihm am Ende einen Winkelhebel anbaut, der von einem anderen e* in/aus dem Weg geschoben werden kann. Nur wenn er in dem Weg ist, und dann noch der Schieber geschoben wird, hat das Auswirkung:

                       ===== e0  0 A    Hier auf/ab gezeichnet, kann aber
          Winkelhebel   | |        |    auch seitliche Ausweichung sein
                    \   | |_.-.  1 V
          ..         \ _| |_.-'         aus  wenn e0 unten/1 UND e1 rechts/1
       e1 |`--------.-'_:-'    .-------------
          |.--------`-'        `------------- -> Anzeige
          `'
    

Man sieht hier beim UND bereits eine ganze Menge Probleme die für mechanische Logik typisch sind:

Ein EXODER oder ein NICHT bringen noch grössere Probleme mit sich, sind aber machbar.

Wie man sieht ist Mechanik zwar möglich und recht simpel, aber weder schnell noch günstig. Nicht überraschend wurde auch nur ein ernsthafter Computer in Mechanik implementiert, der Z1 von Karl Zuse (ca 1935), aus Blech und Stangen mit Laubsäge in der elterlichen Wohnung gebaut. Von dem steht übrigens ein Nachbau (von ihm selbst gemacht) im Deutschen Museum in München.

Elektromechanische Digitallogik

Die Elekromechanik entstammt grossteils aus der Welt der Telekommunikation, genauer der Morse Telegraphie. Zentral sind hier 2 Geräte: Der Sender, bestehend aus einer Batterie und einem Taster (Schalter der nur geschlossen ist, wie Druck drauf ist, wegen Feder die ihn aufdrückt), sowie der Empfänger bestehend aus einem Elektromagneten und einem Schreiber welcher von Magneten gegen einen bewegten Papierstreifen gedrückt wird (auch mit Feder zum wegdrücken), und so das bekannte Punkt-/Linienmuster erzeugt (Zeitfolge in Raumfolge umwandelt).

               Taste drücken |                      Hebel rotiert _.-'
        .-------------.      V        |                       _.-'  .-. S
        |             |      _.-o     |     Strom           o'      | | c
        |      .----. `----o'    o-- - - ----->--------.     \      | | h
      .----------.  |                 |            .---|----  \     | | r |
      | +      - |  |              Distanz  Elektro`[/////]-   \    | | e V
      |          |  |                 |      magnet    |            | | i
      | Batterie |  `--------------- - - --------------'            \ / b
      `----------'                    |                              V  e
                  Sender              |            Empfänger            r
    

Schnell wurde dabei gemerkt, dass der vom Sender angeschickte Strom mit der distanz immer schwächer wurde. Die Lösung bestand dann darin, bevor es zu schwach wurde, einen Elektromagneten hinzustellen, der einen weiteren Schalter (mit eigener Batterie) betätigte, und somit ein wieder starkes Signal weiter schicken konnte. Der erste (nur digital nutzbare) Vertärker war erfunden. In Anlehnung an die Relaisstationen der Pferdepost (Reiter wechseln alle paar Stunden an einem solchen das Pferd gegen ein frisches) wurden diese elektromagnetisch betätigten Schalter ebenfalls Relais genannt (das Signal wechselt Stromkreis zu einem frischen).

Da Relais erlauben somit, mit Strom weiteren Strom zu schalten, also kann man mit ihnen ebenfalls digitale Logik implementieren. Dabei definiert man die Bits als kein Strom = 0 und Strom = 1. Der Magnet geht wie oben von Signal zu Minus ist also bei 1 Aktiv, und drückt damit den Schalter, der von Plus zu Signal verdrahtet ist.

Die ODER Funktion lässt sich nun sehr einfach implementieren: Man verdrahtet einfach die Schalter zweiter Relais neben einander. Egal ob der eine ODER der andere geschaltet wird, wird Strom abgegeben. Im Gegensatz zu der Mechanik oben ist nun aber auch ein UND simpel: Man verdrahtet einfach so, dass die Leitung von Plus durch den ersten UND dann durch den zweiten Schalter geht. Auch ein EXODER ist einfach: 2 Schalter in beiden Positionen Anschlüsse haben und über Kreuz zusammengehängt sind. Auch ein NICHT ist nur ein Schalter der nur ungedrückt zu ist:

             |                 |             |
             V                 V             V                 A
            _.-o ODER          _.-o         _.-o UND           |    NICHT
      ----o'    o----    ----o'    o------o'    o----    ----o----oo----
       |     |     |            |
       |     V     |            V            A
       |    _.-o   |           _.-oo------o  |   EXODER  
       `--o'    o--'     ----o'    o------oo----o----
    

Richtungen spielen auch keine Rolle, da Strom einfach den Drähten folgt. Energie liefern die Batterien, und nicht die Signale. Und es ist schneller weil die Schalter die einzigen bewegten Teile sind, nicht ganze Schieber. Und bei Reed Relais sind die bewegten Schalterteile auf eine unter 1 Gramm schwere federnde Metallzunge reduziert, was Frequenzen bis etwa 1kHz zulässt.

Elektronische Digitallogik

Mechanik und Elektromechanik mögen zwar simpel und einfach zu verstehen sein. Aber sie sind langsam und brauchen viel Platz, teuer und nutzen sich ab. Elektronik ist die mit Abstand schnellste und kompakteste Form die wir heute haben (Photonik ist noch völlig unausgereift), aber auch die Form mit der undurchsichtigsten Physik (Elektrizität) dahinter (wieder von Photonen abgesehen). Grund für deren Geschwindigkeit ist, dass dabei nur Elektronen bewegt werden, und nicht ganze Atome. Der Gewichts- und Grössenunterschied macht alles aus.

Spannung

Elektrische Spannung entsteht entweder Reibungshitze (Blitz), oder durch chemische Reaktionen (Batterie/Akku) oder durch an Drähten vorbei bewegten Magneten (Dynamo/Generator).

Diese Vorgänge sind zwar interessant, aber kompliziert und interessieren uns für die Verständnis von Computer Hardware nichts! Wir nehmen hier daher einfach an, das wir ein Netzteil haben, das Spannung liefert, solange wir ihm nicht zuviel Strom abverlangen (dann tut ein besseres Netzteil abschalten und ein schlechteres geht in Rauch auf).

Strom durch Widerstände

Die Basis aller Elektronik ist der gesteuerte Umgang mit elektrischem Strom. Strom kann nur fliessen, wenn elektrische Spannung ihn antreibt und ein elektrischer Leiter vorhanden ist, durch den er getrieben werden kann. Wieviel Strom dabei entsteht hängt ab von der Spannung die antreibt sowie dem Widerstand den der Leiter ihm entgegenstellt:

Strom = Spannung / Widerstand (Ohmsches Gesetz)

Aus dem Grund, und weil Leistung (bzw Hitzeentwicklung) = Spannung * Strom ist, nimmt man heute die immer kleineren Spannungen, 5V, 3.3V, 2.8V, 2.5V, 1.8V, ... . Je weniger Spannung und Strom umso kühler, aber zuwenig und der Rechner wird unzuverlässig (leicht störbar). Also geht es in immer kleineren Schritten, und man ärgert sich über Spannungsregler einstellen auf den Motherboards.

Strom durch Spannungsteiler

Wenn wir unsere Spannung an eine Reihe/Serie von Leitern anhängen dann ergibt sich ein Strom, der der Summe der Widerstände der Leiter entspricht.

Strom = Spannung / (Widerstand W1 + Widerstand W2 + Widerstand W3)

        Strom            Spannung
      ---->-----.
                |        voll    100%
               .-.
               | | W1
               | | 50%
               `-'
                |        wenig    50% (100%-50%)
               .-.
               | | W2
               | | 25%
               `-'
                |        weniger  25% (50%-25%)
               .-.
               | | W3
               | | 25%
               `-'
                |        nichts   0% (25%-25%)
      ----------'
    

Dabei stellt man dann fest, dass an jedem Widerstand genau ein Teil der Gesammtspannung "anfällt" der seinem Anteil am Gesammtwiderstand entspricht.

Transistoren in Spannungsteiler

Wenn wir nun einen Transistor anschauen, so haben wir einen elektrisch steuerbaren variablen Widerstand. Wenn man einen Transistor in einen Spannungsteiler einbaut, und seinen Widerstand auf gross schaltet, bekommt er den grossen Teil der Spannung ab, wenn man ihn auf wenig schaltet, bekommt er wenig ab. Damit hat man eine variable Spannung am Knotenpunkt W1 und T1, und somit an einer Ausgangsleitung, mit der man wiederum andere Transistoren ansteuern kann.

Da Transistoren bei kleiner Spannung am Eingang "zu" sind, also grosser Widerstand, und grosse Spannung am Ausgang, und umgekehrt grosse Spannung am Eingang kleinen Widerstand macht, und somit der Spannungsteiler kleine Spannung abgibt, haben Transistor-Spannungsteiler automatisch eine Signal unkehrende Funktion, sind also automatisch eine NICHT Funktion.

Uns interessieren hier nur 2 Transistor Sorten: Die spannungsgesteuerten MOSFET (Metall Oxid Silizium Feld Effekt Transistor) Transistoren (die sind in den meisten modernen hochintegrierten Chips drin) ergeben dann NMOS Logik:

                Strom                 Spannung
      ------------>----.
                       |        voll            voll
                      .-.
                      | | W1
                      | |
                      `-'       T2 gross        T2 klein
      ----------.      |------- fast voll       fast nichts
                |   |--'
                `--||      T2 (n-MOSFET, ergibt NMOS Logik)
                    |->.
                       |
                       |        nichts          nichts
      -----------------'
    

Und die stromgesteuerten Bipolar Transistoren (die sind in den frühen niedrig integrierten Chips drin und noch früher in Einzeltransistor Logikmodulen verwendet), ergeben dann RTL Logik:

                Strom                 Spannung
      ------------>----.
                       |        voll            voll
                      .-.
                      | | W1
                      | |
         ____         `-'       T2 gross        T2 klein
      --|____|--.      |------- fast voll       fast nichts
          W3    |   |.-'
        Strom   `---|      T2 (npn-Bipolar, ergibt mit W3 die RTL Logik)
      Begrenzer     |`>.
                       |
                       |        nichts          nichts
      -----------------'
    

Komplementäre Transistoren in Spannungsteiler

Besser (weil schneller) aber teurer (weil teuerere oder mehr Bauteile) wird es, wenn man anstelle vom Widerstand W1 und Transistor T2 dann 2 Transistoren T1 und T2 nimmt, von denen jeweils einer gross und der andere klein wird. Vor allem ist dann die Stromstärke wenn unten=klein erheblich kleiner, und somit ist der Chip kühler. Und der obere Transistor kann schneller Strom liefern als ein Widerstand, und somit ist der Chip schneller. Sowas nennt man Komplementär-Betrieb.

Mit 2 MOSFET Transistoren (einem p-MOSFET oben und einem n-MOSFET unten) nennt man dies CMOS Technik (C wie complementary) und das ist seit 1985 in fast allen modernen Chips drin:

                Strom                 Spannung
      ------------>----.
                       |        voll            voll
                    |-<'
                .--||     T1 (p-MOSFET)
                |   |--.        T1 kl /T2 gr    T1 gr/T2 kl
      ----------|      |------- fast voll       fast nichts
                |   |--'
                `--||     T2 (n-MOSFET, ergeben zusammen CMOS)
                    |->.
                       |
                       |        nichts          nichts
      -----------------'
    

Mit 2 npn-Bipolar Transistoren (kein pnp-Bipolar oben weil zu teuer zum integrieren, daher ist auch ein 3. Ansteuertransistor nötig) und ergibt sich die TTL Technik die von 1970 bis 1985 dominierte.

                Strom                 Spannung
      ------------>----.
                       |        voll            voll
                    |.-'
                .---|     T1 (npn-Bipolar, kein pnp, braucht daher T3!)
            |.-'    |`>.       T1 kl /T2 gr    T1 gr/T2 kl
      ------|  T3      |------- fast voll       fast nichts
            |`>.    |.-'
                `---|     T2 (npn-Bipolar, ergibt mit oben TTL)
                    |`>.
                       |
                       |        nichts          nichts
      -----------------'  (vereinfacht gezeichnet, Widerstände fehlen)
    

Transitoren als Logik

Nun haben wir einerseits Boole kennengelernt, anderseits Elektronik. Nun vermischen wir die beiden. Das macht man, indem man Bits als Spannungen representiert. Da Transitoren unter 0.5V sicher zu sind, und anderseits offen auf sicher 3.5V hinauf kommen (bei 5V Versorgung) hat man bei standard TTL Logik definiert, das 0..0.8V=0, 2.4..5V=1, bei LVTTL (das was man heute zwischen Chips oft hat) sind die 5V auf 3.3V reduziert, Rest bleibt. Man merke, dass 2/3 Spannung -> 2/3 Strom -> 4/9 Leistung und Wärmeentwicklung ist. In den Chips drinnen geht man heute bis auf 1V Versorgung hinunter.

Nimmt man nun bei MOSFETs und NMOS mehrere untere Transistoren so müssen alle mit 2.4..5V gefüttert werden, damit sie alle klein werden und der Ausgang auf 0..0.5V geht. Damit ergibt sich automatisch ein UND gefolgt von NICHT, also ein NICHT-UND, (englisch NOT-AND, kurz NAND):

                Strom                 Spannung
      ------------>-----.         voll=5V             voll=5V
                        |
                       .-.
                       | | W1
                       | |
                       `-'        T2a UND T2b kl      T2a ODER T2b gr
                        |-------  fast nichts=0.5V    fast voll=3.5V
                    |---'
      -------------||      T2a (n-MOSFET, ergibt NMOS Logik)
                    |->-.
                        |
                    |---'
      -------------||      T2b (n-MOSFET, ergibt NMOS Logik)
                    |->-.
                        |
                        |
      ------------------'         nichts=0V           nichts=0V
    

Bei Bipolar Transistoren geht diese "Staplerei" nicht, weil der Strom durch T2a durch T2b gestört werden würde. Da muss man mehrere untere Transistoren neben einander stellen. Was dann dazu führt dass T2a ODER T3b klein ein 0 produziert, man bekommt ein NICHT-ODER (englisch NOT-OR, kurz NOR), das für RTL Logik typisch ist (und nervend, siehe später). Man kann RTL Logik retten indem man 0=2.4..5V und 1=0..0.8V umdefinitert, das nennt man inverse Logik, aber es ist verwirrend damit zu arbeiten. Daher ist RTL Logik schon früh selten geworden.

Alternativ kann man aber mit bipolar Transistoren auch immer nur einen T2 nehmen, aber dann in der Eingangsleitung mehrere Dioden Da und Db setzen (D3 ist nur ein weiteres Hilfsbauteil wie W3). Damit wird der Transistor gross bleiben, solange irgendein Eingang wenig liefert. Dieser Trick ist uralt, und wurde bereits in Röhrenrechnern benutzt (Dioden kosten etwa 1% von einer Röhre und ihrer Helferteile). Dies liefert auch das erwünschenswertere NAND Verhalten, und verdrängte RTL Logik. Man nennt sowas dann DTL Logik:

                Strom                 Spannung
      ------------>-------.
            |             |       voll=5V             voll=5V
           .-.           .-.
           | | W3        | | W1
           | |           | |
        Da `-'    D3     `-'      T2 gross        T2 klein
      --|<--+-->|--.      |------ fast voll       fast nichts
            |      |   |.-'
      --|<--'      `---|      T2 (npn-Bipolar, ergibt mit Da u Db DTL Logik)
        Db             |`>.
                          |
                          |       nichts=0V           nichts=0V
      --------------------'
    

Diese Logiken kann man auch alle komplementär machen. Bei CMOS mit je einem Transistorpaar pro Eingang. Dabei werden die unteren "gefaltetet" hintereinander verdrahtet (von T2a zu T2b), und die oberen T1a und T1b "neben einender" verdrahtet. Jeder Eingang a und b steuert sein T1 und T2 Paar an.

Bei TTL wird das normale TTL T1 und T2 Paar (mit T3 Helfer) genommen und mit DTL-artigen Dioden und W3 versehen. RTL kann man nicht komplementär machen, und das ist neben der NOR Logik ein weiterer Grund für dessen aussterben.

Man kann dies auch bei beiden für beliebige Eingangszahlen "verbreitern", bei CMOS einfach mehr Paare, bei TTL einfach mehr linke Dioden.

Bisher können wir nur NICHT-UND (oder NICHT-ODER) herstellen. Wie steht es mit dem Resten der logischen Funktionen?

Integrierte Schaltungen

In der ersten Hälfte 1960ern wurden Computer noch so gebaut, aus einzelnen Transistoren. Man nahm die für ein oder 2 Gatter und setzte sie auf eine kleine Platine (Modul), hier PDP-6 und setzte viele Module zu einem Rechner zusammen, hier ein PDP-8/i. Das Resultat war gross (1/2 19" Gestell für einen 12bit Rechner mit 6kByte Speicher, 3 19" Gestelle für einen 36bit Rechner ohne seine max 1MByte Speicher), langsam (so 1-3MHz und erst noch mehrere Takte pro Befehl) und teuer (so $30'000 (12bit, minimale Ausstattung) bis $3'000'000 (36bit, gut ausgebaut)).

Die Rechner wurden kleiner, schneller und billiger (und stromsparender und zuverlässiger), dadurch das man in der zweiten Hälfte der 1960er die integrierte Schaltungen einführte, mehrere Transistoren auf einen Stück Silizium produziert und verschaltet. Zuerst mit SSI (small scale intergration, bis 30 Transistoren) nur ein grosses Modul pro kleinen Chip (z.B. ein TTL 7400 ist 4 Stück 2-Eingang NICHT-UND). Dann mit MSI (medium scale integration, bis 300 Transistoren) immer mehr (z.B. ein 7474 ist 2 volle D-FF, 7483 ist ein 4Bit Voll-Addierer), und dann mit LSI (large scale integration, bis 3000 Transistoren) noch mehr (z.B. ein 74181 ist eine ganze 4bit ALU, 74374 ist ein 8bit Register) und schliesslich ganz viel (z.B: ein 74481 ist ein 4bit Ausschnitt eines ganzen Rechner Datenpfades).

Solche 74(x)xx Chips sind universell verwendbar, und wurden daher bis ca 1990 auch in professionellen Schaltungen verbaut. Heute sind sie wegen langsam und viel Platzverbrauch und teuer (die Kosten vieler kleiner Chips addieren sich) fast nur noch in Hobby und unkritischen professionellen Schaltungen drin.

Je grösser man Chips macht, umso mehr trift nun aber das Problem auf, dass sie zu spezialisiert werden, nur für eine Funktion brauchbar, also nicht für jedermann nutzbar. Kombiniert man die daraus resultierenden geringeren Stückzahlen mit den immer grösseren Entwicklungskosten grosser Chips und sie werden zu teuer. Das ist so ab 1000-10'000 Transistoren der Fall.

Grosse Firmen wie IBM oder Telephonzentralen Hersteller können sich die Kosten erlauben, einen eigenen 1000-1'000'000 Transitor Chip nur für sich entwickeln zu lassen und die Kosten über wenige 1000 Kopien bezahlen zu lassen (daher sind Grossrechner auch heute noch so teuer). Sowas nennt man ein Application Specific Integrated Circuit (ASIC). Der Rest der Welt hat keine Chance da mitzumachen. Das war der Stand um 1970.

Eine Spezialform von ASIC, die Gate Arrays (GAs) erschien um 1980. Sie nutzen die CMOS Chip Struktur aus und baut die ganzen Transistor Zeilen und Stromversorgung nur einmal, um dann nur die Verdrahtung schaltungsspezifisch zu machen. Das verringert die Entwicklungskosten um den Faktor 10, aber unterhalb von 10'000 Stück wird es auch so zu teuer. Ein einzelnes solches GA im ZX81 ersetzte die gleichwertigen 18 74(x)xx Chips des ZX80. Ebenso haben ein paar ASICs ("Chipset") in meinem 386er die fast gleichwertigen vielen (etwa 100) 74(x)xx Chips in meinem 286er ersetzt.

Mikroprozessoren

Der Einsatz eines Computers anstelle von Logik half in der zweiten Hälfte der 1960er dieses Problem zu limitieren, aber nur für Projekte die gross genug waren, die Kosten eines Computers zu finanzieren. Zumeist industrielle Prozesssteuerungen.

Der Mikroprozessor um 1970 löste dann dieses Mengenproblem auch für viele kleine Aufgaben. Dies ist nichts anders als ein kleiner kompletter Prozessor auf einem Chip. Alle Daten Ein- und Ausgaben sind/waren normale 74(x)xx Chips, oder ebenfalls universelle Prozessor IO Erweiterungschips (PIO, UART, FDC, ...). Die ganze anwendungsspezifische Logik steckt im Program des Mikroprozessors, gespeichert in einem ebenfalls universellen Speicherchip. Alle Chips sind daher in grossen Stückzahlen herstellbar und somit billig trotz komplex.

Der Speicher kann, da universell und für jedes Program geeignet, beliebig gross hergestellt werden. Und somit sind komplexe Programme in einem einzelnen Speicherchip möglich, und sind so kleiner und billiger als viele einzelne Chips. Wie wir heute wissen, entstanden aus Mikroprozessoren nach einer Weile (etwa 5 Jahre) die PCs, vollständige richtige Computer.

Der erste Mikroprozessor, der Intel 4004 ( eingescanntes Datenblatt DPF) (daraus extrahierte Daten) bestand aus 2300 Transistoren, rechnete mit 4 Bit, hatte 17 4bit (1 Haupt und 16 Hilfs) Register, und konnte 4096*8bit ROM (kein PROM oder EPROM!) Program und 256*4bit separates SRAM (Daten) adressieren. Er war für einen Tischrechner (Vorläufer eines Taschenrechners) gedacht, und wurde ab Nov 1971 allgemein verkauft.

Der 1 Jahr später erschienene 8008 ( eingescanntes Datenblatt DPF) (daraus extrahierte Daten) mit 3500 Transistoren rechnete bereits mit 8 Bit, hatte nur 1+6 Register, konnte aber schon 16k*8bit gemeinsamen Program+Daten Speicher adressieren, und war für die Interna eines Terminals gedacht. Aus diesem wurden die ersten primitiven PCs (Scelbi 8-H, Titus Mark 8, nochmals Titus Mark 8) gebaut. Sein inkompatibel erweiterter Nachfolger 8080 (1974) war dann die Basis der ersten Phase der PC Revolution (die ganzen S100 und CP/M Rechner). Der oben benutzte Z80 ist wiederum ein kompatibel erweiterter 8080 Clone.

Als logische Konsequenz der Mikroprozessoren gab es mit grössern Chips danach die Mikrocontroller, anfangend mit dem 8048 (neu gezeichnetes Datenblatt PDF) (daraus extrahierte Daten). Diese haben auf einem einzelnen Chip einen Prozessor (im 8048 in etwa ein auf 8bit Daten verbreiteter 4004) und etwas Speicher (etwas ROM/PROM/EPROM (im 8048 1-4kBytes) und wenig SRAM (im 8048 64-256Bytes) und etwas IO (im 8048 3 8bit Ports), also ein ganz simpler kompletter Computer. Man muss nur Stromversorgung und etwas analog Krempel anhängen, und ein Program dafür schreiben.

Diese Microcontroller sind was man üblicherweise heute in Geräten findet, z.B. in Audio oder Video Geräten (und in deren Fernbedienungen), oder Telephonen (und in deren SIM Karten), oder in Haushaltsgeräten (Küche, Waschmaschine), oder in Heizungen, oder in Maschinen oder Autos oder Liften, oder in Ampelsteuerungen, oder in Verkaufsautomaten, aber auch in den meisten PC Peripheriegeräten (Tastatur, Maus, HD, Drucker, Scanner, Modem, WLAN Accesspoint), dort z.T. grössere Versionen mit separatem Speicher. Hierbei spricht man dann von Embedded Systemen. Der heutige Normalbürger besitzt und benutzt, ohne es zu wissen, etwa 30 solcher Computerchen.

Kernspeicher

Neben einem Prozessor brauchen wir ja noch einen Speicher. Man könnte diesen ganz aus Flipflops herstellen. Aber wenn man Tausende oder gar Millionen Speicherworte, zu je einige Bits will, laufen einem die Kosten, der Platz und der Stromverbrauch samt Wärmeabgabe davon. Also musste ein Weg gefunden werden, Speicher per Bit billig und klein zu machen. Zum Glück dürfen Speicher langsamer arbeiten als prozessorinterne Register, was dies einfacher gestaltet. Trotzdem waren Speicher lange Zeit (bis gegen Ende 1950er) das Problemkind schlechthin der Computerei.

Die erste Form die sich gross durchsetze und Jahrzehnte lang den Standard blieb waren die Kernspeicher. Hier wird pro Bit genau ein kleines billiges Bauteil, ein ringförmiges Stück Ferrit (Eisenoxid), benutzt. Da dieses gar keine elektrischen Anschlüsse hat werden die Bits als Magnetfeld eingespeichert statt als elektrische Spannung. Dazu wird ein Gitter horizontaler und vertikaler dünner Drähte benutzt, bei denen an jedem Kreuzungspunkt ein Ferritkern sitzt.

Zum die Drähte möglichst nahe zu packen, und somit den Speicher klein zu machen, werden sie abwechslungsweise von ihren beiden Enden mit Strom versorgt. Dies führt dazu, dass der Strom abwechslungsweise in beiden Richtungen läft, wesshalb die Ringe abwechslungsweise umgekehrt angeordnet werden:

               | |            | | Draht vertikal
               | | .-.    .-. | |
               | .' .'    `. `. |    Draht horizontal
       --------.' .'--------`. `.--------
       ------.' .'------------`. `.------ -->   Stromrichtung
           .' .' |            | `. `.
           `-' | |            | | `-'Kern
               | |            | |               4 Kernspeicher Bits
           .-. | |            | | .-.
           `. `. |            | .' .'           in beiden Richtungen
       ------`. `.------------.' .'------       horizontal und vertikal
       --------`. `.--------.' .'-------- <--   viele Male wiederholt
               | `. `.    .' .' |    
               | | `-'    `-' | |               z.B. 4kBit = 64x64 Kerne
               | |            | |                    4kByte = Stapel 8* 64x64 

                |              A
                V              |
    

Zum ein Bit einschreiben werden beide durch dessen Kern laufende Drähte mit Strom versorgt, alle anderen Kerne die nur auf einem Draht sind bekommen zu wenig Magnetfeld ab und bleiben unbeeinflusst.

Zum ein Bit auslesen wird einfach das Bit gelöscht. Ein dritter Draht der 45 grad quer durch alle Bits geht registriert ob das Bit dabei gekehrt wurde (der Kern gibt beim "Wenden" einen kurzen Magnetpuls ab, und erzeugt im dritten Draht so einen Stromimpuls), und folgich vorher gesetzt war. Hinter dem Speicher muss ein Detektor (ein Satz reiner RS-FFs) stehen, dessen Bits vorgängig alle gelöscht wurden, und nun dort wo ein Stromimpuls kommt gesetzt werden. Dann wird der Detektor wie ein normales Register ausgelesen.

Da das Bit beim Lesen gelöscht wird, muss es nach dem Lesen erneut geschrieben werden, bevor der Detektor für das nächste Lesen gelöscht wird, sonst geht das Bit verloren. Da man oft eine Variable ausliest, verändert und wieder speichert (z.B. einen Zähler inkrementieren) konnten manche alte Prozessoren das erste Rückschreiben in solchen Fällen auslassen (read-modify-write Zyklus).

Speicherchips

Kernspeicher haben 2 Probleme: Erstens sind sie immer noch recht teuer, weil all die Kerne auffädeln kostet, selbst mit Fädelmaschine. Zweitens sind sie immer noch recht gross, weil Drahtdurchmesser und damit Kern Innendurchmesser nicht beliebig kein gemacht werden können. Diese beiden Probleme wurden mit Speicherchips in den Griff bekommen, sobald es gelang ein paar Tausende Transistoren auf ein Chip herzustellen, und damit 1kBit/Chip zu erreichen, was um 1970 herum gelang.

Heute gibt es 2 Sorten Speicher Chips:

ROM Speicher

Prozessoren erwarten ein Program sofort nach dem Einschalten, sonst stürzen sie ab. SRAM und DRAM vergessen ihren Inhalt wenn sie keinen Strom haben (DRAM sogar wenn nur das Auffrischen fehlt). Kernspeicher hatten das Problem noch nicht. Eine Chip Sorte, die im Zusammenhang mit Mikroprozessoren aufkam (aber vorher schon existierte) waren ROM Chips. ROM (Read Only Memory) ist eine Form von Speicher, dessen Inhalt nicht veränderbar (= nicht schreibbar, also nur lesbar) in die physikalische Struktur eingebaut/eingeformt is. Diese sind also nur für Programcode und Konstanten brauchbar, aber dafür bleibt der Inhalt auch ohne Strom erhalten, kann also ohne Bootvorgang von Band/Disk auskommen (und Code zum booten drin haben (beim PCs), oder den ganzen Code (bei Embedded Rechnern)).

Auch diese Chips wurden bereits ab einem Tausend Transistoren (also vor den SRAMs die mehrere Tausend brauchen) interessant, zumals sogar bloss eine Diode (halber Bipolar Transitor) pro Bit benötigt wird. Nicht nur für Mikroprozessoren, sondern auch für "normale" Computer. Also wurden auch hier beliebige Grössen verkaufbar. Man muss nur das Program in den Speicher einbauen (ROMs), einbrennen (PROMs), oder nur draufschreiben (EPROMs und Flash).

Ein Problem der ersten ROMs war, dass ihr Inhalt bei der Herstellung eingebaut werden muss, bevor das Gehäuse hergestellt wird. Die Struktur ist ähnlich wie bei DRAM, aber mit einem Metalltröpfchen (oder eben nicht) statt dem Kondensator. Also musste man sein Program dem ROM Hersteller schicken, damit er die Tröpfchen richtig setzt/weglässt. Das war zeitaufwendig (monatelang warten), vor allem bei Bugfixes. Und teuer war der Prozess auch, wenn auch deutlich weniger langwierig und teuer als ein Gate Array oder gar ein ASIC fertigen lassen. Das kann man heute mit dem Pressen von CDs vergleichen. Das war eines der Hauptprobleme des nur-ROM Program des 4004 Prozessors, das der RAM-Program taugliche 8008 nicht mehr hatte.

Dieses Problem wurde gelöst durch die Erfindung von PROM Chips (P = programmierbar), die erst beim Chip-Käufer programmiert werden. Die haben statt Metalltröpfchen kleine Nickelbückchen, welches an unerwünschten Orten vom Benutzer "weggebrannt" werden können (einfach Adresse und Daten anlegen und einen Puls hohe Spannung 12.5..25V geben). Die PROMs stehen zu ROMs im selbigen Verhältnis, wie CD-Rs zu gepressten CDs.

Später entstanden auch noch die mit UV-Licht löschbaren EPROMs (E = erasable) was übrigens eines der ersten Chips der Firma Intel waren. Die heutigen ohne UV elektrisch löschbaren Flash Chips sind deren Nachfolger, mit Intel als grösster Hersteller, quasi die CD-RWs der Chips.

SRAM und DRAM ist im Vergleich zu all denen die HDs der Chips, beliebig oft jederzeit schnell beschreibbar, die allerdings ohne Strom alles vergessend, dafür aber ohne Beschränkung auf ganze Blöcke lesen/schreiben, eben RAM.

Natürlich sind auch ROMs keine Erfindung des Chip Zeitalters, es gab sie schon früher. Dabei wurden mehrere Techniken verwendet. Für kleine ROMs (maximal ein paar 100 Worte) nahm man grosse Felder von Dioden, pro Bit eine (und damit einiges Platz verbrauchend). Eigentlich die selbige Schaltung wie bei Chip ROMs, nur dass man statt fehlender Metalltröpfchen einfach die Diode wegliess (und so Kosten etwa halbierte, bei 0.25$/Diode sehr relevant). Auch das in Logiktabellen und Arithmetisch-Logische Einheit erwähnte Whirlwind 40x120bit Befehlsdekodierer ROM war so ein Dioden ROM. Diese kleinen ROMs waren fast nur für Bootloader nutzbar (z.B. PDP-8/E Autostart Option mit 32*12bit ROM), zum beim Rechnerstart etwas Program von der Disk zu holen, das dann das restliche System nachlädt. Bereits ein BIOS war zu viel.

Wollte man mehr Platz, kam eine ROM Variante des Kernspeichers zum Einsatz. Bei diesem wurde eines der beiden Drähte durch oder neben den Kern geführt, je nachdem ob das Bit 0 oder 1 sein soll. Da der Kern nur noch dazu dient einen Impuls zu erzeugen oder nicht, und nicht als eigentlichen Speicher, können mehrere Bits sich einen grossen billigen Kern teilen. Limite ist wieviele Drähte durch den Kern durchpassen. Für Serienfabrikation wurden die auf Verdrahtungsfehler (= Bitfehler in einem einzelnen Exemplar) anfälligen Drähte durch einen Stapel dünner Platinen (oder gar Folien) ersetzt bei denen die Leiterbahnen durch oder neben einen 2-teiligen Kern gehen, z.B. das TROS Design im Befehlsdecoder eines IBM 360/40. Es gab sogar eine reine Drahtschleifen Variante ohne die Kerne, die im HP 9100 Tischrechner verwendet wurde.

Programmierbare ROMs als Logik Ersatz

Mikroprozessoren sind erfolgreich als Steuerungen und Computer, aber in der ursprünglichen Anwendung, viele Logikchips zu ersetzen haben die ein grosses Problem: sie sind zwar kleiner und billiger, aber langsam. Verdrahtete Logik arbeitet je nach Aufgabe etwa 30-1000 mal schneller als Programme, weil bei Logik alles gleichzeitig passiert, während ein Program jede einzelne Funktion sequenziell abarbeiten muss. Das sieht man gut bei Emulatoren, wo ein x-100MHz PC gerade einen 1MHz C64 emulieren kann. Das ist der Preis den man zahlt dafür, dass der Mikroprozessor ein Program Befehl für Befehl abarbeitet.

ROM/PROM/EPROM/Flash Chips machen aber eigentlich nur eines: für jede Adresse (= Eingangs Kombination von a Bits) am Ausgang d Datenbits von sich geben. Sie sind dazu so verdrahtet:

      ROM/PROM/EPROM/Flash Ausschnitt
      3 Adressleitungen, 2^3=8 Adressen, 4bit breit

        I = Inverter, ergeben ~A0, ~A1, ~A2, ...
        o = verbunden, - = keine Verbindung
        U = UND um Adresse zu dekodieren, pro Adresse eine Zeile

      ... A2    A1    A0
          |__   |__   |__ 
          |  |  |  |  |  | 
          |  I  |  I  |  I                              Produktterm:
          |  |  |  |  |  |               |  |  |  |
      ..-----o-----o-----o--U - 0 ---..--x--x--x--x-    alle ~An UND = 0
          |  |  |  |  |  |               |  |  |  |
      ..-----o-----o--o-----U - 1 ---..--x--x--x--x-    nur A0 rest ~ UND = 1
          |  |  |  |  |  |               |  |  |  |
      ..-----o--o--------o--U - 2 ---..--x--x--x--x-    nur A1 rest ~ UND = 2
          |  |  |  |  |  |               |  |  |  |
      ..-----o--o-----o-----U - 3 ---..--x--x--x--x-    A1 UND A0 rest ~ = 3
          |  |  |  |  |  |               |  |  |  |
      ..--o--------o-----o--U - 4 ---..--x--x--x--x-    nur A2 rest ~ UND = 4
          |  |  |  |  |  |               |  |  |  |
      ..--o--------o--o-----U - 5 ---..--x--x--x--x-
          |  |  |  |  |  |               |  |  |  |
      ..--o-----o--------o--U - 6 ---..--x--x--x--x-
          |  |  |  |  |  |               |  |  |  |
      ..--o-----o-----o-----U - 7 ---..--x--x--x--x-    x = Datenbits und Diode
          |  |  |  |  |  |               |  |  |  |       = o oder -
          :  :  :  :  :  :               :  :  :  :
                                         |  |  |  |
                                         O  O  O  O     ODER der Bits

                                         |  |  |  |
                                     ..  D3 D2 D1 D0    Daten Ausgang
    

Die "I" sind Inverter, die "o" sind Verbindungen (- = keine Verbindung) die beim Herstellen eingebaut werden, die zusammen mit den "U" UND Elementen sorgen dafür, dass bei jeder möglichen Adresse eine Zeile (Produktterm genannt) aktiv wird. Die x sind die Daten. Bei ROMs sind das die Metalltröpfchen, bei PROMs die Metallbrückchen, bei EPROMs und Flash spezielle Transistoren die "hangen bleiben" können. Beides wirken mit dem "O" ODER Elementen unten zusammen um die Daten zu sammeln. Das ROM/PROM/EPROM/Flash ist also eine NICHT+UND+ODER Logik (implementiert als NICHT+NICHT-UND+NICHT-UND). Eigentlich nichts anders als ein grosser Multiplexer bei dem die UND Teile vor dem ODER teilweise angeschlossen sind.

Man kann daher ROMs auch als kombinatorische Logik Chips missbrauchen. Dazu lädt man in den Speicherchip die gewünschte Logik als Tabelle. So wurde z.B. ein PROM im Disk Controler vom Apple II benutzt. Die MIT Lisp Maschine verwendete z.B. 16 Stück 1024x4bit PROMs um einen 32bit breiten 32:1 Multiplexer zu bauen, mit dem Bitfelder innerhalb eines Wortes verschoben werden.

Als kleines Beispiel würde unser 2:1 Multiplexer von Schalten mit Boole weiter oben eine Tabelle von (2^3)x1=8x1=8it benötigen:

      Adresse     Daten            Wir verdrahten:
      ...76543210  76543210              A0 = e0, A1 = e1, A2 = wahl, D0 = aus
           ...000:     ...0              ab A3... = andere Eingäange
           ...001:     ...1                 für andere Funktionen
           ...010:     ...0              ab D1... = andere Ausgänge
           ...011:     ...1                 für andere Funktionen
           ...100:     ...0
           ...101:     ...0              Eingänge können von mehreren
           ...110:     ...1              Funktionen geteilt werden, spart Platz
           ...111:     ...1
                           \wiederholen für alle möglichen Positionen
    

Als grösseres Beispiel würde ein 4bit Addierer, 9 Eingänge (4 a Bits, 4 b Bits, 1 Übertrag) und 5 Ausgänge (4 Ergebnis Bits, 1 Übertrag) haben. Also braucht das ein (2^9)x5=512x5=2560bit an PROM Platz. Und folglich hier eine 512-zeilige Tabelle, wesshalb ich das hier nicht aufzeichne. Das würde man minimal in einem 512x8=4kBit Chip implementieren. Wenn man aber z.B. einen 64kBit=8192x8bit Chip hat, könnte man noch eine weitere 13-9=4 Eingang und 8-5=3 Ausgang Funktion mit einbauen. Oder man nimmt einen weiteren Eingang und verwendet diesen um zwischen Addition und Subtraktion (oder anderen Funktionen, bis zur vollen 16-Funktion und Arithmetik ALU, addiert dafür 5 Adressleitungen) Tabellen zu wählen.

Es wurden auf diese Weise schon ganze Eigenbau Rechner (z.B. die MyCPU) mit nur TTL Register Chips und EPROMs gebaut.

Programmierbare Logik Arrays (PLA)

Speicherchips als Logik zu missbrauchen hat ein Problem: Sie sind darauf optimiert, möglichst viel Speicher mit möglichst wenig Adressen zu auswählen. Wenn wir nun für Logik möglichst viele Eingänge (also Adressen) wollen gibt das einen riesigen Chip. Z.B. brauchte unsere 74181 4bit ALU Ersatz Tabelle in Logiktabellen und Arithmetisch-Logische Einheit weiter oben einen ganzen 128kBit grossen 27128 Chip, um einen ursprünglich ganze 75 Gatter grossen Chip zu ersetzen. Ein 8bit breiter nur-Addierer+Subtrahierer sind bereits 2*8+1+1=18 Eingänge, das ergibt 2^18=262144=256k mal die 9 Ausgangs Bits. Das wären bei den Standardgrössen von ROMs 2 Stück 256kx8=2Mbit Chips, also vor 1990 nicht implementierbar. Der obige MyCPU Rechner verwendet sogar 27801 (1024kx8bit=8MBit) EPROMs.

PROMs brauchen so viel Platz, weil ihre Multiplexer voll dekodiert sind. Das heisst für jede Kombination der Eingänge hat es eine Zeile im Chip drin. Das gibt für n Adressleitungen, 2^n Adressen und somit 2^n Zeilen. Eine kombinatorische Explosion. Das ist so für Speicher sinnvoll, weil man mit möglichst wenigen Auswahlsignalen möglichst viele Speicherstellen haben will, aber für Logik massive Platzverschwendung. Das exponentiale Wachstum an Zeilen killt diese Lösung oberhalb von etwa 10..12 Eingängen (der 74181 hat deren 14).

Die Antwort darauf war ein Chip mit ebenfalls brennbarer Definition der Zeilen, also auch ein "x" überall links im Chip. Das gibt ein PLA, und wurde ab etwa 1975 hergestellt. So eins hat nur etwa 4*Ausgänge an Zeilen statt 2^Eingänge an Zeilen. So eines wurde z.B. im C64 als Adress-Decoder verwendet um die Speicher Chips (Haupt DRAM, Farbtabelle SRAM, 3 ROMs, 2 Modul ROMS, sowie IO Chips) auszuwählen. Mit 16 Eingängen und 8 Ausgängen wäre dazu ein 64kx8=512kbit PROM nötig gewesen. So ist es aber nur ein kleines 2*16x4*8+8x4*8=1280bit PLA. Das zu einer Zeit als es bei ROMs noch mit 64kbit Schluss war. Und viel schneller sind diese kleinen Chips auch noch, weil geringere Signallaufdistanzen.

Programmierbare Array Logik (PAL)

PLAs haben ein kleineres Problem: die haben 2 programmierbare Sektionen und sind daher langsamer als PROMs gleicher Grösse. Dies wurde gelöst, indem man nur die Bits links programmierbar macht, und dann rechts fix 8 Zeilen jedem Ausgang zuordnet, also gewöhnliche hartverdrahtete 8-Eingang ODER verwendet. Das ergab dann die PALs, die späte 1970er erschienen. Z.B. ist ein PAL14L4 ein 20pin (2 Strom, 18 Daten) Chip, mit 14 Eingangspins, 14 Spalten, 32 Zeilen, und 4 Ausgangspins. Ganze 2*14*8*4=896bit sind dafür nötig. Selbst der grosse 20L8 (24pin (2S, 22D), mit 14 Eingangspins, 20 Spalten, 64 Zeilen, 8 Ausgängen (6 davon geben weitere Eingänge)) braucht nur 2*20*8*8=2560bits. Er dürfte den selbigen 74181 ersetzen können wie unser 128kBit 27128 EPROM, das 51.2 mal grösser ist.

Aber die PAL Entwickler gingen noch weiter und lieferten auch PALs mit Flipflops bereits drin, damit man nicht mit Ausgang-pin + externes FF + Eingangs-pin Pins (und Chips) verschwenden muss. Das waren die "R" Serie Register PALs. Z.B. ist ein PAL16R6 ein 20pin (2 Strom, 18 Daten) Chip, mit 8 Eingangspins, 16 Spalten (8 von Pins, 8 von Ausgängen), 64 Zeilen, 8 8-Eingang ODER, 6 FFs (andere 2 Ausgänge ohne FF), 8 Ausgangspins. Die restlichen 2 Pins sind einer Clk für die FFs und einer um die FF Ausgänge abzuschalten. Alle PAL16Rx haben 2*16*8*8=2048bit.

Aber die "R" PALs waren recht inflexibel. Sie haben genau 2/4/6/8 FFs, fest verdrahtet, und können nur alle Eingänge aufs mal schalten, und haben kein Reset für die Flipflops. Also wurde anfangs 1980er dann der flexible 22V10 erfunden. Dies ist ein 24pin (2 Strom, 22 Daten), mit 12 Eingangspins, 12+10 Spalten, abgestuft 2*8+2*10..+2*16=120Zeilen pro Ausgang, 10 ODER, 10 wählbar aktiven FFs, sowie auch wählbar Ein-/Ausgänge, sogar in Betrieb schaltbar, und Reset. Der 22V10 braucht dafür ganze 5280 Bits. Den konfigurierbaren Ausgangs Mechanismus nennt man Makrozelle.

In den späteren 1980ern dann die kleineren 16V8 und 20V8 produziert. Ziel war, so klein (und billig) wie 16R8 bzw 20R8 zu sein, alle H, L und R Typen emulieren zu können (nur noch 2 statt über 30 Typen), und doch so viel wie möglich der 22V10 Features zu bieten. Diese waren ein Riesenerfolg. Seit etwa 1990 werden daher nur noch "V" PALs eingesetzt.

Die heutigen PALCE und GAL varianten der PAL Chips sind ausserdem noch löschbar, wie Flash Chips, und in CMOS Technik schnell und stromsparend und kühl. Sie sind auch durch Fehlprogrammierung nicht mehr zerstörbar. Und sie sind heute immer noch von mehreren Firmen als geringe Variantenzahl Standardteile erhältlich, und daher billig. Also ideal für Hobby-Elektroniker neben Mikroprozessoren, RAM und Flash Chips. ROMs als Logik Ersatz haben damit ausgedient.

Complex Programmable Logic Device (CPLD) und Field Programmable Gate Array (FPGA)

PALs haben nur noch ein grosses Problem: sie wachsen langsam. Jeder Ausgang braucht 2*i*8 Bits., was für o Ausgänge 2*i*n*o Bits braucht. Das wächst bei R und V PALs, wo i grösser als sein muss als o, mit dem Quadrat der Ausgänge, also O(2) für die Informatiker. Trotz Moore-schem Gesetz (Chips werden alle 3 Jahre 4 mal so gross, exponential) ergibt dieses nur lineare Grössenzunahme. Doppelte Ausgänge alle 3 Jahre. Und langsamer werden dabei. Ersteres wäre noch erträglich, da Pins nur langsam zunehmen, aber zweiteres nicht.

Aber für komplexe Schaltungen, mit viel Innenleben in einem Chip, muss man alle die internen (nicht nach aussen verdrahteten) Makrozellen auch als "Ausgänge" (und folglich auch Eingänge) des Arrays zählen. Wenn man eine quadratisch anwachsende Anzahl MZs haben will, wie bei ASICs, dann muss man die Anzahl Bits auf O(1) statt O(2) reduzieren können.

Als Ausweg hat man die CPLDs (mehrere PALs auf einem Chip) und FPGAs (viele kleine ROMs auf einem Chip) erfunden. Diese offerieren Platz für viel (CPLDs) oder gar sehr viel (FPGAs) Logik auf einem Chip, ohne dass man in teure Gate Array oder ASIC investieren müsste.

Diese haben aber ein extrem massives Problem: Um die Programmierbits zu machen wird wie bei ROM/PROM/EPROM/Flash für Prozessoren oder PLAs/PALs ein Compiler benötigt. Die CPLD und FPGA Hersteller liefern dazu zwar Compiler/Tools. Aber diese sind aber alle Closed Source, zumeist nur für Windows NT und Solaris/Sparc, selten mal noch AIX oder HP-UX oder Linux/Intel, und sind Kommerzsoftware vom schlechtesten: bloatig, langsam, buggy, mit Dongles, und mit einer "Der User muss nichts wissen" (und darf nichts wissen!) Einstellung geschrieben. Und ausser den kostenlosen Abgaben für ausgewählte kleine Chips (für Schulungszwecke) sind sie schweineteuer, so $3000-30'000.

Das schlimmste ist aber, dass mangels Dokumentation der Bit Anordnungen (und Weigerung der Hersteller diese zu veröffentlichen) bessere alternative Compiler unmöglich sind. Dies verunmöglicht es auch, der "eine brauchbare Programmiersprache kann ihren eigenen Compiler kompilieren" Regel mit einer "ein brauchbarer Eigenbaurechner kann seine eigenen programmierbaren Chips kompilieren" Regel zu folgen. Daher kann man als Hobbyist diese Chips getrost vergessen, so schön (oder gar ideal) sie auch wären.

Die PALs sind dagegen, bis zum 22V10 hinauf (und noch ein paar wenige Einzelhersteller erweiterte Versionen), vollständig dokumentiert. Also kann man für die auch einschränkungslose gut funktionierende Open Source Compiler bekommen. Ich fand bisher zwei, die aber nur die beiden 16V8 und 20V8 Teile können: galprog - GAL Programmer Software for Linux und Galassembler für 16V8 und 20V8 Gals. Diese beiden PALs reichen aber eh für fast alles.


Home | Artikel | VCFE Rechner Grundlagen

Diese Seite ist von Neil Franklin, letzte Änderung für VCFe 2006.04.27, letzte Änderung für LUGS Wiederholung 2007.01.12