← Zurück zum BlogCPU-Architektur

Der Befehlszähler erklärt: Das Lesezeichen der CPU

Wie ein Befehlszähler die nächste Instruktion bestimmt, sequentiellen Code durchläuft und durch Sprünge, Verzweigungen, Ausnahmen und Reset umgeleitet wird.

DennyDenny
8 Min. Lesezeit
Chalkboard feature illustration for “Der Befehlszähler erklärt: Das Lesezeichen der CPU”

Kurzfassung: Der Befehlszähler (Program Counter, PC), in manchen Architekturen auch Befehlszeiger genannt, bestimmt die nächste zu holende Instruktion. Bei sequentieller Ausführung wird er um die decodierte Instruktionslänge weitergeschaltet; Verzweigungen, Sprünge, Ausnahmen, Interrupts und Reset wählen einen anderen Wert für den nächsten PC.

Der Befehlszähler ist ein spezialisiertes Architekturzustandselement im Zentrum des Befehlsabrufs. In einer Lehr-CPU kann er ein buchstäbliches Register sein, das eine Adresse für den Instruktionsspeicher ausgibt. In einem modernen Prozessor machen Vorhersage, Warteschlangen, Caches, Adressübersetzung und spekulativer Abruf die Implementierung verteilter, doch die Architektur definiert weiterhin eine Adresse der nächsten Instruktion.

Dieser Beitrag erläutert, was der PC speichert, wie er inkrementiert wird, wie Verzweigungen und Sprünge ihn umleiten und wie man ihn aus einem Register, einem Addierer und einem Multiplexer verdrahtet. Jede Komponente verweist zurück auf eine funktionierende Schaltung, die Sie in der Vorlage Sequential Instruction Executor ausprobieren können.

Was ist der Befehlszähler?

Der Befehlszähler ist ein Register mit genau einer Aufgabe: auf die nächste Instruktion im Speicher zu zeigen. Er ist kein Allzweckregister, das Anwendercode frei lesen und schreiben kann. Es ist ein Spezialregister, das von der Steuerlogik der CPU angesteuert wird.

In der einfachsten Maschine begrenzen seine Breite und die adressierbare Speichereinheit den Codeadressraum:

  • Ein 8-Bit-PC kann 28=2562^8 = 256 adressierbare Stellen auswählen.
  • Ein 16-Bit-PC kann 216=65,5362^{16} = 65{,}536 Stellen auswählen.
  • Ein 32-Bit-PC hat 2322^{32} mögliche Bitmuster.
  • Ein 64-Bit-PC hat 2642^{64} mögliche Bitmuster.

Diese Stellen sind nur in einer byteadressierten Architektur Bytes. Reale Prozessoren können weniger Adressbits implementieren als die nominelle Registerbreite, virtuelle Instruktionsadressen in physischen Speicher übersetzen und Regeln für Ausrichtung oder kanonische Adressen auferlegen.

Die Komponente PROGRAM_COUNTER_8BIT von DigiSim implementiert den 8-Bit-Fall, der groß genug ist, um im Simulator echte Programme auszuführen, und zugleich klein genug, um jedes Bit einzeln zu untersuchen.

Der PC steht am Anfang der Abrufstufe

Die Abrufstufe des Fetch-Decode-Execute-Zyklus liest vier Signale der Reihe nach:

  1. Der PC legt seinen aktuellen Wert auf den Adressbus.
  2. Der Speicher liefert das Instruktionswort an dieser Adresse zurück.
  3. Das Instruktionswort wird in das Instruktionsregister übernommen.
  4. Der PC wird aktualisiert, sodass der nächste Abruf die nächste Instruktion liest.

Die Schritte 1–3 werden in der umfassenderen Behandlung des Abrufs behandelt. Um Schritt 4 geht es in diesem Beitrag.

Warum muss der PC automatisch inkrementieren?

Bliebe der PC stehen, würde die CPU dieselbe Instruktion endlos abrufen. Die Hardware verdrahtet daher ein automatisches Inkrement mit dem PC, sodass auf jeden Abrufzyklus standardmäßig ein Schritt vorwärts folgt.

