Instruction Encoding heißt: Wie wird aus add x5, x6, x7 eine 32-Bit-Zahl, die der Prozessor direkt ausführen kann? RISC-V hat dafür sechs feste Schablonen. Das ganze Kapitel erklärt diese Schablonen und warum sie genau so aussehen.
add x5, x6, x7 → 0x007302B3. Jede Farbe ist ein Feld. Die gleichen Farben gelten auf der ganzen Seite.
Jeder Befehl ist genau 32 Bit lang und in feste Felder aufgeteilt: ganz rechts der opcode, daneben die Register, dazu Zusatzinfos (funct3/funct7) und eine Konstante.
Es gibt sechs Formate: R, I, S, B, U und J. Sie unterscheiden sich fast nur darin, wo die Konstante steckt. Die Register und der Opcode bleiben immer an ihrem Platz.
Das ganze Kapitel in einem Bild. Tippe auf rs1 oder opcode, dann siehst du, dass sich nur die gelben Teile (die Konstante) verschieben.
add x5, x6, x7addi x5, x6, -10, außerdem Laden mit lwsw x5, 12(x6), die Konstante ist zweigeteiltbeq x5, x6, +16lui x5, 0x12345jal x1, +2048opcode: die Befehlsgruppe (Bit 6:0)funct3 / funct7: die genaue Operation in der Gruppe, z. B. add oder subrd: Zielregister, dort landet das Ergebnisrs1, rs2: Quellregister, die gelesen werdenimm: Konstante, die direkt im Befehl stehtWarum lohnt es sich, dass rs1, rs2 und opcode in jedem Format an derselben Stelle sitzen? Denk an den Decoder aus Modul 02 (CISC gegen RISC, Iron Law).
Die Registerdatei kann schon lesen, während der Opcode noch dekodiert wird. Die Bits 19:15 und 24:20 hängen direkt an den Leseports, ohne Multiplexer. Dadurch ist der Decode-Schritt schneller und der Takt kürzer. Der Preis dafür sind die zerstückelten Konstanten. Mehr dazu unten bei „Warum das Ganze so aussieht“.
Jeder Basisbefehl ist genau 4 Byte lang. Der nächste Befehl liegt deshalb immer bei PC + 4. Die Hardware muss nie erst herausfinden, wie lang ein Befehl ist (bei x86 muss sie das).
Die unteren 7 Bit (6:0) sind der Opcode. Daran erkennt die Hardware, welche der sechs Schablonen gilt und wie der Rest zu lesen ist.
rs1, rs2 und rd liegen in jedem Format an denselben Bitpositionen. Die Registerdatei kann also schon lesen, bevor der Befehl fertig dekodiert ist.
Damit die Register fest bleiben, werden die Immediates (Konstanten im Befehl) auf die übrigen Bits verteilt, teils in seltsamer Reihenfolge. Das sortiert der Assembler. Die Hardware kostet es nur Leitungen und keine Logik.
Bei 32-Bit-Befehlen sind Bit 1:0 immer 11. Die eigentliche Klasse steht in Bit 6:2. Für die Klausur reichen die zehn wichtigen Opcodes:
| Opcode [6:0] | Name | Format | Befehle |
|---|
Auf Folie 33 steht die volle Tabelle (Floating Point, Atomics, custom, reserved). Die Logik dahinter: Bit 6:5 wählt die Spalte, Bit 4:2 die Zeile. Auswendig lernen musst du die nicht.
Das Bild aus dem Vorspann als Tabelle. Alles in Gelb ist Immediate. Gleiche Gelbtöne bedeuten gleiche Immediate-Bits.
| Format | Wofür | Register | Immediate | Bereich |
|---|---|---|---|---|
| R | Rechnen mit zwei Registern: add sub and or xor sll slt | rd, rs1, rs2 | keins | – |
| I | Rechnen mit Konstante, Laden, jalr: addi lw lb jalr | rd, rs1 | 12 Bit | −2048 … +2047 |
| S | Speichern: sw sh sb | rs1 (Basis), rs2 (Daten) | 12 Bit, geteilt | −2048 … +2047 |
| B | Bedingt springen: beq bne blt bge bltu bgeu | rs1, rs2 | 13 Bit, Bit 0 = 0 | −4096 … +4094 |
| U | Obere 20 Bit setzen: lui auipc | rd | 20 Bit (oben) | imm ≪ 12 |
| J | Unbedingt springen und merken: jal | rd | 21 Bit, Bit 0 = 0 | ± 1 MiB |
Merkhilfe: S ist I mit einem zweiten Quellregister statt rd. B ist S mit umsortierten Bits. J ist U mit umsortierten Bits. Eigentlich gibt es also nur R, I, S, U und zwei Varianten davon.
Guntoro begründet jede Designentscheidung mit der Hardware. Das ist der rote Faden. Wenn eine Prüfungsfrage mit „Why“ anfängt, steht die Antwort meistens hier.
Bit 19:15 hängt direkt an Leseport 1 der Registerdatei, Bit 24:20 an Leseport 2. Kein Multiplexer heißt keine Verzögerung („zero mux delay“). Bei CISC muss der Decoder erst Länge und Adressierungsart verstehen.
In I, S, B, U und J steht in Bit 31 das höchste Immediate-Bit. Für die Sign-Extension reicht deshalb ein einziger Draht von Bit 31, ganz ohne Auswahl-Logik.
imm[10:5] steht in I, S, B und J immer in Bit 30:25. Die Leitungen zum Immediate-Generator werden dadurch geteilt (im Bild oben derselbe Gelbton).
B und J sehen chaotisch aus, weil die Register fest bleiben sollen. Das Umsortieren ist reine Verdrahtung und kostet keine Gatterlaufzeit.
Sprungziele sind immer gerade (Befehle sind mindestens 2-Byte-aligned). Deshalb fehlt Bit 0 bei B und J, die Hardware hängt eine 0 an. Mit denselben Bits erreicht man so den doppelten Bereich.
Fetch heißt immer PC + 4. Der Decode liest Opcode und Register parallel. Genau das will RISC: kurzer Takt, CPI ≈ 1 (Iron Law aus Modul 2).
Oben steht, wo die Bits im Befehl liegen, unten die Konstante, die daraus gebaut wird. Die Farben zeigen, welches Stück wohin wandert.
imm[12|10:5] … imm[4:1|11]imm[20|10:1|11|19:12]Die Werte stehen direkt in den Feldern. Lies von rechts nach links: Opcode, dann rd, funct3, rs1 und so weiter.
Ein Befehl kann keine 32-Bit-Konstante enthalten, weil der Platz fehlt. Deshalb gibt es feste Paare: U-Type liefert die oberen 20 Bit, ein I-Type die unteren 12.
lui + addiBeliebige Konstante: lui t0, 0x12345 ergibt t0 = 0x12345000, danach addi t0, t0, 0x678.
auipc + lw/addird ← PC + (imm ≪ 12). Die Adresse wird relativ zum PC berechnet, damit läuft das Programm an jeder Ladeadresse (position-independent code, ±2 GB).
jalr (I-Type!)rd ← PC+4; PC ← (rs1 + imm) & ~1. Daraus wird ret = jalr x0, x1, 0, außerdem Funktionszeiger und mit lui davor ein absoluter Sprung.
jal (J-Type)rd ← PC+4; PC ← PC + offset. Mit rd = x1 (ra) ist es ein Funktionsaufruf, mit rd = x0 ein einfacher Sprung.
Stolperfalle aus der Praxis: Weil addi sign-extended, muss man bei lui 1 dazuzählen, wenn Bit 11 der unteren 12 Bit gesetzt ist. Das kommt in Modul 03 Kapitel 7 (Pseudo-Instructions) wieder.
x6 → 00110).| funct3 | 000 | 001 | 010 | 011 | 100 | 101 | 110 | 111 |
|---|---|---|---|---|---|---|---|---|
| OP / OP-IMM | add/sub | sll | slt | sltu | xor | srl/sra | or | and |
| BRANCH | beq | bne | – | – | blt | bge | bltu | bgeu |
| LOAD | lb | lh | lw | – | lbu | lhu | – | – |
| STORE | sb | sh | sw | – | – | – | – | – |
funct7 0100000 macht aus add ein sub und aus srl ein sra. Sonst ist es 0000000.
Hier ohne Lösung, damit du sie danach kalt rechnen kannst. Mit dem Kochrezept oben sind alle vier machbar.
0x003102B3: Opcode, Format, alle Felder und der Assembler-Befehl.
0x00C32283: Opcode, Format, Felder, 12-Bit-Immediate und der Assembler-Befehl.
sw x5, 8(x10) nach Binär und Hex.
bne x5, x6, LOOP steht bei 0x00001008, LOOP bei 0x00000FC0. Wie groß ist der Offset und wie sieht das 13-Bit-Immediate aus?
Folie 48 beq x5, x6, +16: Die Binärzahl auf der Folie stimmt, aber das Hex dazu nicht. Richtig ist 0x00628863 statt 0x00620863.
Folie 53 jal x1, +2048: 2048 = 2¹¹, also ist imm[11] = 1 und imm[10:1] = 0. Die Folie setzt imm[10], das wäre +1024. Richtig ist 0x001000EF statt 0x400000EF.
Folie 37 sagt, imm[4:1] liege in I, S, B und J immer in Bit 11:8. Das stimmt nur für S und B. In I und J liegt es in Bit 24:21 (siehe das Bild mit den sechs Schablonen).
Kann auch ein Fehler im PDF-Text sein. In der Vorlesung kurz nachfragen lohnt sich, das sind dankbare Prüfungsfragen.