The description, in one figure
Your core was not written for MIPS. It was written once, against a of what an ISA is. The MIPS package is one instance of that shape:
What a CPU generator needs to know about MIPS: the five parts of the description
Left: what the package states, as data. Right: what the engine builds from it, for any ISA.
What to notice
Every left box is something this book taught. Chapter 2 is the first box.
Chapters 3 to 8 are the 63 µops and their slots. Chapter 1’s formats and
the funct rules are the third box. The %hi/%lo, branch and jump
arithmetic of chapters 3 and 5 are the fourth. The right side is what you
never had to write for MIPS: the engine derived it.
Read the left column as five decisions.
- Register classes.
ris 32 registers with$0constant.hiandloare two classes of one register each. That is whymultcan have two destinations andmfhireads one without an index (§7.1). - µops and slots. One µop per instruction, 63 in all. They are numbered so that each unit’s µops form one run of ids. Each µop says which slot reads which field. A shift’s value is
rt, an add’s isrs(§3.3). - The encoding table. 26 rows keyed on the opcode. Four of them,
SPECIAL,REGIMM,SPECIAL2andSPECIAL3, refine onfunct,rtorsa(§1.3, §8.1). A word matches exactly one row or none. - Immediate rules. Five small functions that place bits and fill the sign.
s16andlui(§3.2). The branch offset times four and the jump index times four (§5.1). Theext/insbit-field pair (§8.2). - Unit bodies. What
alu,mem,controlandmuldivcompute, in hardware code. The delay slot’s link valuepc + 8is written here.
What the engine did with it, and what it does not read yet
The generator read the package and built the machine of §1.1. A decoder with one guard per row. A rename table and a physical file for each of the three classes. Four reservation stations, a store buffer, and the rollback. Then the compile tool built all seventeen C programs for MIPS, and the core ran them. 34 of 34 runs, at -O0 and -O2, matched their host builds on the first try. No engine bug surfaced.
One feature is written but not read. Every branch and jump carries
delay_slot, and the engine ignores it. Until it reads it, every slot must hold a nop (§5.4). The compile tool refuses any other word there. The
design for reading it exists, and the GoDeeper below sketches it.
The whole table
Here are all 63, grouped as the book met them. Click a group to see only its rows, and a row for its encoding.
Every MIPS32 instruction your core runs
Which chapter taught each group, and which rows share an opcode?
| instruction | format | code | operands | meaning | unit | taught in |
|---|---|---|---|---|---|---|
| ▸ add | R | op 0 · funct 32 | rd, rs, rt | rd = rs + rt; traps on signed overflow | alu | §3.1 |
| ▸ addu | R | op 0 · funct 33 | rd, rs, rt | rd = rs + rt; wraps, never traps (what C uses) | alu | §3.1 |
| ▸ sub | R | op 0 · funct 34 | rd, rs, rt | rd = rs − rt; traps on signed overflow | alu | §3.1 |
| ▸ subu | R | op 0 · funct 35 | rd, rs, rt | rd = rs − rt; wraps, never traps | alu | §3.1 |
| ▸ and | R | op 0 · funct 36 | rd, rs, rt | rd = rs AND rt, bit by bit | alu | §3.3 |
| ▸ or | R | op 0 · funct 37 | rd, rs, rt | rd = rs OR rt, bit by bit (move is or rd,rs,$zero) | alu | §3.3 |
| ▸ xor | R | op 0 · funct 38 | rd, rs, rt | rd = rs XOR rt, bit by bit | alu | §3.3 |
| ▸ nor | R | op 0 · funct 39 | rd, rs, rt | rd = NOT (rs OR rt); not rd,rs is nor rd,rs,$zero | alu | §3.3 |
| ▸ slt | R | op 0 · funct 42 | rd, rs, rt | rd = 1 if rs < rt (signed), else 0 | alu | §3.4 |
| ▸ sltu | R | op 0 · funct 43 | rd, rs, rt | rd = 1 if rs < rt (unsigned), else 0 | alu | §3.4 |
| ▸ addi | I | op 8 | rt, rs, imm | rt = rs + sign-extended imm; traps on overflow | alu | §3.2 |
| ▸ addiu | I | op 9 | rt, rs, imm | rt = rs + sign-extended imm; wraps (what C uses) | alu | §3.2 |
| ▸ slti | I | op 10 | rt, rs, imm | rt = 1 if rs < sign-extended imm (signed) | alu | §3.4 |
| ▸ sltiu | I | op 11 | rt, rs, imm | rt = 1 if rs < sign-extended imm, compared unsigned | alu | §3.4 |
| ▸ andi | I | op 12 | rt, rs, imm | rt = rs AND zero-extended imm | alu | §3.3 |
| ▸ ori | I | op 13 | rt, rs, imm | rt = rs OR zero-extended imm | alu | §3.3 |
| ▸ xori | I | op 14 | rt, rs, imm | rt = rs XOR zero-extended imm | alu | §3.3 |
| ▸ lui | I | op 15 | rt, imm | rt = imm << 16 (the top half; low half zero) | alu | §3.2 |
| ▸ sll | R | op 0 · funct 0 | rd, rt, sa | rd = rt << sa; sll $zero,$zero,0 is nop | alu | §3.3 |
| ▸ srl | R | op 0 · funct 2 · rs 0 | rd, rt, sa | rd = rt >> sa, filling with zeros (logical) | alu | §3.3 |
| ▸ sra | R | op 0 · funct 3 | rd, rt, sa | rd = rt >> sa, filling with the sign bit (arithmetic) | alu | §3.3 |
| ▸ rotrr2 | R | op 0 · funct 2 · rs 1 | rd, rt, sa | rd = rt rotated right by sa | alu | §8.2 |
| ▸ sllv | R | op 0 · funct 4 | rd, rt, rs | rd = rt << (rs & 31) | alu | §3.3 |
| ▸ srlv | R | op 0 · funct 6 · sa 0 | rd, rt, rs | rd = rt >> (rs & 31), logical | alu | §3.3 |
| ▸ srav | R | op 0 · funct 7 | rd, rt, rs | rd = rt >> (rs & 31), arithmetic | alu | §3.3 |
| ▸ rotrvr2 | R | op 0 · funct 6 · sa 1 | rd, rt, rs | rd = rt rotated right by (rs & 31) | alu | §8.2 |
| ▸ movz | R | op 0 · funct 10 | rd, rs, rt | if rt == 0 then rd = rs (else rd unchanged) | alu | §8.1 |
| ▸ movn | R | op 0 · funct 11 | rd, rs, rt | if rt != 0 then rd = rs (else rd unchanged) | alu | §8.1 |
| ▸ clz | R | op 28 · funct 32 | rd, rs | rd = number of leading zero bits in rs (32 for zero) | alu | §8.2 |
| ▸ clo | R | op 28 · funct 33 | rd, rs | rd = number of leading one bits in rs | alu | §8.2 |
| ▸ sebr2 | R | op 31 · funct 32 · sa 16 | rd, rt | rd = the low byte of rt, sign-extended to 32 bits | alu | §8.1 |
| ▸ sehr2 | R | op 31 · funct 32 · sa 24 | rd, rt | rd = the low halfword of rt, sign-extended to 32 bits | alu | §8.1 |
| ▸ extr2 | R | op 31 · funct 0 | rt, rs, pos, size | rt = bits pos+size-1..pos of rs, zero-extended | alu | §8.2 |
| ▸ insr2 | R | op 31 · funct 4 | rt, rs, pos, size | replace bits pos+size-1..pos of rt with the low size bits of rs | alu | §8.2 |
| ▸ lb | I | op 32 | rt, off(rs) | rt = one byte at rs+off, sign-extended | mem | §4.3 |
| ▸ lh | I | op 33 | rt, off(rs) | rt = one halfword at rs+off, sign-extended | mem | §4.3 |
| ▸ lw | I | op 35 | rt, off(rs) | rt = the word at rs+off | mem | §4.2 |
| ▸ lbu | I | op 36 | rt, off(rs) | rt = one byte at rs+off, zero-extended | mem | §4.3 |
| ▸ lhu | I | op 37 | rt, off(rs) | rt = one halfword at rs+off, zero-extended | mem | §4.3 |
| ▸ sb | I | op 40 | rt, off(rs) | the low byte of rt → memory at rs+off | mem | §4.3 |
| ▸ sh | I | op 41 | rt, off(rs) | the low halfword of rt → memory at rs+off | mem | §4.3 |
| ▸ sw | I | op 43 | rt, off(rs) | rt → the word at rs+off | mem | §4.2 |
| ▸ beq | I | op 4 | rs, rt, label | if rs == rt go to label (after the delay slot) | control | §5.1 |
| ▸ bne | I | op 5 | rs, rt, label | if rs != rt go to label | control | §5.1 |
| ▸ blez | I | op 6 | rs, label | if rs <= 0 go to label | control | §5.2 |
| ▸ bgtz | I | op 7 | rs, label | if rs > 0 go to label | control | §5.2 |
| ▸ bltz | I | op 1 · rt 0 | rs, label | if rs < 0 go to label | control | §5.2 |
| ▸ bgez | I | op 1 · rt 1 | rs, label | if rs >= 0 go to label | control | §5.2 |
| ▸ bltzal | I | op 1 · rt 16 | rs, label | $ra = PC+8; if rs < 0 go to label | control | §5.3 |
| ▸ bgezal | I | op 1 · rt 17 | rs, label | $ra = PC+8; if rs >= 0 go to label (bal is bgezal $zero) | control | §5.3 |
| ▸ j | J | op 2 | target | go to target (same 256 MB region) | control | §5.3 |
| ▸ jal | J | op 3 | target | $ra = PC+8; go to target — the function call | control | §5.3 |
| ▸ jr | R | op 0 · funct 8 | rs | go to the address in rs — the function return | control | §5.3 |
| ▸ jalr | R | op 0 · funct 9 | rd, rs | rd = PC+8 (default $ra); go to the address in rs | control | §5.3 |
| ▸ mult | R | op 0 · funct 24 | rs, rt | HI:LO = rs × rt, signed 64-bit product | muldiv | §7.1 |
| ▸ multu | R | op 0 · funct 25 | rs, rt | HI:LO = rs × rt, unsigned 64-bit product | muldiv | §7.1 |
| ▸ div | R | op 0 · funct 26 | rs, rt | LO = rs ÷ rt, HI = rs mod rt (signed) | muldiv | §7.2 |
| ▸ divu | R | op 0 · funct 27 | rs, rt | LO = rs ÷ rt, HI = rs mod rt (unsigned) | muldiv | §7.2 |
| ▸ mul | R | op 28 · funct 2 | rd, rs, rt | rd = low 32 bits of rs × rt; HI/LO become undefined | muldiv | §7.2 |
| ▸ mfhi | R | op 0 · funct 16 | rd | rd = HI | muldiv | §7.1 |
| ▸ mflo | R | op 0 · funct 18 | rd | rd = LO | muldiv | §7.1 |
| ▸ mthi | R | op 0 · funct 17 | rs | HI = rs | muldiv | §7.1 |
| ▸ mtlo | R | op 0 · funct 19 | rs | LO = rs | muldiv | §7.1 |
What to notice
Ten groups, four units. Everything under opcode 0 is one row of the
encoding table refined by funct. The release-2 rows, rotr, ext, ins, seb, seh, hide under opcodes 0 and 31. clz and mul hide under opcode 28. Nothing
here traps, nothing is privileged, and nothing touches floating point.
MIPS beside RISC-V
The same engine also runs your RV32IM description. So the comparison this book kept promising can now be made on facts, not opinions.
MIPS32 and RV32IM: ten places they differ
MIPS32 Vol. II; the RISC-V manual Vol. I; both descriptions in carolyne/isa
| Property | MIPS32r2 | RV32IM |
|---|---|---|
| one immediate layout? | ||
| a zero register? | ||
| a delay slot? | ||
| multiply into special registers? | ||
| a < b in one branch? | ||
| a fixed reset vector? | ||
| add can trap? | ||
| rotates and bit-fields in the base? | ||
| conditional moves in the base? | ||
| 32 registers with ABI names? |
Select any cell to read why — and what it costs.
Most rows are choices with a cost each way. The delay slot and the branch forms are the two that change instruction counts on ordinary C.
What to notice
Most rows are trades. A single immediate layout buys MIPS a simpler extractor. RISC-V pays with scrambled B and J forms but keeps the sign bit fixed. HI/LO cost MIPS two register classes; RISC-V pays two instructions
for a 64-bit product. Only two rows cost instructions on ordinary C. The delay slot: one nop per taken branch today. And a < b: two words on MIPS against one.
The sweep
Seventeen programs, both ISAs, one engine, -O2, two lanes. Every run
matched its host build.
Cycles at -O2 on the same engine: MIPS32 against RV32IM, seventeen programs
generated/sim/run/*_mips32_o2 and *_rv32im_o2, result.json; the sweep of 2026-09-21
| riscv_temp | 34 | 34 | 1.00 | same |
| hello | 264 | 244 | 1.08 | RISC-V |
| ldst | 50 | 51 | 0.98 | MIPS |
| rec | 45 | 45 | 1.00 | same |
| m4 | 44 | 44 | 1.00 | same |
| fwd | 50 | 49 | 1.02 | RISC-V |
| subword | 58 | 61 | 0.95 | MIPS |
| sort_3 | 6,112 | 6,215 | 0.98 | MIPS |
| stencil | 4,565 | 4,896 | 0.93 | MIPS |
| fib | 3,391 | 3,151 | 1.08 | RISC-V |
| hanoi | 9,854 | 11,052 | 0.89 | MIPS |
| acker | 24,950 | 20,414 | 1.22 | RISC-V |
| combinat | 52,098 | 53,806 | 0.97 | MIPS |
| stirling | 33,501 | 31,861 | 1.05 | RISC-V |
| cprime | 167,850 | 170,399 | 0.99 | MIPS |
| tarai | 247,744 | 222,545 | 1.11 | RISC-V |
| komachi | 1,709,313 | 1,744,670 | 0.98 | MIPS |
What to notice
The ratio stays between 0.89 and 1.22. MIPS takes fewer cycles on eight
programs, more on six, and ties three. hello shows the slot cost plainly:
twenty taken branches, twenty nops, twenty more cycles. hanoi and
stencil go the other way. Where the programs differ by a thousand times,
the ISAs differ by a fifth at most.
Before reading on, decide:
hanoi runs in 9,854 cycles on MIPS and 11,052 on RISC-V, on the same engine. Which claim does that support?
Go deeper: phase B: letting the engine execute the delay slot
The plan is one µop feature, read in four places. Decode marks a lane as a
slot when the previous instruction carried delay_slot. The tag generator books the speculation tag on the slot’s lane instead of the branch’s. The branch carries the tag its slot will book. So the slot is older than
everything the squash covers and survives by construction. The reorder
buffer rolls back by one more entry when the branch has a slot. And the branch station holds a branch until the entry after it is allocated. So the tag it carries exists before it can resolve. About a hundred lines of
engine code, no MIPS name in any of them. Then -fno-delayed-branch and
the compile tool’s check go, and GCC fills the slots.
Can you now explain it?
An ISA is five kinds of data: register classes, µops with slots, an encoding table, immediate rules and unit bodies. The engine builds the rest. MIPS and RISC-V differ in ten stated places, two of which cost instructions, and run within a fifth of each other on the same core.
If you remember only three things
- The description is data. Three classes, 63 µops, 26 encoding rows, five immediate rules, four unit bodies. The chapters of this book are its rows.
- The engine adapts. Decoder, rename, stations and rollback came from the description; 34 of 34 runs matched on the first try. The delay slot is the one feature it does not read yet.
- The ISAs are close. Ten differences, most of them trades. Cycles at -O2 stay within 0.89 to 1.22 of each other over seventeen programs.
Now try to explain this one yourself: a colleague asks you to bring up a third ISA on this engine. Name the five files you would write first. Then name one thing about that ISA that might force a change in the engine.
Go deeper: a model answer, once you have tried
First reg.py: the register classes and which registers are constant.
Then uop.py with operand.py: one µop per instruction and the slot each
field feeds. Then mop.py: the opcode rows and their sub-rules. Then
imm.py: how each immediate is placed and sign-filled. Then the unit
bodies. The thing to watch is anything the µop record cannot say. MIPS
found one, the delay slot, which the record can state but the engine does
not yet honour. An ISA whose instructions read four sources would find the next one. So would one that cracks an instruction into several µops.
Check your understanding
0/5 answered1.A colleague wants to add a third ISA to the same engine. What must they write?
2.Why does the description declare hi and lo as two one-register classes instead of one 64-bit accumulator?
3.Today every delay slot must be a nop. What has to change in the engine before the compiler may fill slots?
4.Which of these is a real difference between MIPS32 and RV32IM, rather than a difference in names?
5.Across the seventeen programs, MIPS and RISC-V finish within about twenty percent of each other on one engine, most within a few. What does that show?