Das Inkrement wird von einem dedizierten Addierer berechnet – typischerweise einem kleinen Ripple-Carry-Addierer wie dem in Binäre Addition meistern ausführlich beschriebenen –, dessen Eingänge der aktuelle PC und ein konstanter Offset sind. Der Ausgang dieses Addierers wird auf den Dateneingang des PC zurückgeführt, und der PC übernimmt ihn an der nächsten Taktflanke.

PC + 1 gegenüber PC + 4: Warum die Instruktionsbreite zählt

Die zum PC addierte Konstante ist die Breite einer Instruktion in adressierbaren Einheiten, nicht immer der buchstäbliche Wert 1.

ArchitekturInstruktionsbreiteInkrementGrund
DigiSim-Spielzeug-CPU mit 8 Bit1 Byte (1 Wort)PC + 1Jede Adresse enthält eine vollständige Instruktion
MIPS; RISC-V ohne komprimierte Instruktionen4 BytePC + 4Der Speicher ist byteadressiert; diese Codierungen sind 4 Byte breit
ARM (A32)4 BytePC + 4Derselbe Grund
RISC-V mit C-Erweiterung; Arm Thumb/Thumb-22 oder 4 BytePC + decodierte LängeBeide enthalten 16-Bit-Codierungen; auch 32-Bit-Codierungen können vorkommen
x861–15 BytePC + (decodierte Länge)Instruktionen variabler Länge; der Decoder meldet die Länge

Das architektonische Prinzip bleibt gleich: Die sequentielle nächste Adresse folgt der Codierung der aktuellen Instruktion. Eine ISA mit fester Breite kann ein konstantes Inkrement verwenden. Eine ISA mit variabler Breite muss die Länge der aktuellen Instruktion bestimmen, selbst wenn ein leistungsstarkes Frontend über diese Grenze hinaus vorhersagt und abruft, bevor das Decodieren abgeschlossen ist.

Boolesche Beschreibung

Für eine Architektur mit fester Instruktionsbreite WW lautet der Standardwert für den nächsten PC:

PCn+1=PCn+W\text{PC}_{n+1} = \text{PC}_{n} + W

Im DigiSim-8-Bit-Fall gilt W=1W = 1, daher ist das Inkrement einfach ein +1-Addierer.

Wie überschreiben Verzweigungen und Sprünge den PC?

Sequentielle Ausführung ist der langweilige Fall. Reale Programme benötigen Schleifen, Bedingungen, Funktionsaufrufe und Rückkehr – all das verlangt, dass der PC an eine andere Stelle als die nächste sequentielle Adresse springt.

Zwei verbreitete Klassen von Kontrollübertragungen veranschaulichen die Änderung:

  • Sprung (unbedingt): Ersetzt den PC unabhängig von jeder Bedingung durch eine Zieladresse.
  • Verzweigung (bedingt): Wählt ein Ziel nur, wenn die von der ISA festgelegte Bedingung wahr ist; andernfalls wird die sequentielle Adresse gewählt. Manche ISAs prüfen Statusflags wie Null, Übertrag, Vorzeichen oder Überlauf. Andere, darunter das Basis-RISC-V, vergleichen Register direkt und besitzen kein Register für arithmetische Bedingungscodes. Der Leitfaden zu CPU-Flags erklärt den flagbasierten Entwurf.

Beide Operationen laufen darauf hinaus, einen nichtsequentiellen Wert in den PC zu schreiben.

Woher kommt die Zieladresse?

Das Ziel wird auf drei Arten codiert:

  1. Unmittelbar (PC-relativ): Das Instruktionswort enthält einen Offset. Der nächste PC ist PC + offset. RISC-V BEQ, ARM B und kurze x86-Sprünge verwenden alle diese Form. Ihr Vorteil ist positionsunabhängiger Code.
  2. Direkt oder regionsrelativ: Die Instruktion liefert Zielbits, die mit einer festen Basis oder hohen Bits des aktuellen PC kombiniert werden können. Das klassische MIPS-J ist ein vertrautes Beispiel. Eine vollständige absolute Adresse passt selten in eine normale Instruktion fester Breite.
  3. Registerindirekt: Das Ziel liegt in einem Allzweckregister oder im ALU-Ausgang. RISC-V JALR, ARM BX und x86 JMP rax sind Beispiele. Diese Form unterstützt Funktionszeiger, virtuelle Disposition und berechnetes goto.

