Üblicherweise hören Beschreibungen der Funktionsweise eines Prozessors an dieser Stelle auf, aber im Interesse der Vollständigkeit wollen wir hier doch einmal ein wenig tiefer in die Materie schauen; das Laden der Instruktion aus dem Speicher in ein Register ist nämlich an sich ein interessanter Vorgang.

Nehmen wir einmal an, die Instruktion wird aus dem Level-1-Cache geladen (was in der Regel auch der Fall ist). Der Cache ist aus sogenannten SRAM-Zellen aufgebaut; das sind im Grunde Flipflops mit einer speziellen Steuerelektronik. Untenstehende Abbildung (entnommen aus der Wikipedia) zeigt eine derartige Zelle, die ein einzelnes Bit speichern kann.

i-220068e80bcfea5ee24aba825ee485e4-SRAM.svg.png

Die Zelle besitzt die 3 Anschlüsse WL (Word Line), BL und BL. WL dient zur Aktivierung der Zelle – solange an WL 0 anliegt, geben BL und BL eine 0 aus. Soll nun der Wert des in der Zelle gespeicherten Bits ausgelesen werden, wird WL auf 1 geschaltet, woraufhin an BL der gespeicherte Wert (0 oder 1) anliegt (an BL entsprechend der invertierte Wert). Zum Schreiben eines Wertes in die Zelle reicht es, den gewünschten Wert an BL anzulegen (an BL wiederum entsprechend den invertierten Wert) und WL anschließend wieder auf 1 zu setzen (für die technisch Versierteren: damit das funktioniert, muss die an BL/BL angelegte Spannung größer sein als die in den Transistoren gespeicherte, um den aktuellen Wert zu überschreiben). Schaltet man mehrere dieser Zellen in einer zweidimensionalen Matrix (also einem Gitter) zusammen, so kann man damit auch größere Werte speichern – und so sind auch Caches aufgebaut. Üblicherweise hat das Gitter dabei so viele Spalten, wie ein Byte Bits hat (meistens 8, aber nicht immer) und entsprechend so viele Zeilen, wie Bytes im Cache gespeichert werden sollen. Über das Ansprechen einer bestimmten Zeile kann demzufolge ein Byte aus dem Cache ausgelesen werden. Ein Cache mit 5 Bytes, die jeweils aus 4 Bit bestehen, könnte also folgendermaßen aussehen; die einzelnen Bytes werden jeweils über die Eingänge b1 bis b5 angesprochen, wobei die einzelnen Bits der Zeilen an den Ausgängen o1 bis o4 anliegen (die invertierten Ausgänge der SRAM-Zellen habe ich einmal weggelassen):

i-b5be03f6ed9fbd3fa00121e505c079d8-CacheGrid.png

Bleibt noch zu klären, wie denn nun eine bestimmte Zeile angesprochen werden kann. Hierfür wird in der Regel ein sogenannter Decoder benutzt (den wir nachher auch noch einmal benötigen werden). Vereinfacht gesagt ist ein Decoder ein Bauelement, welches ein Signal in ein anderes umwandelt. Das wichtigste dabei ist, dass das Eingangs- und das Ausgangssignal nicht die gleiche Länge haben müssen – ein Decoder kann also etwa eine Bitkette (die ja nichts anderes ist als ein Signal) der Länge 2 in eine Bitkette der Länge 4 überführen. Hierbei können natürlich nie sämtliche Bit-Kombinationen an den Ausgängen erreicht werden (bei einem Eingangssignal mit 2 Bit sind eben nur insgesamt 4 Kombinationen möglich), aber das benötigt man auch gar nicht. Da der Aufbau eines Decoders in der Regel von der Signaltransformation abhängt, die er durchführen soll, spare ich mir diesmal das interaktive Bildchen; nur so viel: er lässt sich mit einfachen Grundbauelementen wie UND-Gattern und Invertern bauen. Im Folgenden der schematische Aufbau eines beispielhaften Decoders mit einem 2-Bit-Signal als Eingang und einem 4-Bit-Signal als Ausgang, gleich mit Wertetabelle (Quelle ist mal wieder die Wikipedia):

i-951bec0de4b6a5e3f56d26a9c32f6ec2-Decoder.svg.png

An dieser Schaltung lässt sich auch gleich sehen, wie ein Decoder helfen kann, einzelne Zeilen in einem Cache anzusprechen, indem er nämlich ein Eingangssignal (die Adresse) immer so transformiert, dass an lediglich einem Ausgang eine 1 anliegt (ein Decoder muss nicht zwangsläufig so arbeiten – die Ausgaben können beliebige Bitketten sein). Mit obigem Decoder könnten 4 Bytes adressiert werden, bei mehr Eingängen dann auch entsprechend mehr Bytes. Formal ausgedrückt: bei einer Adressbreite von n Bits können 2n Bytes adressiert werden; bei den heute üblichen Rechnern mit 32 Bit wären das 232, also etwas mehr als 4 Milliarden Bytes – das entspricht 4 Gigabyte (deswegen bringt es übrigens auch nichts, in einen Rechner mit einem 32-Bit-Betriebssystem mehr als 4 Gigabyte RAM einzubauen – sie können schlicht nicht vollständig adressiert werden).

