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.
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 adressierbare Stellen auswählen.
- Ein 16-Bit-PC kann Stellen auswählen.
- Ein 32-Bit-PC hat mögliche Bitmuster.
- Ein 64-Bit-PC hat 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:
- Der PC legt seinen aktuellen Wert auf den Adressbus.
- Der Speicher liefert das Instruktionswort an dieser Adresse zurück.
- Das Instruktionswort wird in das Instruktionsregister übernommen.
- 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.
| Architektur | Instruktionsbreite | Inkrement | Grund |
|---|---|---|---|
| DigiSim-Spielzeug-CPU mit 8 Bit | 1 Byte (1 Wort) | PC + 1 | Jede Adresse enthält eine vollständige Instruktion |
| MIPS; RISC-V ohne komprimierte Instruktionen | 4 Byte | PC + 4 | Der Speicher ist byteadressiert; diese Codierungen sind 4 Byte breit |
| ARM (A32) | 4 Byte | PC + 4 | Derselbe Grund |
| RISC-V mit C-Erweiterung; Arm Thumb/Thumb-2 | 2 oder 4 Byte | PC + decodierte Länge | Beide enthalten 16-Bit-Codierungen; auch 32-Bit-Codierungen können vorkommen |
| x86 | 1–15 Byte | PC + (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 lautet der Standardwert für den nächsten PC:
Im DigiSim-8-Bit-Fall gilt , 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:
- Unmittelbar (PC-relativ): Das Instruktionswort enthält einen Offset. Der nächste PC ist
PC + offset. RISC-VBEQ, ARMBund kurze x86-Sprünge verwenden alle diese Form. Ihr Vorteil ist positionsunabhängiger Code. - 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-
Jist ein vertrautes Beispiel. Eine vollständige absolute Adresse passt selten in eine normale Instruktion fester Breite. - Registerindirekt: Das Ziel liegt in einem Allzweckregister oder im ALU-Ausgang. RISC-V
JALR, ARMBXund x86JMP raxsind Beispiele. Diese Form unterstützt Funktionszeiger, virtuelle Disposition und berechnetesgoto.
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:
- 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.
- Ein Addierer, bei dem ein Eingang mit PC und der andere mit der konstanten Breite verdrahtet ist.
- 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:
| Quelle | Wann ausgewählt |
|---|---|
| PC + W | Standard – sequentielle Ausführung |
| PC + offset (PC-relativ) | Bedingte Verzweigungen, kurze Sprünge |
| Direktes Ziel / Vektor | Direkte Sprünge, Reset oder architekturdefinierte Kontrollübertragungen |
| Register / ALU-Ausgang | Indirekte Sprünge, Funktionsrückgaben, Switch-Verteilung |
| Reset- / Bootvektor | Nach dem Einschalten oder externen Reset |
| Ausnahme- / Interruptvektor | Nach 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 Adresse0x00000004gespeichert ist. Manche Chips der 6502-Familie lesen den Resetvektor aus0xFFFC/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 :
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:
| Zyklus | PC beim Abruf | Instruktion | Nach Abruf | Verzweigung genommen? | Nächster PC |
|---|---|---|---|---|---|
| 1 | 0x00 | LDI R0, 5 | PC + 1 = 0x01 | k. A. | 0x01 |
| 2 | 0x01 | LDI R1, 0 | PC + 1 = 0x02 | k. A. | 0x02 |
| 3 | 0x02 | ADD R1, R0 | PC + 1 = 0x03 | k. A. | 0x03 |
| 4 | 0x03 | DEC R0 (R0=4, Z=0) | PC + 1 = 0x04 | k. A. | 0x04 |
| 5 | 0x04 | BNZ 0x02 | PC + 1 = 0x05; target = 0x02 | ja (Z=0) | 0x02 |
| 6 | 0x02 | ADD 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 + 1fü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
0x1003zurü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
1addiert. - Einen Mux, der zwischen
PC + 1und einer von der Steuereinheit bereitgestellten Zieladresse wählt. - Einen Reset-Schalter, der den PC auf
0x00zwingt.
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.