In einem kleinen Datenpfad mit einem einzigen Taktzyklus können diese Kandidaten einen Multiplexer speisen. Fortgeschrittenere Implementierungen können Ziele in mehreren Einheiten berechnen, vorhersagen und in Warteschlangen einreihen, bevor der gewählte architektonische Pfad in den Ruhestand tritt.

Implementierung: Register + Inkrement + Mux

Die Hardwareschaltung des PC gehört zu den klarsten Datenpfaden in einer CPU:

                        ┌──────────────┐
       PC current ─────▶│  Adder (+W)  │──── PC + W ──┐
                        └──────────────┘              │
                                                      ▼
                                              ┌───────────────┐
                                              │ MUX (2:1)     │
                target_addr ─────────────────▶│  sel = take?  │──── next_PC
                                              └───────────────┘
                                                      │
                                                      ▼
                                              ┌───────────────┐
                  clock ─────────────────────▶│   PC register │──── current_PC
                                              └───────────────┘

Drei Bausteine:

  1. Ein Register, das die aktuelle Adresse hält. Im Lehrdatenpfad ist dies ein 8-Bit-Register, das aus flankengesteuerten Speicherelementen auf die Breite des Adressbusses erweitert wurde.
  2. Ein Addierer, bei dem ein Eingang mit PC und der andere mit der konstanten Breite WW verdrahtet ist.
  3. Ein 2:1-Multiplexer mit einem Eingang vom Addierer (standardmäßiger sequentieller Pfad) und einem vom Verzweigungsziel (Pfad bei genommener Verzweigung). Seine Auswahllinie wird von der Steuereinheit angesteuert und ist aktiv, wenn die aktuelle Instruktion ein Sprung oder eine Verzweigung ist, deren Bedingung wahr wird. Multiplexer sind Thema von Der Verkehrsregler für Daten.

An jeder Taktflanke übernimmt der PC den Wert, den der Mux gerade auswählt. Dieses eine Speicherelement ist der gesamte Mechanismus, der „weitergehen“ von „verzweigen“ unterscheidet.

Optionale Eingänge des Mux

Reale Entwürfe ergänzen denselben Mux um weitere Quellen:

QuelleWann ausgewählt
PC + WStandard – sequentielle Ausführung
PC + offset (PC-relativ)Bedingte Verzweigungen, kurze Sprünge
Direktes Ziel / VektorDirekte Sprünge, Reset oder architekturdefinierte Kontrollübertragungen
Register / ALU-AusgangIndirekte Sprünge, Funktionsrückgaben, Switch-Verteilung
Reset- / BootvektorNach dem Einschalten oder externen Reset
Ausnahme- / InterruptvektorNach einer Trap

Die Auswahllinie verbreitert sich von 1 Bit auf 3 Bit, die strukturelle Idee bleibt jedoch unverändert.

Reset-Verhalten: Wo startet der PC?

Wenn die CPU eingeschaltet oder zurückgesetzt wird, muss der PC eine bekannte Adresse halten – andernfalls beginnt die Ausführung zufällig.

Es gibt zwei verbreitete Konventionen:

  • PC = 0: Die Ausführung beginnt an Adresse 0x0000…0. Die DigiSim-Lehr-CPU verwendet diese einfache Konvention; die erste Instruktion liegt an Position null.
  • Bootvektor: Eine feste Adresse ungleich null. Bei Kaltreset startet x86 an der physischen Adresse 0xFFFFFFF0 (am oberen Ende des 4-GB-Adressraums, nahe dem BIOS-ROM). ARM-Cortex-M-Kerne lesen den anfänglichen PC aus einem Resetvektor, der an Adresse 0x00000004 gespeichert ist. Manche Chips der 6502-Familie lesen den Resetvektor aus 0xFFFC/0xFFFD.

In Hardware ist „Reset“ eine Leitung zum asynchronen Clear- (oder synchronen Load-) Eingang des PC. Ist sie aktiv, wird der PC unabhängig vom Mux auf den Bootwert gezwungen.

Durchgerechnetes Beispiel: Eine Schleife aus fünf Instruktionen

Betrachten Sie das folgende Pseudoprogramm in einer 8-Bit-CPU mit W=1W = 1:

