Level 3 — ProductionInstruction Set Architecture (ISA)

Instruction Set Architecture (ISA)

45 minutes60 marksprintable — key stays hidden on paper

Level: 3 — Production (from-scratch derivations, code-from-memory, explain-out-loud) Time Limit: 45 minutes Total Marks: 60

Instructions: Answer all questions. Show all working. Where encoding is required, derive bit-fields from memory. Assembly may be written in RISC-V (RV32I/RV64I) unless a specific ISA is named.


Question 1 — RISC-V Instruction Encoding from Scratch (12 marks)

The RV32I base ISA defines the R-type, I-type, and S-type formats. From memory:

(a) Draw the 32-bit field layout (with bit positions and field widths) for the R-type, I-type, and S-type formats. (6 marks)

(b) Encode the instruction add x5, x6, x7 into its full 32-bit binary and hexadecimal value. Given: R-type add has funct7 = 0000000, funct3 = 000, opcode = 0110011. (4 marks)

(c) Explain why the S-type format splits the 12-bit immediate into two disjoint fields (imm[11:5] and imm[4:0]) instead of keeping it contiguous. (2 marks)


Question 2 — Addressing Modes & Load/Store Model (10 marks)

(a) A programmer writes the C statement x = A[i]; where A is a base address in register x10, i is in x11, and each element is a 4-byte word. Write the RISC-V load/store sequence to compute this (result in x12), using only base-plus-offset addressing. (4 marks)

(b) RISC-V is a load/store architecture. Explain out loud (in prose) two concrete consequences this imposes on the instruction set compared to a CISC architecture that permits memory operands in arithmetic. (4 marks)

(c) Name the single addressing mode RV32I actually provides for loads/stores, and state how "register indirect" and "PC-relative" addressing are expressed using it. (2 marks)


Question 3 — Endianness Derivation (8 marks)

The 32-bit word 0xDEADBEEF is stored at memory address 0x1000.

(a) Give the byte stored at each of addresses 0x10000x1003 under little-endian and under big-endian. (4 marks)

(b) A byte-load lb x5, 0(x10) executes with x10 = 0x1000. Give the sign-extended 32-bit value loaded in x5 for each endianness. (2 marks)

(c) Explain why endianness is invisible to a program that only ever accesses the word via aligned 32-bit loads/stores, but becomes visible the moment it does a byte access or transfers the raw bytes over a network. (2 marks)


Question 4 — Calling Convention & ABI (12 marks)

Write a RISC-V (RV32I) leaf-and-non-leaf-aware function from scratch.

(a) Implement this C function in RISC-V assembly following the standard integer calling convention:

int sum_sq(int n) {          // n in a0
    if (n <= 0) return 0;
    return n*n + sum_sq(n-1);
}

Your code must correctly save/restore any callee-saved registers and the return address. Use the mul instruction (assume M extension). (8 marks)

(b) State which registers are caller-saved vs callee-saved in the RISC-V integer ABI, and explain why ra must be saved on the stack in your recursive function but would not need saving in a leaf function. (4 marks)


Question 5 — CISC vs RISC & Privilege (10 marks)

(a) Give three architectural design decisions that distinguish a RISC philosophy from CISC, and for each state the trade-off it makes (what is gained, what is paid). (6 marks)

(b) A user-mode program executes an instruction that attempts to write a control-and-status register (CSR) reserved for machine mode. Describe the sequence of events the hardware performs, and explain the role of privilege levels in preventing user code from compromising the system. (4 marks)


Question 6 — Instruction-Level Semantics & Exceptions (8 marks)

(a) Distinguish a synchronous exception (trap) from an asynchronous interrupt, giving one concrete example of each in a RISC-V system. (4 marks)

(b) When a divide-by-zero-like fault or a page fault occurs mid-instruction, the ISA must define precise exception semantics. Explain what "precise" means in terms of architectural state, and why precise exceptions are essential for correct restart/resume of the faulting instruction. (4 marks)

Answer keyMark scheme & solutions

Question 1 (12 marks)

