Master the fundamental concepts of instruction set architecture through this focused micro-challenge.
You have read the whole brief, and the concepts above stay free on every task. Writing and running the code needs a plan.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it worksRISC-V defines a modular open ISA. RV32I provides 32 registers (x0 hardwired zero), load/store, branches, and immediate arithmetic. Emulator structure mirrors your toy CPU: fetch, decode, execute.
cLoading…
For example, ADDI x5, x0, 10 places decimal 10 into x5 using opcode 0x13.
For this exercise, you will implement decode and execute for a subset of RV32I and run small binaries linked for qemu-riscv32 or flat hex images. This task asks you to see how a clean encoding map simplifies decoder tables compared to x86.
Keep the relevant datasheet, ISA manual, or architecture textbook chapter open while you implement. When your output disagrees with the reference trace on the same program, the bug is usually a mis-decoded opcode, a stale register read, or a flag bit left unchanged after arithmetic.
For this exercise, you will use those habits while implementing the requirement in the starter code. Microarchitectural product names change across CPU generations, but the control ideas (fetch, bypass, cache lines, vector lanes) stay stable enough to debug from first principles.
Write an emulator for a subset of RV32I, the RISC-V base integer ISA. It reads raw 32-bit machine-code words and decodes each one's fields: opcode, registers, funct3/funct7 and the scrambled immediates. It executes each instruction and prints a disassembled trace with the effect of every step.
x0: x31, 32-bit signed, all 0. x0 always reads 0, and writes to it are discarded.0, 4, 8, …. Data memory is a separate 4 KiB (addresses 0x000: 0xfff), zero-filled, little-endian.ecall (0x00000073), an illegal instruction, a memory fault, a pc outside the program, or after 1000 instructions.| Format | Instructions (funct3) | Immediate |
|---|---|---|
R 0x33 | add(0, funct7 0) sub(0, funct7 0x20) sll(1) slt(2) xor(4) srl(5) or(6) and(7) | - |
I 0x13 | addi(0) slli(1) slti(2) xori(4) srli(5) ori(6) andi(7) | inst[31:20] sign-extended (shift amount = inst[24:20], funct7 must be 0) |
I 0x03 | lw(2) | same |
S 0x23 | sw(2) | inst[31:25] ‖ inst[11:7] sign-extended |
B 0x63 | beq(0) bne(1) blt(4) bge(5), signed compares | inst[31] ‖ inst[7] ‖ inst[30:25] ‖ inst[11:8] ‖ 0, sign-extended, relative to pc |
U 0x37 | lui | inst[31:12] << 12 |
J 0x6f | jal (rd = pc + 4) | inst[31] ‖ inst[19:12] ‖ inst[20] ‖ inst[30:21] ‖ 0, relative to pc |
I 0x67 | jalr (rd = pc + 4, pc = (rs1 + imm) & ~1) | I-type |
Anything else is illegal. A lw/sw address that isn't a multiple of 4 or goes past 0xffc is a memory fault.
One line per instruction: PPPP: DISASSEMBLY -> EFFECT (PPPP = 4 hex digits).
| Kind | Disassembly | Effect |
|---|---|---|
| R | add x7, x5, x6 | x7=7 |
| I (ALU) | addi x5, x0, -1 · slli x5, x5, 2 | x5=-1 |
lw / sw | lw x8, 16(x2) / sw x7, 16(x2) | x8=… / mem[0x0010]=7 |
lui | lui x5, 0x12345 | x5=305418240 |
| branch | bne x10, x0, 0x0008 (absolute target) | taken / not taken |
jal / jalr | jal x1, 0x0028 / jalr x0, 0(x1) | x1=12, pc=0x0028 (just pc=0x… when rd is x0) |
A write to x0 prints x0 unchanged. Values are signed decimal. Special endings: PPPP: ecall -> exit, PPPP: illegal instruction 0xHHHHHHHH, PPPP: lw x6, 2(x9) -> memory fault at 0x0017, pc 0xPPPP out of program, step limit reached. Finally:
cLoading…
N counts completed instructions, including ecall, but not an illegal or faulting one.
Input:
cLoading…
Output:
cLoading…
x0 = 0 in the write-back step, not in each instruction.Hidden tests cover jal/jalr calls and returns, lui, shifts, slt/blt, a write to x0, xori/ori/andi with negative immediates, an illegal instruction and a misaligned load.