0x00: LDI R0, 5      ; load 5 into R0
0x01: LDI R1, 0      ; load 0 into R1
0x02: ADD R1, R0     ; R1 = R1 + R0
0x03: DEC R0         ; R0 = R0 - 1, sets Z flag if R0 hits 0
0x04: BNZ 0x02       ; if Z == 0, branch back to 0x02
0x05: HLT

Durchlaufen wir die PC-Werte:

ZyklusPC beim AbrufInstruktionNach AbrufVerzweigung genommen?Nächster PC
10x00LDI R0, 5PC + 1 = 0x01k. A.0x01
20x01LDI R1, 0PC + 1 = 0x02k. A.0x02
30x02ADD R1, R0PC + 1 = 0x03k. A.0x03
40x03DEC R0 (R0=4, Z=0)PC + 1 = 0x04k. A.0x04
50x04BNZ 0x02PC + 1 = 0x05; target = 0x02ja (Z=0)0x02
60x02ADD R1, R0………

In Zyklus 5 wählt der Lehrdatenpfad das Verzweigungsziel (0x02) statt der sequentiellen nächsten Adresse (0x05), weil das Z-Flag nicht gesetzt ist. Reale Kontrollflussmechanismen sind aufwendiger, doch das architektonische Ergebnis ist weiterhin eine Wahl zwischen dem Fall-through-Pfad und einem Kontrollübertragungsziel.

Häufige Fallstricke

  • Off-by-one bei PC + W. Ein häufiger Simulatorfehler besteht darin, PC + 1 für eine Architektur zu verwenden, deren Instruktionen 4 Byte breit sind. Die CPU ruft dann das zweite Byte jeder Instruktion als Beginn der nächsten Instruktion ab und verhält sich, als wäre jedes Programm Zufallsdaten.
  • Den PC aktualisieren, bevor der Abruf den Speicher liest. Der PC muss die aktuelle Adresse halten, während der Speicher gelesen wird. Aktualisieren Sie ihn an der Taktflanke, nicht kombinatorisch.
  • Den Reset vergessen. Ein PC ohne Resetleitung startet mit dem Wert, auf den die Flipflops beim Einschalten zufällig geraten. Verbinden Sie stets den Reset.
  • Zu einer ungültigen Ausrichtung verzweigen. Die erforderliche Instruktionsausrichtung hängt von ISA und Erweiterungen ab. Eine Maschine mit ausschließlich auf 32 Bit ausgerichteten Instruktionen weist ein Ziel wie 0x1003 zurück; eine ISA mit 16-Bit-Codierungen kann mehr Ziele erlauben. Erzwingen Sie die Architekturregel, statt eine universelle Annahme fest zu verdrahten.

In DigiSim aufbauen

Öffnen Sie die Vorlage Sequential Instruction Executor. Dort sehen Sie:

  • Einen PROGRAM_COUNTER_8BIT-Block am Anfang des Abrufpfads.
  • Einen Addierer, der fest verdrahtet bei jedem Taktzyklus 1 addiert.
  • Einen Mux, der zwischen PC + 1 und einer von der Steuereinheit bereitgestellten Zieladresse wählt.
  • Einen Reset-Schalter, der den PC auf 0x00 zwingt.

Tasten Sie den Takt manuell und beobachten Sie, wie der PC weiterläuft. Laden Sie dann ein Programm mit einer Verzweigungsinstruktion und beobachten Sie den Moment, in dem der Mux von „sequentiell“ auf „Ziel“ umschaltet – dieses eine Bit ist Kontrollfluss in seiner reinsten Form.

Der PC im größeren Zusammenhang

Der Befehlszähler ist ein Knoten im größeren CPU-Datenfluss. Die von ihm bestimmte Instruktion wird für die Verarbeitung im Instruktionsregister und beim Decodieren übernommen. Arithmetische Ergebnisse können die CPU-Flags aktualisieren, die ein flagbasierter Verzweigungsentwurf auswertet.

Lesen Sie die Fallstudie zu Fetch-Decode-Execute für den vollständigen Ablauf, laden Sie anschließend die Vorlage Sequential Instruction Executor und führen Sie eine Verzweigungsinstruktion im Einzelschritt aus. Den PC zum ersten Mal nichtsequentiell springen zu sehen, ist der Moment, in dem die Abstraktion keine Zeichnung mehr ist, sondern eine Schaltung.

Machen Sie aus der Idee eine funktionierende Schaltung.