Assignment Chef icon Assignment Chef
All German tutorials

Programming lesson

RISC-V-Pipeline-Simulator in Python: Schritt-für-Schritt-Tutorial für CDA 4102/5155

Lerne, wie du einen cycle-genauen Simulator für eine 5-stufige RISC-V-Pipeline in Python baust. Dieses Tutorial erklärt die Kernkonzepte anhand des Projekts 2 aus CDA 4102/5155 und unterstützt dich mit praktischen Tipps und Trendbeispielen.

RISC-V Pipeline Simulator CDA 4102 Projekt 2 CDA 5155 Pipeline RISC-V Simulator Python Cycle-genaue Simulation Scoreboard Algorithmus Pipeline Hazards Out-of-Order Issue RISC-V Befehlsformate Pre-Issue Queue Structural Hazard Register File Synchronisation RISC-V Assembler Vsim Simulator Prozessorarchitektur Tutorial KI Chip Pipeline

Einführung: Warum ein Pipeline-Simulator?

Stell dir vor, du entwickelst einen RISC-V-Pipeline-Simulator – genau wie in CDA 4102 / CDA 5155 gefordert. Dieses Projekt ist der Schlüssel, um zu verstehen, wie moderne Prozessoren Befehle parallel abarbeiten. Egal ob du später an KI-Chips, Gaming-Konsolen oder Smartphone-Apps arbeitest – Pipeline-Konzepte sind überall. Aktuell (Juni 2026) nutzen selbst die neuesten RISC-V-Chips in KI-Beschleunigern ähnliche Techniken.

Projektübersicht: Was wird verlangt?

Dein Simulator (Vsim) lädt eine RISC-V-Assembly-Datei und simuliert Zyklus für Zyklus die Ausführung. Dabei werden Register, Warteschlangen und Speicherinhalte ausgegeben. Der Fokus liegt auf einer 5-stufigen Pipeline mit Scoreboard und Out-of-Order-Issue. Keine Exceptions oder Interrupts – nur saubere Testfälle.

Pipeline-Struktur (vereinfacht)

  • IF (Instruction Fetch/Decode): Holt bis zu 2 Befehle pro Takt, dekodiert sie und legt sie in die Pre-Issue-Queue.
  • Issue (Scoreboard): Liest Operanden aus dem Registerfile und gibt Befehle out-of-order an die ALU-Warteschlangen weiter.
  • ALU1 (Load/Store), ALU2 (Arithmetik), ALU3 (Logik): Führen die eigentlichen Operationen aus.
  • WB (Write Back): Schreibt Ergebnisse zurück ins Registerfile.

Zwischen den Stufen liegen Warteschlangen (Queues): Pre-Issue (4 Einträge), Pre-ALU1 (2), Pre-ALU2 (1), Pre-ALU3 (1), Post-ALU1 (1) usw. Diese begrenzen die Parallelität und verursachen Strukturelle Hazards.

Trend-Beispiel: Warum das wie ein Gaming-Turnier ist

Stell dir vor, du organisierst ein E-Sports-Turnier (z. B. League of Legends). Die IF-Stufe ist der Spielleiter, der die nächsten Matches (Befehle) auswählt. Die Issue-Stufe prüft, ob ein Team (Register) bereit ist – ähnlich wie ein Scoreboard, das wartet, bis alle Spieler online sind. Die ALUs sind die Spieler, die ihre Aktionen ausführen: einer kämpft (ALU1), einer farmt (ALU2), einer bufft (ALU3). Wenn ein Spieler noch beschäftigt ist, müssen andere warten – genau wie Strukturelle Hazards. Das Write-Back ist der Siegesscreen, der die Ergebnisse anzeigt. Ohne eine gute Pipeline würden alle nacheinander spielen – stundenlang. Mit Pipeline laufen mehrere Aktionen gleichzeitig ab, solange keine Abhängigkeiten stören.

Schritt 1: Grundgerüst des Simulators

Wir nutzen Python, weil es schnell zu prototypen ist. Der Simulator besteht aus Klassen für die Pipeline-Stufen, Warteschlangen und Register. Ein zentraler Clock-Zyklus steuert den Ablauf.

class Pipeline:
    def __init__(self):
        self.pc = 0x1000
        self.regs = [0]*32
        self.memory = {}
        self.pre_issue = Queue(maxsize=4)
        self.pre_alu1 = Queue(maxsize=2)
        self.pre_alu2 = Queue(maxsize=1)
        self.pre_alu3 = Queue(maxsize=1)
        # ... weitere Queues
        self.cycles = 0

Schritt 2: Instruction Fetch/Decode (IF)