1 / 2 / 3

Kommentare (12)

  1. #1 etth
    August 24, 2011

    Jetzt wo bekannt ist, wie ein Prozessor Zahlen addieren kann stellt sich mir die Frage, ob die Addition denn reicht, um alle notwendigen Operationen auszuführen. Die Division ist meines Wissens die einzige Rechenoperation, die nicht als Addition umschrieben werden kann. Wie wird diese durchgeführt? Und tun die Dinger wirklich nicht mehr als so grundsätzliche Rechenoperationen durchzuführen?

  2. #2 Marcus Frenkel
    August 24, 2011

    @etth
    Die Division mit ganzen Zahlen (also Division mit Rest) lässt sich ziemlich einfach als wiederholte Subtraktion umschreiben. Von der ersten Zahl wird die zweite einfach so lange abgezogen, bis ein Wert < 0 rauskommt - dann hat man das Ergebnis und den Rest. Etwas anders sieht es natürlich bei reellen Zahlen aus - aber vor denen habe ich mich mit voller Absicht etwas gedrückt, da insbesondere die Erklärung der Rechenoperationen hier ziemlich umfangreich werden würde. 😉 Teilweise arbeiten Prozessoren mit den grundsätzlichen Rechenoperationen, teilweise sind bestimmte Operationen aber auch anders umgesetzt; da sind schon einige Tricks möglich, die dann auch meist im Rechenwerk fest verdrahtet werden. Die Multiplikation wird zum Beispiel auch nicht einfach über wiederholtes addieren umgesetzt (was ginge, aber langsam wäre), sondern über eine Kombination aus Addition und Verschiebungen (Shiften, dafür waren die Schieberegister gut). Die Rechenoperationen beschränken sich natürlich auch mehr oder weniger auf die ALU - außerhalb davon kann der Prozessor natürlich auch noch Dinge, vor allem Daten hin- und herschicken und (de-)codieren. Aber ein Großteil der Arbeit findet schon in der ALU statt - nicht umsonst wird die Leistung eines Prozessors auch in FLOPS, also floating point operations per second (im Grunde Berechnungen) angegeben.

  3. #3 Dagger
    August 25, 2011

    Schöne Serie! Erinnert mich gut ans Studium 🙂

  4. #4 Jan von nebenan
    August 26, 2011

    Sehr schöner Artikel! Ein paar technische Sachen gäbe es vielleicht schon noch, aber ich weiß nicht, ob das nicht zu tief in die Materie geht. Ich fände z.B. interessant, mal auf die Features der neueren Prozessoren einzugehen. Die sich ja nicht nur deswegen so schnell, weil die Taktfrequenz höher ist: SIMD, Pipelining etc. wären solchen Themen, oder auch “intelligente” Funktionen wie branch prediction (Prozessor versucht zu ermitteln, ob eine If-Bedigung wahr oder falsch sein wird, und lädt entsprechende Daten vor – Trefferquote bis 98% bei modernen CPUs).

    Und eine Sache noch: Misst man die Leistung der ALU nicht in MIPS (million instructions per second)? FLOPS bezieht sich doch auf die FPU, die du ja geschickt ausgelassen hast… 😉

  5. #5 Marcus Frenkel
    August 26, 2011

    @Jan von nebenan
    Ui. Branch prediction ist natürlich ein ganz schöner Brocken. 😉
    Pipelining wäre aber in der Tat ganz interessant; ich hatte überlegt, es noch in diesem Artikel zu bringen, es dann aber auf Grund der Länge gelassen. SIMD ginge ja Richtung Prozessorarchitektur – da müssen dann auch noch SISD, MISD und MIMD erklärt werden. Ich überleg mir mal, ob das schon zu technisch ist oder nicht…oder zu magisch, da ich da auch nicht alles erklären kann und oft genug auf die Verdrahtung in der CPU verweisen muss. 😉

    Das mit den FLOPS war etwas ungeschickt ausgedrückt, ja. Man misst die Leistung einer ALU unter anderem in FLOPS (und meint dann damit die FPU darin). Die MIPS gibt es ja auch, aber die sind ähnlich aussagekräftig wie die Taktrate. 😉

    Und mit der FPU werde ich mich nur beschäftigen, wenn hier mindestens 5 Leute einen derartigen Wunsch äußern – Gleitkommawerte sind einfach zu garstig und mathematisch. 😉

  6. #6 icehawk
    August 27, 2011

    Erstmal möchte ich sagen, dass ich den Wunsch von Jan teile und auch gerne etwas über moderne Features lesen würde.

    Aber zu deiner einen Anmerkung:
    “für die technisch Versierteren: damit das funktioniert, muss die an BL/BL angelegte Spannung größer sein als die in den Transistoren gespeicherte, um den aktuellen Wert zu überschreiben”
    Das verwirrt mich etwas: Braucht man dann nicht mit der Zeit immer höhere Spannungen? Bei mehreren Ghz kommen ja schon ein paar Überschreibvorgänge zusammen: Wann geht der CPU der Saft aus? ^^

  7. #7 Marcus Frenkel
    August 27, 2011

    @icehawk
    Das Wort “gespeichert” war hier etwas unglücklich gewählt – ein Transistor speichert natürlich keine Spannung, das wäre ein wenig schwierig. Aber die beiden Transistor-Paare in der Mitte “speichern” ein Bit dadurch, dass sie sich in einem von zwei sogenannten stabilen Zuständen befinden, was die Verteilung der Leitfähigkeit und damit Spannung angeht – es reicht übrigens eine sehr niedrige Spannung, um so einen Zustand aufrecht zu erhalten. Zum Überschreiben muss kurzfristig eine stärkere Spannung angelegt werden, um die bisherige zu “überschreiben” – aber die stärkere Spannung bleibt nicht in den Transistoren bestehen. Somit braucht man auch nicht immer höhere Spannungen.

  8. #8 Engywuck
    August 31, 2011

    och, gegen FPU in leicht verdaulicher Form hätte ich nichts einzuwenden 😉 Addieren kann ja jeder ^^

    Zum Thema “Multiplizieren über Addition”: mein Opa hatte einen mechanischen Tischrechner (mit Motor) mit den drei Grundrechenarten: Addition, Subtraktion, Multiplikation (Division ist ja nicht einfach….). Damit habe ich als Kind gerne gespielt 🙂
    Man überlegt sich dann aber recht schnell, ob man “4 * 50” oder “50 * 4” berechnet. In ersterem Fall wird 4 – plus 4 – plus 4 – plus 4 …. gerechnet. Das Ganze mit Höllenlärm wegen der Mechanik samt Motor und entsprechendem Zeitaufwand (so 1-2 Operationen pro Sekunde….).
    Angeschafft wurde das Teil damals übrigens von der Milchgenossenschaft im Ort, weil mein Opa als “Zahlstelle” sich geweigert hat, die Auszahlungen von Hand zu berechnen. Bei seinem Vorgänger hatten die Mitglieder sich immer beschwert, er habe falsch gerechnet, mit der Maschine haben sie’s dann einfach geglaubt… 😉

  9. #9 Marcus Frenkel
    August 31, 2011

    Ja ja, die lieben Optimierungen bei der Befehlsausführung. Die gibt es ja immer noch – nur in hochkomplexer Form. 😉

    Ok, da sich nun einige was zu Gleitkommazahlen gewünscht haben, werde ich dazu wohl auch noch irgendwann einen Artikel bringen, in der Hoffnung, damit nicht die letzten Leser zu vergraulen. 😉

  10. #10 rolak
    September 4, 2011

    Hi etth, mit einer den üblichen Divisions-Algorithmen angemessenen Verzögerung: Da Fließkommazahlen letztendlich nur ganze Zahlen sind, läßt sich auch deren Division auf add/sub reduzieren. Es gibt meines Wissens nur einen wesentlichen Unterschied: Liegt ein HW-Multiplizierer für n-bit-Zahlen vor, kann mit minimalem Aufwand ein Algorithmus für k*n-bit-Zahlen implementiert werden (k² zu addierende Zwischenergebnisse) während einem ein HW-Dividierer diese Möglichkeit nicht bietet.
    In der Praxis werden jedoch schnellere Techniken genutzt (SRT, N/R, Goldschmidt) — was dann vielleicht schon erste Vorschläge für den FPU-Artikel wären, da ansonsten doch exakt genauso gerechnet wird wie vom schriftlichen multiplizieren/dividieren gewohnt. Nur halt im Binärsystem.

    Branch predicition ist doch, wenn auch wesentlich für Tempo und effiziente Pipeline-Auslastung, ‘nur’ eine Kombination aus statistischer Auswertung (zB saturating counter) und Schmierzettel (zB BTB).
    Beim Z80 war die Ausführungszeit ja noch fest – doch auch in diesem Zusammenhang hatte ich ziemliche Schwierigkeiten, einen Programmierer davon zu überzeugen, daß ~150 gesparte Takte bei dem 1aus256-Sonderfall die je 2 Takte für den nicht ausgeführten bedingten Sprung in keiner Weise aufwiegen. Auch eine Art von branch prediction 😉

    Du wirst schon keinen vergraulen, Marcus. Schlimmstenfalls werden einige posts seltener gelesen als andere(wenn schon nicht seltener geklickt), doch das dürfte bei noch so sorgfältiger Vorabfilterung eigentlich bei keinem Themenkomplex zu vermeiden sein.

  11. #11 Michi
    September 23, 2011

    Vielen vielen Dank! Die Serie hat mir viel geholfen!

  12. #12 ilikepepsi
    September 26, 2011

    @Marcus:

    Hi, hast du vor in der “Programmierung von Computern” Reihe die Funktionsweise von Compilern näher zu beleuchten? Falls nicht, das fände ich sehr interessant.