(a) Formats (bit 31 … bit 0), 6 marks — 2 per format:

  • R-type: funct7[31:25] | rs2[24:20] | rs1[19:15] | funct3[14:12] | rd[11:7] | opcode[6:0] (7/5/5/3/5/7 bits).
  • I-type: imm[11:0][31:20] | rs1[19:15] | funct3[14:12] | rd[11:7] | opcode[6:0] (12/5/3/5/7).
  • S-type: imm[11:5][31:25] | rs2[24:20] | rs1[19:15] | funct3[14:12] | imm[4:0][11:7] | opcode[6:0].

Why: rs1, rs2, funct3, opcode occupy fixed positions across formats so the decoder can read register selectors and opcode without first fully decoding the format — a hallmark of fixed-field RISC encoding. (Award 1 mark per correct field ordering, 1 for correct widths.)

(b) Encode add x5, x6, x7: rd=5=00101, rs1=6=00110, rs2=7=00111, funct3=000, funct7=0000000, opcode=0110011. (2 marks)

Assemble (bit31→0): 0000000 00111 00110 000 00101 0110011 Group into nibbles: 0000 0000 0111 0011 0000 0010 1011 0011 = 0x007302B3. (2 marks)

(c) In S-type the destination is memory, so there is no rd; but keeping rs1, rs2, funct3, and opcode in the same bit positions as other formats is more valuable to the decoder than a contiguous immediate. The immediate is therefore split to fill the leftover bits ([31:25] and [11:7]) without disturbing the fixed register/opcode fields. (2 marks)


Question 2 (10 marks)

(a) (4 marks):

    slli x13, x11, 2      # x13 = i*4 (byte offset)
    add  x13, x10, x13    # x13 = &A[i]
    lw   x12, 0(x13)      # x12 = A[i]

(1 mark scaling by 4, 1 for address add, 1 for lw, 1 for correct target register/offset syntax.)

(b) (4 marks, 2 each):

  1. Arithmetic/logic instructions only take register (or immediate) operands — memory is touched only by explicit lw/sw, so instructions have a small, uniform set of operand sources → simpler decode and pipelining.
  2. The compiler must explicitly generate loads/stores and manage register allocation; instruction count rises but each instruction has fixed, predictable latency and a single memory access at most, easing hazard detection and a fixed pipeline structure.

(c) (2 marks): RV32I provides only base-plus-12-bit-signed-offset (offset(rs)). "Register indirect" = offset 0 (0(rs)). "PC-relative" is done via auipc (which forms PC + imm) into a register, then a base+offset access — the base register holds a PC-derived address.


Question 3 (8 marks)

Word 0xDEADBEEF: bytes DE (MSB), AD, BE, EF (LSB).

(a) (4 marks, 2 per column):

Addr Little-endian Big-endian
0x1000 0xEF 0xDE
0x1001 0xBE 0xAD
0x1002 0xAD 0xBE
0x1003 0xDE 0xEF

(LE stores least-significant byte at lowest address.)

(b) (2 marks):

  • Little-endian: byte at 0x1000 = 0xEF → sign-extend (0xEF has bit7=1) → 0xFFFFFFEF = −17.
  • Big-endian: byte at 0x1000 = 0xDE → sign-extend → 0xFFFFFFDE = −34.

(c) (2 marks): An aligned 32-bit load reassembles the same numeric word regardless of internal byte order — the CPU applies its own endianness consistently on store and load, so the abstraction of "a 32-bit integer" is preserved. Endianness only leaks when the program observes individual bytes (byte load/pointer aliasing) or emits the raw byte stream to another agent (network/file) whose byte-order convention may differ — hence network byte order (big-endian) standards.


Question 4 (12 marks)

(a) (8 marks):

sum_sq:
    addi sp, sp, -16      # allocate frame
    sw   ra, 12(sp)       # save return address
    sw   s0, 8(sp)        # save callee-saved s0 (holds n)
    blez a0, .Lzero       # if n <= 0 -> return 0
    mv   s0, a0           # s0 = n (survives recursive call)
    addi a0, a0, -1       # arg = n-1
    call sum_sq           # a0 = sum_sq(n-1)
    mul  t0, s0, s0       # t0 = n*n
    add  a0, a0, t0       # a0 = n*n + sum_sq(n-1)
    j    .Ldone
