Dabei soll gezeigt werden, dass Bits eine grundlegende mathematische Sache sind und keine elektrische, und dass sie auch ohne Strom und Elektronik leben können. Elektronik ist lediglich eine sehr effiziente und daher heute verbreitete Methode.
Es wird angenommen, dass der Zuhörer die Hardware Seite von Rechnern bisher nicht gross kennt, ausser asllenfalls 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 teilweise vorausgesetzt, dass der Zuhörer die Grundlagen von programmieren kennt (und sei dies nur Scripte oder Makros). Allenfalls kann mehr Nutzen herausgeholt werden mit Konzepte wie Variablen, Ausdrücke, Zuweisungen, Schleifen und Unterprogramme kennen.
Dieser Vortrag ist eine 2006 überarbeitete und stark abgewandelte (manches weggekürzt und mit einiges an anderem erweitert, dadurch etwa 1/4 grösser geworden) Form eines früheren Vortrages am 2003.05.30 am LUGcamp zum Thema Offene Hardware herstellen.
Er 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).
Später wurde ein zweiter Teil addiert am 2008.04.26 am VCFe zum Thema 8008/U808 8080/8085 und Z80/U880 - Maschinencode und Assembler.
Er wurde weit später, für eine Wiederholung am 2025.08.02 am VCFe in Hünfeld, etwas mehr überarbeitet (selbige Sorte Sachen, nur etwas mehr davon) und etwas erweitert (alles zu BCD).
Man hat bereits im 17. Jahrhundert Rechenmaschinen gebaut. Wilhelm Schickard (1592-1635), Astronomieprofessor, hat damals einen mechanischen Rechner entworfen und als Prototyp gebaut, aber die finale Ausführung wurde noch unfertig durch einen Brand vernichtet. Blaise Pascal (1623-62), Sohn eines Steuerbeamten, hat damals einen mechanischen Addierer für seinen Vater gebaut, der produktiv war. Ältere Zuhörer werden sich noch an die mechanischen Registrierkassen errinnern, die früher in den Läden benutzt wurden, die Dinger mit einer Zehnerspalte Tasten pro Dezimalstelle, auf die man "300 70 5 +" tippte, gefolgt von "<ratter> <ratter>". Dort war die selbige Mechanik darin, 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 Abakus 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, weil Zahlen als reversierbare Kugelpositionen notiert werden.)
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 zweistellige Ausgabe mit einen Übertrag will, gibt das zudem 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 Kombinationen, mal die zwei Einrichtungen. Multiplikation hat wegen Übertrag 0 bis 9 sogar 10*10*10=1000 Kombinationen, mal die zwei Einrichtungen.
Auch der Versuch von Charles Babbage (1791-1871), mitte 1800ern einen Computer zu bauen, die Difference Engine, um mit Differentialen Tabellen von Navigationsdaten automatisch und damit fehlerfrei zu rechnen, und ohne Person dazwischen fehlerfrei auszudrucken (bzw eher zu schriftsetzen), ist letztlich daran gescheitert, dass dies mechanisch zu kompliziert war. Dies trotz Finanzierung durch die britische Navy, die gestrandete und gesunkene Schiffe wegen Rechen- und Druckfehler in Tabellenbüchern einsparen wollte.
(Nicht geholfen hat, dass diese ebenfalls durch Brand vernichtet wurde, und er als Erfinder dann statt gemachtes wiederholen lieben gleich die inzwischen entworfene frei programmierbare Analythical Engine bauen wollte. Wozu die vorhandene Finanzen nicht mehr reichten, und er mangels etwas liefern keine Erweiterung bekam.)
Der Computer wurde realistisch möglich, als man auf binäre Zahlen umstellte, also Zahlen mit nur 2 Ziffern. Das vereinfachte die Rechner gewaltig (die zwei Einrichtungen mit nur noch 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. Nur ist dies ungewohnt, also hier eine Einführung:
Beispiel binäres Rechnen mit vorzeichenlosen 8bit Zahlen (Zahlenbereich 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 ( 1 1 1 Übertrag) | ---- ---- | --- 1011 0011 Ergebnis | = 128 +32+16 +2+1 = 179 Zum Vergleich normales Dezimales Rechnen mit vorzeichenlosen 3 Ziffern (Zahlenbereich von 0..10^n, für n=3: 0..10^3-1 = 0..999) 210 Nummer n der Ziffer, n=2..0 1 Wert der Ziffer, für n=2..0: 10^n = 100..1 01 001 --- 76 Operand1 + 103 Operand2 ( Übertrag keiner) --- 179
Weil Binärzahlen sehr schnell sehr lang und unübersichtlich 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 A C E sind gerade wie 0 2 4 6 8, und B D F ungerade wie 1 3 5 7 9 0000 0000 -> 00 0000 1111 -> 0F 0001 0000 -> 10 0001 1111 -> 1F 0010 0000 -> 20 ... ... 1111 1111 -> FF
Dieser Gebrauch von Buchstaben A bis F als Ziffern 10 bis 15 musste zuerst enzdeckt werden. Davor wurden deswegen Oktale Zahlen benutzt, bei denen man nur 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, wie 12 18 24 36 48 60bit sowie 6bit Textformate ohne Kleinbuchstaben. Demensprechend kamen hexadezimal designte Rechner auf, nachdem auf 2^n Bits Wortbreite standardisiert wurde, die nicht durch 3 teilbar sind, die heutige 4 8 16 32 64bit Serie mit 8bit Textformate. Dieser Wechsel geschah um 1970 herum.
Zum Glück 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 an Zahlenbereich fassen kann. In n Bits passen nur 2^n Zahlen, von 0 bis 2^n-1 (bei 8bit 0..2^8-1 = 0..255). Wenn man nun von 0 einen Schritt zurückgeht, was -1 geben sollte, landet man wieder auf 255 = maximal. Man definiert nun einfach, dass 255 = -1 ist, damit 254 = -2, und so weiter. Damit kollidieren die 0..n laufenden positiven und -1..-n laufenden negativen Zahlen irgendwo in der Mitte.
Man spaltet dabei der ganzen Zahlenbereich von 0..2^n-1 in 2 Hälften. Dazu definiert man das ganz links liegende höchstwertige Bit als das Vorzeichen um, mit 0=positiv und 1=negativ. Womit alle 100... bis 111... als den negativen Bereich uminterpretiert werden, mit -2^(n-1)..-1 (hier bei 8bit -128..-1), und der verbleibende Rest von n-1 Bits (hier 7bit) als positiven Bereich 0..(2^(n-1))-1 (hier 0..127) erhalten bleibt. Das sieht dann so aus:
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 bei 8bit die -128 ohne +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 um sie herzustellen und sind somit verschwunden. Wieder etwa um 1970.
Dezimal eingeben ist noch recht einfach, da obige 179 schlicht kompakt für 1*100+7*10+9*1 ist. Dies kann man erstaunlich einfach umrechnen, mit einer *10 Serie, dabei anfangs 0 und dann pro Ziffer *10 +1 dann *10 +7 dann *10 +9. Dies geschieht erst noch genau in der Reihenfolge der Ziffern eingeben. Die *10 ist eine Konstante, und kann mit mehrmals Addition gemacht werden, mit zuerst a+a = 2*a dann 2a+2a = 4*a, dann 4a+a = 5*a, dann 5a+5a = 10*a.
Aber dezimal ausgeben ist weit aufwendiger, weil dazu /10 Division notwendig wird, mit den Modulo Resten als Ziffer ausgeben, und dann den Quotienten weiter so zerlegen bis dieser zu 0 wird, was aber Ziffern von hinten nach vorne liefert. Gerade bei kleinen Geräten wie Taschenrechner kann dies zu langsam sein, wenn nach jedem Rechenschritt die Anzeige nachf¨hren. (Stikte kann man auch dazu mit Multiplikation arbeiten. Nur müsste man dann die *2 Serie rechen, aber diese in dezimal, was der binäre Rechner genau nicht kann!)
Brüche kann man zumindest in der Buchhaltung vermeiden durch alles in der kleinesten Einheit rechnen. Dazu muss nur obige Eingabe skaliert werden, von der Eingabeeinheit zur kleinsten, mit nach dem Komma passende Menge Nullen anfügen/auffüllen. Weiter muss bei der Ausgabe das Komma an der richtigen Stelle eingefügt werden. Aber bei manchen Rechnungen wird dies unpraktisch.
In beiden Fällen kann man zu Binär Codiertes Dezimal (BCD) greifen. Dabei werden pro Dezimalziffer 4bit benutzt, mit nur die Werte 0..9 gültig sein, aber A..F nicht mehr. Eine Rechnung 5+7 ergibt Hexadezimal C, was Dezimal 12 ist. Aber das muss nun auf BCD 12 korrigiert werden, was binär identisch mit Hexadezimal 12 ist, und damit Dezimal 18. Also muss jede der A..F erkannt werden, gefolgt von mit einem +6 diese Ungültigen übersprungen, hier von 12 zu 18.
Preis ist diese extra Logik, sowie etwas mehr Speicherverbrauch (0..999 in 3*4=12bit statt 0..1023 in 10bit). Dies wird bei kleinem Speicher und dank obige Umrechnung meiden gerade in kleinen Rechnern wie Taschenrechner weniger teuer. Und ist auch der Grund, warum in solchen lange Zeit 4bit Prozessoren benutzt wurden. Aber selbst IBM Grossrechner offerieren BCD, weil mehr Datenverwaltung als rechnen. Auch frühe (=1970er) 8bit und 16bit konnten dies, aber seit 32bit (in 1980ern) ist BCD verschwunden. (Strikte kann auch auch aufwendige Rechnungen in Binär machem, und nur die Ausgabe dann mit BCD *2 Serie rechnen statt in Binär /10. Weil das nur wenige Rechnungen sind, kann man die auch ohne BCD Hardware in Software abstottern.)
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, genauso wie 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 bei Negation. Ebenso sind UND und ODER kommutativ, also a & b = b & a und a | b = b | a, wie bei 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.)
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 bei der ersten Ziffer immer 0 ist) berücksichtigen, für einen Voll-Addierer üb,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, aber das ODER kümmert dies 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 noch mehr Aufwand. Noch schlimmer, wenn man weiss, dass mit Elektronik jedes EXODER wiederum in 4 NICHT-UND zerlegt werden muss.
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 in der b Leitung). Dazu noch bei der ersten Stelle den Übertrag von sonst stets 0 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 mit allenfalls 64bit Resultat 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 jetzt 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 nun versteht, wurde es aus gutem Grund erst so um 1990 (486er und 68040, je etwa 1 Million 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 darin. Selbst manche 32bit RISC Prozessoren haben keine Division darin (z.B. ARM). Frühe Sparcs hatten beides nicht.
Ein UND gibt bei b=0 stets 0 ab, sowie bei b=1 einfach a, und kann damit als "Durchlass" wirken. Ein ODER gibt bei irgendeinem Eingang 1 ein 1 ab, und kann damit als "Sammler" wirken. Somit kann man diese auch als Schalter verwenden. Dazu bekommt jeder wählbare Eingang ein UND als Schalter, der neben den Daten an a einen "Steuereingang" an b 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 e0 und e1 sind, und b und d die Steuereingänge s0 und s1 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 s0 s1 wahl aus 0 0 1 0 0 = 0 (= e0) 0 1 1 0 0 = 0 (= e0) 1 0 1 0 0 = 1 (= e0) 1 1 1 0 0 = 1 (= e0) 0 0 0 1 1 = 0 (= e1) 0 1 0 1 1 = 1 (= e1) 1 0 0 1 1 = 0 (= e1) 1 1 0 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. Dies ergibt (e0 & ~wahl) | (e1 & wahl). 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 (s0..s3) oder 2 eher Auswahleingänge (w0..w1). Die s0..s3 müssen aus w0..w1 abgeleitet werden, mit UND und davor allenfalls NICHT (s0 beide, s1 und s2 je einen, s3 keinen). Dies ergibt (e0 & ~w0 & ~w1) | (e1 & w0 & ~w1) | (e2 & ~w0 & w1) | (e3 & w0 & w1). Das ist dann ein 4:1 Multiplexer.
Mit 8 Steuereingängen (s0..s7) 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 passsende NICHT vor UND generieren kann. Die n Auswahl Signale kann man als n-bit breite Zahl auffassen, mit Wert 0..(2^n)-1, die man Auswahl (englisch Select) nennt.
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 grosser 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 und Zeit kritisch ist. Z.B. wurde der Befehlsdekodierer des Röhrenrechners Whirlwind (1952/53) so implementiert, als 120 bit breite Tabelle zu je 40 Zeilen (Zeilen von 5 Eingänge für 2^5=32 Operationen + 8 Zusatzsignale Timing).
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 welches 0 ist das zugehörige UND aus dem Multiplexer weglässt (und das ODER dahinter erst noch schmaler macht), und bei mehreren Tabellen (z.B. sum und üb) von den selbigen Eingängen (a und b und üb) die NICHT und UND 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 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 vom Ü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. Dazu 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.
.---------. Eingänge: -----| |----- Ausgänge | Logik | .----------. .----| |-----| Speicher |----. | `---------' `----------' | `----------------<-------------------' (Solche Graphiken nennt man übrigens ASCII-Art.)
Sobald wir Bits speichern können, können wir unsere Rechnung ü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 maximal unstabil. Das nennt man einen Oszillator. Der schwingt sehr schnell, so schnell die Schaltung schalten kann. Für einen langsameren Oszillator, welcher der restlichen Schaltung Zeit lässt, bremst man den Oszillator aus, mit einem Quarz in der Rückleitung (das Signal legt in diesem eine Strecke mit Schallgeschwindigkeit im Festkörper zurück statt Lichtgeschwindigkeit im Leiter). Das ist was in jeder Quarzuhr und jedem Rechner den Takt generiert.
(Bei Uhren per Standard 2^15=32768Hz benutzt, weil das einen noch angenehm kleinen Quarz mit einem relativ kleinen 15-stufigen /2 Teiler auf Sekunden reduzieren kann, mit sehr geringem Batterieverbrauch.)
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.
Um so ein FF umzuschalten, 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" bekommt. (In der Elektronik ist ohnehin ein solches NICHT-UND (englisch NAND) der einfachste Fall, ein normales UND entsteht durch NICHT NAND.)
Ein RS-FF zu benutzen ist mühsam. Um dies einfacher zu machen gibt es verschiedenste "Verteiler" Schaltungen. Die einfachste ist in beiden "Störleitungen" ein UND, welches als "Durchlass" wirkt, und vor dem einten ein NICHT, damit wir nur bei 0 eines "schubsen" und bei 1 das andere. Der Steuereingang der beiden UND wird als Tor (englisch Gate) nezeichnet. Es gibt darauf aufbauend komplexere, die ich hier um Zeit zu sparen weglasse.
Das Resultat davon ist zumeist das heute üblich benutzte Data FF (D-FF), das 2 Eingänge hat: D für Daten, und Clk (= Clock, = Takt) um es "anzuschubsen", als "speicher jetzt das passende" Datenbit Signal. Dieses Clk 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 nun ein Konzept das jeder Programmierer kennt. Rechnen tut man ja indem man Variable C = Variable A OP Variable B; 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 Variablen A und B) auswählen und zur ALU schicken, ALU Funktion auswählen, und beim Ergebnis Register (für Variable C) einen Clk Puls anlegen.
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 an 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 der üblichen 10^n Serie 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 braucht eine Speicherstelle. Die Variablennamen sind nichts anders als symbolische Namen für die Adressnummern der Speicherstellen der Variablen, also benamste Konstanten. Array Variablen sind eine Serie von Speicherstellen, deren Name die Adresse der ersten Elementes (= Basisadresse) entspricht. (Daher werden Array Indizes auch von 0 an durchnummeriert, weil das erste Element an Adresse = Basisadresse + 0 ist.)
Da Adressen durch Bits representiert sind, kann man Adressen auch im Speicher wie 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] das n zur Basisadresse hinzu 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 post-incrementierte Adressierung. Moderne Rechner machen diese Addition während sie auf Antwort vom Speicher warten, mit der ohnehin vorhandenen und zu diesem Zeitpunkt noch unbenutzten Addiereinheit. Ausser sie haben einen separaten Addierer dazu, um Datenumwege einzusparen.
Die Fähigkeit beliebige Speicherstellen in beliebiger Reihenfolge zu benutzen ergab dem Speicher auch den Namen Random Access Memory (RAM), im Gegensatz zu Disk basiertem nur blockweise zugreifbaren oder gar Band basierten nur von Anfang zu Ende seriellen Speicher.
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 a) nur das Clk Signal des richtigen wartenden Registers anstossen. Anderseits wissen wir, dass man b) Addition/Subtraktion/etc muss auswählen können. Ebenso muss man c) x verschiedene Multiplexer steuern für Eingabedaten auswählen. Und schliesslich d) Register und Speicher mit den richtigen Demultiplexer und Clk Signalen versorgen. All das in der richtigen Reihenfolge.
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 sollte also nun 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. Diese sind wiederum strukturiert, um alles obige a) bis d) zu erfüllen.
Ein Ausschnitt C Code als Beispiel:
... int b=10, c=5; /* nur statische Variablen weil einfacher */ int rechne(int a) { /* nur ein Parameter damit einfacher */ b = a+b*c-30; /* nein, dies rechnet nichts sinnvolles */ return (b*4-1); } /* es ist nur zur Demo diverser Operationen */ ... int d; ... d=rechne(5); /* und dann rufen wir es auf */ ...
Nun C zu Assember übersetzen (compilieren) wir das. In diesem Beispiel für einen Z80 Prozessor, das Resultat davon 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 als 16bit, Z80 üblich, also DW rechne: PUSH HL ; Parameter a, per Konvention in HL Register ; wegspeichern für später LD HL,(b) ; Haupt 16bit Arbeitsregister HL = b LD DE,(c) ; Hilfs 16bit Arbeitsregister DE = c CALL mult16 ; angenommen vorhandene 16bit*16bit Routine ; resultiert in HL = HL*DE, also b*c POP DE ; a wieder zurückholen, nach DE ADD HL,DE ; addieren, resultiert in HL = a+b*c LD DE,30 ; 30 holen für Subtraktion, in DE XOR A ; C Flag löschen damit SBC richtig rechnet SBC HL,DE ; subtrahieren, resultiert 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 Prozessoren LD (b),HL ; Speicherplatz b = HL ADD HL,HL ; sich selbst addieren, HL = HL*2 ADD HL,HL ; nochmals, HL = HL*2, ergibt *4 DEC HL ; 1 subtrahieren, HL = HL-1 RET ; HL enthält bereits den Rückgabewert ; passt zur üblichen Konvention ... d: DW ; ohne Vorgabe wird mit 0 gefüllt ... LD HL,5 ; erster Parameter in HL übergeben ; bei mehreren Parametern müssten PUSH rein CALL rechne ; rechne aufrufen LD (d),HL ; und Ergebnis wegspeichern nach d ...
Nun Assembler zu Maschinencode übersetzen (Assemblieren) wir das weiter (das was in der Programdatei darin ist). Die Details der Bedeutung der Maschinencode Zahlen wird hier nicht erklärt, dies ist in einem späterem zweiten Vortrag beschrieben. Man sieht aber bereits hier, das es teils mehrere Bytes (bei Z80 sind 1..4 möglich) Codezahlen pro Befehl braucht:
Adres Bytes (alle in Hexadezimal, 0 = binär 0000, F = 1111) .... : ... 1234: 0A 00 | b DW 10 ; 000A = 10, merken: b an 1234 1236: 05 00 | c DW 5 ; 0005 = 5, merken: c an 1236 1238: E5 | rechne PUSH HL ; merken: rechne 1238 1239: 2A 34 12 | LD HL,(b) ; 2A = LD HL,() | ; 34 12 = die 1234 von oben b | ; Z80 will die 34 vor der 12 123C: ED 58 36 12 | LD DE,(c) ; ED = erweiterter Befehl | ; solche Unregelmässigkeit | ; ist typisch für | ; erweiterte Prozessoren 1240: CD xx xx | CALL mult16 ; CD = call | ; xx xx = wo mult16 liegt 1243: D1 | POP DE 1244: 19 | ADD HL,DE 1245: 11 1E 00 | LD DE,30 ; 001E = 30, als 1E vor 00 1248: AF | XOR A 1249: ED 52 | SBC HL,DE ; wieder nur dank ED erweitert 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 ; als 0, merken: d an 2345 .... : ... 3456: 21 05 00 | LD HL,5 ; *** Program startet hier *** 3459: CD 38 12 | CALL rechne ; 1238 von oben, ab zum PUSH HL 345C: 22 45 23 | LD (d),HL .... : ...
Um dies auszuführen wird nun so vorgegangen: Mit einem speziellen Adressregister namens Programzähler (englisch Program Counter, PC), das momentan z.B. die Adresse 3456 darin hat, wird auf den Speicher zugegriffen. Es kommt der Inhalt 21 (der Befehl LD HL,Konstante) zurück. Dem PC wird 1 addiert, eben gezählt, damit er 3457 hat, somit für das nächste Byte vom Program bereit it. Wieder einmal die post-incrementierte Adressierung. Wobei anzumerken ist, dass post-increment eigentlich für dies hier erfunden wurde, und erst später für Arrays genutzt wurde. Auch die Entscheidung, ob dazu ein eigener Addierer vorhanden ist, richtet sich vor allem nach diesem Gebrauch. (Ist bei Z80 der Fall.)
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) um 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) um das 1238 zu holen, 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.
Das PUSH HL ist nur ein Befehl holen (und PC + 1), ohne weitere Zugriffe mit PC, aber statt dessen 2 Abspeicher Zugriffe mit einem weiteren Adressregister namens Stapelzeiger (englisch Stack Pointer, SP). Das LD HL,(b) ist noch komplexer, weil es zuerst mit PC zwei weitere Zugriffe das 1234 holt, und dann mit diesem und 1235 zwei Zugriffe die Variable holt, anfangs 10.
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, mit bei einer 4MHz Z80 etwa 1 Zugriff pro Mukrosekunge, also 1 Million Zugriffe pro Sekunde. Daher braucht man auch ein ROM/PROM/EPROM/Flash Speicher mit einem Bootloader oder BIOS darin, damit der Prozessor sofort etwas zu machen vorgesetzt bekommt, sonst ist sofortiger Absturz in Millionstel Sekunden 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!
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 kombinieren. 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 e0..e2 Schieber gedrückt werden. Dies kann man bewerkstelligen, indem man einen der 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:
===== e1 0 A Hier auf/ab gezeichnet, kann aber Winkelhebel | | | auch seitliche Ausweichung sein \ | |_.-. 1 V .. \ _| |_.-' aus wenn e1 unten/1 UND e0 rechts/1 e0 |`--------.-'_:-' .------------- |.--------`-' `------------- -> 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 und Hilfe von Studentenkollegen gebaut, in der Stube von der elterlichen Wohnung. Von dem befindet sich übrigens ein Nachbau (von Zuse selbst gemacht) im Deutschen Museum in München (zumindest Stand um 1990 herum).
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 Leitungsdistanz 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 (Meldereiter wechseln alle Stunde an einem solchen das Pferd gegen ein frisches, und teils auch alle paar Pferde auch den Reiter durch übergeben der Nachricht) 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 durchgelassen. 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, nur wenn beide geschaltet sind kommt Strom durch. Auch ein EXODER ist einfach: 2 Schalter die in beiden Positionen Anschlüsse haben und übers Kreuz zusammengehängt sind. Auch ein NICHT ist nur ein Schalter der nur ungedrückt zu ist:
| | | | V V V V NICHT _.-o ODER _.-o _.-o UND _.-oo---- -,--o' o--,- ----o' o------o' o---- ----o' o | | | | | | | | V | V V EXODER | _.-o | _.-oo--,,--oo-----o---- `--o' o--' ----o' o--'`--o
Angriffswinkel und Reihenfolge spielen auch keine Rolle, da Strom einfach den Drähten folgt. Energieversorgung 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.
Nicht überraschend hat Karl Zuse bereits bei der Z2 auf gebrauchte und ausgemusterte Telephonzentralen Relais umgestellt.
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).
Ohmsches Gesetz: Strom = Spannung / Widerstand
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 = Spannung / (Widerstand W1 + W2 + 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.
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 darin) ergeben dann MOS Logik:
Strom Spannung ------------>----.------- voll | .-. | | W1 | | `-' T2 gross T2 klein ----------. |------- fast voll fast nichts | |--' `--|| T2 (n-MOSFET, ergibt also NMOS Logik) |->. | | -----------------'------- nichts
Und die stromgesteuerten Bipolar Transistoren (die sind in den frühen niedrig integrierten Chips darin und noch früher in Einzeltransistor Logikmodulen verwendet), ergeben dann RTL Logik:
Strom Spannung ------------>----.------- voll | .-. | | W1 | | ____ `-' T2 gross T2 klein --|____|--. |------- fast voll fast nichts W3 | |.-' Strom `---| T2 (npn-Bipolar, ergibt mit W3 die RTL Logik) Begrenzer |`>. | | -----------------'------- nichts
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 darin:
Strom Spannung ------------>----.------- voll | |-<' .--|| T1 (p-MOSFET) | |--. T1 kl /T2 gr T1 gr/T2 kl ----------| |------- fast voll fast nichts | |--' `--|| T2 (n-MOSFET, ergeben zusammen CMOS) |->. | | -----------------'------- nichts
Mit 2 npn-Bipolar Transistoren (kein pnp-Bipolar oben weil zu teuer um zu integrieren, daher ist auch ein dritter Ansteuertransistor T3 nötig) und ergibt sich die TTL Technik die von 1970 bis 1985 dominierte.
Strom Spannung ------------>----.------- 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 (vereinfacht gezeichnet, Strombegrenzer fehlen)
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 | .-. | | 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
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 zu benutzen). 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 | | .-. .-. | | 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
Diese Logiken kann man auch alle komplementär machen. Bei CMOS mit je einem Transistorpaar pro Eingang. Dabei werden die unteren n-MOSFETs "gefaltetet" hintereinander verdrahtet (von Ausgang via T2a und dann T2b zu 0V), und die oberen T1a und T1b "neben einender" verdrahtet (jeder von 5V zu Ausgang). 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 dessen NOR Logik ein weiterer Grund für es aussterben.
Man kann dies auch bei beiden für beliebige Eingangszahlen "verbreitern", bei CMOS einfach mehr Paare, bei TTL einfach mehr linke Dioden. (Röhrenrechner folgen strikte den MOSFETs in ihrem spannungsgesteuertem Verhalten, sind aber teuer und folgen daher den TTLs in ihrem Gebrauch von Dioden.)
Bisher können wir nur NICHT-UND (oder NICHT-ODER) herstellen. Wie steht es mit dem Resten der logischen Funktionen?
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 einzelnen Silizium produziert und fest 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 auf) fast nur noch in Hobby und unkritischen professionellen Schaltungen darin.
Je grösser man Chips macht, umso mehr trift nun aber das Problem auf, dass sie zu spezialisiert werden, nur noch 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 und in Stromversorgungsrichtung laufende Leitungen nur einmal, um dann nur die Verdrahtung per in Querrichtung laufende Leitungen 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. Alle PC Chipsets sind GAs. Die PC Graphikchips sind oft ASICs.
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 aber 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 PDF) (daraus extrahierte Befehssatz 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 PDF) (daraus extrahierte Befehssatz 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) 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 ( ingescanntes Datenblatt PDF) (daraus extrahierte Befehssatz Daten). Diese haben auf einem einzelnen Chip einen Prozessor (in etwa ein auf 8bit Daten verbreiteter 4004) und etwas ROM/PROM/EPROM (je nach Modell 1-4kBytes) und wenig SRAM (je nach Modell 64-256 Bytes) und etwas IO (zumeist 3 8bit Ports). Also ein kompletter wenn auch simpler Computer auf einem Chip. Man muss nur Stromversorgung und etwas analog Krempel anhängen, und ein Program dafür schreiben.
Solche Microcontroller sind was man üblicherweise heute in vielen Geräten findet, z.B. in Audio oder Video Geräten (und in deren Fernbedienungen), oder einfachen Telephonen (und in deren SIM Karten), ebenso in Kreditkarten/Debitkarten, weiter in Haushaltsgeräten (Küche, Waschmaschine), oder in Heizungen, oder in Maschinen oder Autos oder Liften, auch in Ampelsteuerungen, oder in einfachen Verkaufsautomaten, aber auch in den kleineren PC Peripheriegeräten (Tastatur, Maus, HD, DVD/BR).
Teils sind diese auch in grösseren Versionen vorhanden, mit separatem Speicher, aber alles andere auf einem Chip, was als als SoC (System on Chip) bezeichnet. Diesen dann zusammen mit je einem RAM und Flash Chip, ist in allen Smartphones und Tabletts, grösseren Verkaufsautomaten inklusive Billetverkauf (alles mit Touchscreen), sowie grösserer Peripherie (Drucker, Scanner, Modem, WLAN Accesspoint), aber auch in einem Raspberry Pi oder Handheld Spielkonsolen. Hierbei spricht man genereller dann von Embedded Systemen. Der heutige Normalbürger besitzt und benutzt regelmässig, ohne es explizit zu wissen, irgendwo 10 bis 100 solcher Computerchen.
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.
Um 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 |
Um ein Bit einzuschreiben 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.
Um ein Bit auszulesen 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. um einen Zähler zu inkrementieren) konnten manche alte Prozessoren das erste Rückschreiben in solchen Fällen auslassen (read-modify-write Zyklus).
Heute gibt es 2 Sorten Speicher Chips:
bit=0 für diese Spalte bit=1 | | -)|(----------------------------)|(---- 5V | | | | | `-| T1 T3 |-' | ein SRAM Speicherbit | ||-. .-|| | | .-| | | |-. | in beiden Richtungen | T5 | | | | T6 | horizontal und vertikal |--. .--+----|-..-|----+--. .--| viele Male wiederholt | | | | | /\ | | | | | | ----- `-| |/ \| |-' ----- | z.B. 16kBit 128x128 | --- ||-' `-|| --- | | | .-| T2 T4 |-. | | | | | | | | -)|(----------------------------)|(---- Auswahl der Zeilen | | | | | ----- ----- | | 0V 0V | ZZZZZZZZZZZZZZZZAAZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZAAZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZAAZZZZZZZZZZZZZZZZ Gesammtstruktur vom SRAM Chip ZZZZZZZZZZZZZZZZAAZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZAAZZZZZZZZZZZZZZZZ Z = Zellen wie oben ZZZZZZZZZZZZZZZZAAZZZZZZZZZZZZZZZZ A = Auswahl der Zeilen ZZZZZZZZZZZZZZZZAAZZZZZZZZZZZZZZZZ B = Bits auslesen oder ZZZZZZZZZZZZZZZZAAZZZZZZZZZZZZZZZZ einschreiben BBBBBBBBBBBBBBBBSSBBBBBBBBBBBBBBBB S = Steuerung des Chips ZZZZZZZZZZZZZZZZAAZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZAAZZZZZZZZZZZZZZZZ z.B. 6264, 64kBit = 65536 Bit ZZZZZZZZZZZZZZZZAAZZZZZZZZZZZZZZZZ organisiert als 8kx8bit ZZZZZZZZZZZZZZZZAAZZZZZZZZZZZZZZZZ 256 Zeilen a je 32*8Bit ZZZZZZZZZZZZZZZZAAZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZAAZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZAAZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZAAZZZZZZZZZZZZZZZZWelche der Zeilen "zugreifbar" ist, wird von einer Hälfte der Adresse bestimmt. Die Spalte die "angeschubst" wird, hängt von der anderen Adresshälfte ab. Welche Leitung davon ob eine 1 oder 0 gespeichert werden muss. Um zu Lesen wird einfach eines der beiden vertikalen Leitungen benutzt, das Bit bleibt unverändert. SRAM wird heute v.a. im Prozessor darin für die Caches benutzt, ebenso in externen Cache Chips, aber auch als Hauptspeicher in Mikrocontrollern, weil im Stromverbrauch batteriefreundlich.
Bit für diese Spalte | | | | | ein DRAM Speicherbit |--. .----| |---| 0V | | | | | | wie SRAM wiederholt | ----- C selbige A und B und S Struktur | --- T | | | | -)|(------------------ Auswahl der Zeilen |Alle Cs einer Zeile werden zusammen geladen und entladen. Um eines zu schreiben muss die ganze Zeile geholt werden, das eine Bit ersetzt, und die Zeile zurückgeschrieben werden. Lesen leert die Kondensatoren, also löscht es alle Bits der Zeile, also müssen alle neu geschrieben werden. Noch schlimmer als bei Kernspeicher. Das ist der Hauptgrund warum DRAM langsamer als SRAM ist. Selbst ohne Lesen geht der Inhalt nach einer Weile verloren, muss also regelmässig durch Lesen und Neuschreiben aufgefrischt werden. DRAM wird in den SD-RAM Chips auf den DIMMs benutzt, weil es am meisten Kapazität fürs Geld und Platz bietet. Das dauernde neu Laden kostet auch Strom, was es für batteriebetriebene Geräte ineffizient macht.
Ein Cache (aus SRAMs) kann die Langsamkeit vom DRAMs (und den Leitungen) verstecken, indem er (hoffentlich) die häufig benutzten Daten beinhaltet (strikte hat er die in letzter Zeit genutzen darin). Ausserdem tut der Cache gleich einen Block von 4 oder 8 Speicherstellen (eine Cache Zeile) holen, damit man nur jedes 4te oder 8te Mal warten muss auf die DRAMs ihre Zeile auslesen und neu schreiben Aktion.
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 einschreiben (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, bzw oft einfach die Bitleitung wellenfärmig, entweder die Diode treffend oder verfehlend. Also musste man sein Program dem ROM Hersteller schicken, damit er die Leitungen richtig anordnet. 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, welche 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, quasi die CD-RWs der Chips. (Wieder mit Intel als einer der grössten Hersteller.)
SRAM und DRAM ist im Vergleich zu all den obigen die HDs der Chips, beliebig oft jederzeit schnell beschreibbar, aber allerdings ohne Strom alles vergessend, dafür wiederum 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 oder vorbeigehende Leitungen 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), um 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) ersetztn 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.
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 (und analog PROM/EPROM/Flash) Ausschnitt 2+3 Adressleitungen, 2^5=32 Adressen, 1bit breit I = Inverter, ergeben ~A0, ~A1, ~A2, ... o = verbunden, - = keine Verbindung U = UND um Adresse zu dekodieren, pro Adresse eine Zeile eine Adresshälfte Daten Zellen Teil an 8:1 Demultiplexer 4x8=32 bit gross ... A2 A1 A0 |-. |-. |-. U | | | | | | | N V | I | I | I D 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 als Dioden | | | | | | | | | | = o oder - : : : : : : : : : : ^ | | | | | O O O O ODER der Bits Auswahl der Zeilen Teil | | | | | | | | Bits auslesen Teil --> 4:1 Multiplexer <--- andere Adresshälfte | .. D0 Daten Ausgang
Die "I" sind Inverter, umd Aus Adressbits deren NICHT zu erzeugen. Die "o" sind Verbindungen (- = keine Verbindung) die beim Herstellen eingebaut werden, je eines pro Adressbit, entweder Bit oder NICHT, die zusammen mit den "U" UND Elementen sorgen dafür, dass bei jeder möglichen Adresse genau eine Zeile (Produktterm genannt) aktiv wird. Die Summe aller solcher Zeilen ist der Demultiplexer.
Die x sind die Daten. Bei ROMs sind das die Metalltröpfchen oder Leitertreffer, bei PROMs die Metallbrückchen, bei EPROMs und Flash spezielle Transistoren die "hangen bleiben" können. Diese wirken mit dem "O" ODER Elementen unten zusammen um die Daten zu sammeln. Der danach folgende Multiplexer erlaubt es, statt eines "langen" Chips die Adressen in eine Demutiplexer Hälfte und eine Multiplexer Häfte zu zerteilen, um einen quadratischen Chip zu bekommen.
Bei den üblichen 8bit breiten ROMs wird etwas gemischt. Ein 2364 ROM (pinkompatibel zu 2664 PROM und 2764 EPROM und 2964 Flash) ist ein 64kbit 256x256 Satz an Bits. Diese bestehen aus 256 Zeilen von 8 Adressleitungen 0..7 an 256:1 Demultiplexer getrieben und 256 Spalten als 8*32 genutzt. Letzeres mit 5 Adressleitungen 8..12 an 32:1 Multiplexer, für jedes der 8 Datenbits eines. Somit hat es 256*32 Adressen zu 8bit = 8kByte.
Das ROM/PROM/EPROM/Flash ist somit 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 damit alle Adressbits oberhalb A2 irrelevant sind
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. obigen 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 mit nur TTL Register Chips und EPROMs gebaut (z.B. die MyCPU).
PROMs brauchen so viel Platz, weil ihre Demultiplexer voll dekodiert sind. Das heisst für jede Kombination der Eingänge hat es eine Zeile im Chip darin. 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 ist es massive Platzverschwendung. Das exponentiale Wachstum an Zeilen killt diese Lösung oberhalb von etwa 10..12 Eingänge.
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. in Form eines Signetics 82S100 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 bei 64kbit Schluss war. Und viel schneller sind diese kleinen Chips auch noch, weil geringere Signallaufdistanzen.
Aber die PAL Entwickler gingen noch weiter und lieferten auch PALs mit Flipflops bereits darin, damit man nicht mit Ausgang-pin + externes FF + Eingangs-pin Pins (und Chips und Platine) verschwenden muss. Das waren die "R" Serie Register PALs. So ist PAL16R6 ein 20pin Chip (2S, 10E, 6A, 1Takt/Clk, 1Steuerung) mit 16 Spalten (10 von Eingänge + 6 von Teils der 8 Ausgängen) und 64 Zeilen. Es hat 6 Flipflops hinter 6 der 8 Ausgänge (die beiden anderen sind ohne FF kombinatorisch). Der Pin Steuerung ist um die FF Ausgänge abzuschalten, die kombinatorischen sind intern unter Opfer eines ihren 8 Zeilen einzeln steuerbar. Alle 20pin PAL16Rx haben 2*16*8*8=2048bit, alle 24pin PAL20Rx haben 2*20*8*8=2560bits (wie PAL20L8).
Aber die "R" PALs waren recht inflexibel. Sie haben genau 2/4/6/8 FFs, fest verdrahtet, und können nur alle Flipflops aufs mal schalten, und haben kein Reset für diese. Also wurde anfangs 1980er dann der flexible 22V10 erfunden. Dies ist ein 24pin (2S, 12E, 10E/A) mit 22 Spalten (alle 12E und 10E/A) und abgestuft 2*8+2*10..+2*16=120Zeilen pro Ausgang. Dazu kommt jeder Ausgang das Flipflop einzeln aktivierbar/abschaltbar sowie der Ausgangspin einzeln abschaltbar um als Eingang zu werden. Letzteres sogar im Betrieb umschaltbar. Den konfigurierbaren Ausgangs Mechanismus nennt man Makrozelle. Dazu noch Reset. Er braucht dafür ganze 5280 Bits.
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 nur noch die drei "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.
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 Makrozellen 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, mit nur teils Ausgänge programmierbar zu wenige Eingänge verbinden) und FPGAs (viele kleine ROMs auf einem Chip, mit programmierbarer Verdrahtung zwischen ihnen) 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 gar 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, nach 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. Diese drei PALs reichen aber eh für fast alles.
Anderseits sind heutige Prozessoren so schnell, dass vieles in Software machbar ist. Man vergleiche genau obige Emulatoren, wo ein x-100MHz PC gerade einen 1MHz C64 emulieren kann. Heute wo jeder Phone SoC mit GHz ARM daher kommt, ist selbst aufwendiges in Software kein Problem. Gerade seit selbst Smartphomes für Videospiele eine GPU haben, ist selbst 32bit Spielkonsolen emulieren auf neueren Phones und Raspberry Pi machbar. Deshalb haben es FPGA-basierte Emulationen schwer, weil für kleinere 8bit/16bit Sachen Software ausreicht und für grosse 32bit die FPGA massiv teuer werden.
Diese Seite ist von Neil Franklin, letzte Änderung 2006.04.27, letzte Änderung für LUGS Wiederholung 2007.01.12, letzte Änderung für VCFe Wiederholung 2025.06.30.