Die IF-Stufe muss pro Takt bis zu 2 Befehle aus dem Speicher holen. Dabei wird geprüft, ob die Pre-Issue-Queue am Ende des letzten Zyklus freie Plätze hatte. Wichtig: Bei einem Branch (z. B. beq) wird der PC sofort aktualisiert, wenn die Register bereit sind. Sonst wird die Stufe gestallt.

Beispiel: Wenn du zwei Befehle holst und der erste ein Branch ist, wird der zweite verworfen. Ist der Branch der zweite, werden beide dekodiert und der Branch wird nicht in die Queue geschrieben.

Typische Fehlerquelle: Register-Synchronisation

Der gelesene Registerwert ist immer der vom Ende des vorherigen Zyklus. Das bedeutet: Wenn WB im selben Zyklus einen Wert schreibt, kann IF ihn nicht sofort sehen – das führt zu Daten-Hazards, die das Scoreboard auflösen muss.

Schritt 3: Issue-Stufe mit Scoreboard

Die Issue-Einheit sucht in der Pre-Issue-Queue nach bereiten Befehlen. Sie kann bis zu 3 Befehle pro Takt out-of-order ausgeben, aber nur einen pro ALU-Typ. Das Scoreboard verfolgt, welche Register gerade geschrieben werden (WAW) oder noch nicht bereit sind (RAW).

Beispiel-Code für Issue-Logik:

def issue(self):
    for i in range(4):
        inst = self.pre_issue.get(i)
        if inst and self.operands_ready(inst):
            if inst.type == 'lw' or inst.type == 'sw':
                if not self.pre_alu1.full():
                    self.pre_alu1.put(inst)
                    self.pre_issue.remove(i)
            elif inst.type in ['add','sub','addi']:
                if not self.pre_alu2.full():
                    self.pre_alu2.put(inst)
                    self.pre_issue.remove(i)
            # ... ähnlich für ALU3

Schritt 4: ALU-Stufen und Write-Back

Jede ALU-Stufe hat eine eigene Warteschlange (Pre-ALU) und eine Post-Queue. Die ALU selbst braucht einen Takt für die Berechnung. Danach geht das Ergebnis in die Post-Queue und dann zu WB. WB schreibt am Ende des Zyklus in das Registerfile – aber erst nachdem alle Leseoperationen in diesem Zyklus abgeschlossen sind.

Speicherzugriffe (Load/Store)

Für lw und sw ist ALU1 zuständig. Der Speicherzugriff erfolgt in der MEM-Stufe (in diesem Projekt in ALU1 integriert). Achte darauf, dass der Speicher erst am Ende des Zyklus aktualisiert wird.

Schritt 5: Ausgabe des Simulations-Trace

Die Ausgabe muss für jeden Zyklus die Inhalte von PC, Registern (alle 32), Pre-Issue-Queue, Pre-ALU-Queues und Post-Queues zeigen. Orientiere dich am Format der sample_simulation.txt. Ein sauberes Format hilft bei der Fehlersuche.

Trend-Beispiel: Wie KI-Chips das nutzen

Im Jahr 2026 setzen RISC-V-Prozessoren in KI-Beschleunigern auf tiefe Pipelines mit Out-of-Order-Execution, um Matrix-Multiplikationen zu optimieren. Genau wie in deinem Simulator müssen sie Hazards vermeiden – nur dass dort tausende Befehle gleichzeitig in der Pipeline sind. Dein Simulator ist die Miniaturversion davon.

Häufige Fallstricke und Tipps

  • Strukturelle Hazards: Prüfe immer, ob die Ziel-Queue am Ende des letzten Zyklus frei war – nicht im aktuellen.
  • Branch-Handling: Wenn ein Branch mitgenommen wird, verwirf den nächsten Befehl nur, wenn der Branch der erste ist. Sonst dekodiere beide normal.
  • Scoreboard-Update: Markiere ein Register erst als „in Benutzung“, wenn der Befehl ausgegeben wurde, und gib es nach WB frei.
  • Testen mit eigenen Beispielen: Erstelle kleine Testfälle (z. B. nur addi x1, x0, 5; add x2, x1, x1) und prüfe die Ausgabe Schritt für Schritt.

Fazit: Dein Weg zur vollen Punktzahl

Mit diesem Tutorial hast du ein solides Grundverständnis für den Aufbau eines RISC-V-Pipeline-Simulators. Der Schlüssel liegt im genauen Beachten der Zyklus-Grenzen und der Queue-Mechanik. Wenn du die Beispiel-Eingabe aus dem Projekt korrekt simulierst, hast du bereits 60% der Punkte sicher. Die restlichen 40% erreichst du durch eigenständige Tests und saubere Implementierung. Viel Erfolg bei deinem CDA 4102 / CDA 5155 Projekt 2!