.Lzero:
    li   a0, 0
.Ldone:
    lw   s0, 8(sp)
    lw   ra, 12(sp)
    addi sp, sp, 16
    ret

Mark scheme: prologue/epilogue balanced (2), ra saved+restored (2), n preserved across recursive call in a callee-saved reg (2), correct base-case and combine logic (2).

(b) (4 marks):

  • Caller-saved (volatile): t0–t6, a0–a7, ra. Callee-saved (non-volatile): s0–s11, sp. (2 marks)
  • ra holds the return address of this function. The call sum_sq inside overwrites ra with a new return address, so without saving it to the stack the original return address is lost. A leaf function makes no calls, so ra is never clobbered and need not be saved. (2 marks)

Question 5 (10 marks)

(a) (6 marks — 2 per point, gain + cost required):

  1. Fixed-length, simple instructions (RISC). Gain: easy decode, uniform pipelining. Cost: lower code density, more instructions per task.
  2. Load/store only memory access. Gain: regular pipeline, few memory-touching instructions. Cost: explicit loads/stores increase instruction count, pressure on register allocation.
  3. Large uniform register file, few addressing modes (vs CISC's rich modes/microcode). Gain: hardwired control, less microcode, faster clock/simpler design. Cost: compiler complexity, more instructions; loses some dense CISC idioms.

(b) (4 marks): The write to a machine-mode CSR from user mode is illegal → hardware raises an illegal-instruction (or access) exception: it does not perform the write, saves the faulting PC (into mepc/sepc), records the cause (mcause), sets the privilege to the higher (machine/supervisor) level, and jumps to the trap vector (mtvec). The OS/monitor handler then decides to terminate or emulate. Role of privilege levels: CSR/instruction access is gated by the current privilege mode, so user code physically cannot alter system control state — the hardware check enforces isolation and prevents a user process from corrupting or hijacking the system. (2 marks mechanism, 2 marks isolation rationale.)


Question 6 (8 marks)

(a) (4 marks): A synchronous exception/trap is caused by the executing instruction and occurs at a deterministic point in the stream (e.g. page fault, illegal instruction, ecall). An asynchronous interrupt arrives from an external source independent of the current instruction (e.g. timer interrupt, external device IRQ). (2 marks definition, 2 marks correct examples.)

(b) (4 marks): A precise exception guarantees that (i) all instructions before the faulting one have completed and committed their architectural state, and (ii) the faulting instruction and all after it have made no changes to architectural state (registers/memory). Thus the saved PC points to a well-defined instruction boundary. This is essential because the handler (page-fault fixup, emulation) must be able to inspect a consistent state and restart or resume the faulting instruction as if it had never partially executed — impossible if partial side-effects had leaked into registers/memory.

[
  {"claim":"add x5,x6,x7 encodes to 0x007302B3",
   "code":"funct7=0;rs2=7;rs1=6;funct3=0;rd=5;opcode=0x33; enc=(funct7<<25)|(rs2<<20)|(rs1<<15)|(funct3<<12)|(rd<<7)|opcode; result = (enc==0x007302B3)"},
  {"claim":"Little-endian lb of 0xDEADBEEF at base gives 0xEF sign-extended to 0xFFFFFFEF (=-17)",
   "code":"b=0xEF; val = b-256 if b>=128 else b; result = (val==-17 and (val & 0xFFFFFFFF)==0xFFFFFFEF)"},
  {"claim":"Big-endian lb gives 0xDE sign-extended to -34",
   "code":"b=0xDE; val = b-256 if b>=128 else b; result = (val==-34 and (val & 0xFFFFFFFF)==0xFFFFFFDE)"},
  {"claim":"sum_sq(4)=1+4+9+16=30",
   "code":"f=lambda n: 0 if n<=0 else n*n+f(n-1); result = (f(4)==30)"}
]