Instruction Set Architecture (ISA)
Level 4 — Application (novel problems, no hints) Time: 60 minutes | Total: 60 marks
Answer all questions. Show working. Assume RV32I unless stated otherwise. Registers follow the standard RISC-V ABI.
Question 1 — Instruction Encoding & Formats (14 marks)
The RV32I I-type format packs fields as (MSB→LSB):
imm[11:0] (12 bits) | rs1 (5) | funct3 (3) | rd (5) | opcode (7).
Given the instruction addi x15, x8, -24:
- opcode for OP-IMM =
0010011, funct3 for ADDI =000.
(a) Encode the instruction into a 32-bit binary word, then give the result as an 8-hex-digit value. Show each field. (6)
(b) The B-type (branch) format encodes a 13-bit signed offset but only stores 12 bits in the instruction (bit 0 is always 0). Explain why dropping bit 0 is safe, and compute the range of branch target offsets (in bytes, relative to PC) that a B-type instruction can encode. (4)
(c) A designer proposes reusing the freed bit to extend branch reach. State one concrete consequence this has for the instruction set's regularity and for the assembler/linker, and whether it could break existing code alignment assumptions. (4)
Question 2 — Addressing Modes & Load/Store (12 marks)
Consider this data layout in little-endian RV32I memory. Base register x10 = 0x2000.
| Address | Byte |
|---|---|
| 0x2000 | 0x78 |
| 0x2001 | 0x56 |
| 0x2002 | 0x34 |
| 0x2003 | 0x12 |
| 0x2004 | 0xFF |
| 0x2005 | 0x80 |
(a) Give the value loaded into x5 by lw x5, 0(x10). (2)
(b) Give the value in x6 after lh x6, 4(x10) (load half-word, sign-extended). Express as full 32-bit hex. (4)
(c) RISC-V provides only base+displacement addressing for loads/stores. Show the exact instruction sequence to emulate an indexed access x7 = MEM[x10 + x11] (word), and state how many instructions a CISC machine with a scaled-index mode would need for the same. (6)
Question 3 — Calling Convention & ABI (14 marks)
A function int f(int a, int b) calls a helper int g(int) and must preserve the value of a across the call to compute a + g(b).
(a) State which register a and b arrive in, which register the return value uses, and classify t0 and s1 as caller- or callee-saved. (4)
(b) Write the RV32I assembly for f, correctly building a stack frame, preserving a, calling g, and returning. Assume g's address is reachable by call g. (8)
(c) Explain why storing a in t0 across the call would be a bug, but storing it in s1 requires extra work in f's prologue/epilogue. (2)
Question 4 — Endianness & Semantics (10 marks)
A 32-bit value 0xDEADBEEF is stored at address 0x3000 by a big-endian machine.
(a) List the 4 bytes at addresses 0x3000–0x3003. (3)
(b) A little-endian machine reads the same 4 raw bytes as a word without byte-swapping. What 32-bit value does it obtain? (3)
(c) A network protocol requires "network byte order." Explain which endianness this is and why a portable program must convert, even when both endpoints are little-endian. (4)
Question 5 — Privilege, Exceptions & Extensions (10 marks)
(a) A user-mode RV32I program executes an ecall and later a div by a x0-held zero. For each event, state whether it causes a trap/exception and describe the privilege transition (if any) that occurs. Note: in RISC-V, integer divide-by-zero does not trap. (5)
(b) A workload does many a = b * c + d operations on 32-bit integers. Explain the benefit the M extension brings over an RV32I base implementation, and give one reason a minimal embedded core might still omit M. (3)
(c) Name the RISC-V extension that reduces code size using 16-bit instructions, and state one architectural constraint it imposes. (2)
Answer keyMark scheme & solutions
Question 1
(a) Field values:
- imm[11:0] of −24: two's complement 12-bit → 4096 − 24 = 4072 =
1111 1110 1000. - rs1 = x8 =
01000. - funct3 =
000. - rd = x15 =
01111. - opcode =
0010011.
Concatenate: 111111101000 | 01000 | 000 | 01111 | 0010011
Binary: 1111 1110 1000 0100 0000 0111 1001 0011
Hex: 0xFE840793. (2 imm, 1 rs1, 1 rd, 1 opcode, 1 correct hex assembly)
(b) Instructions are 32-bit and (in base ISA) aligned to even (2-byte for C, 4-byte otherwise) addresses, so any valid branch target has bit 0 = 0. Storing that always-zero bit wastes encoding space, so it is implied. (2) Range: 12 stored bits + implied 0 → signed 13-bit offset = −4096..+4094 bytes in steps of 2. (2)
(c) Reusing the bit means the offset can no longer be assumed multiple-of-2, breaking the uniform "shift-and-sign-extend" decode; the assembler/linker must now emit/relocate an extra offset bit and can no longer guarantee target alignment — self-modifying jump tables and instruction alignment (needed for atomic instruction fetch / no misaligned-fetch trap) could break. Regularity (a RISC virtue) is lost. (1 regularity, 1 assembler/linker, 1 alignment, 1 coherent argument)
Question 2
(a) Little-endian word at 0x2000 = bytes 78 56 34 12 → 0x12345678. (2)
(b) Half-word at 0x2004: bytes 0xFF (low), 0x80 (high) → 0x80FF. Bit 15 = 1 → sign-extend: 0xFFFF80FF. (2 value, 2 sign extension)
(c) No single-instruction indexed mode; must compute the address first:
add x12, x10, x11 # x12 = base + index
lw x7, 0(x12) # load word
Two instructions in RISC-V. A CISC scaled-index mode (e.g., mov x7, [x10 + x11*4]) does it in one instruction. (3 sequence correct, 1 both instrs needed, 2 CISC = 1)
Question 3
(a) a → a0 (x10), b → a1 (x11); return value → a0. t0 is caller-saved (temporary); s1 is callee-saved. (1 each)
(b) One acceptable solution (preserve a in callee-saved s0):
f: addi sp, sp, -16
sw ra, 12(sp)
sw s0, 8(sp) # save callee-saved reg we use
mv s0, a0 # preserve a
mv a0, a1 # arg for g = b
call g # a0 = g(b)
add a0, s0, a0 # a + g(b)
lw s0, 8(sp)
lw ra, 12(sp)
addi sp, sp, 16
ret
Marks: frame alloc (1), save ra (1), save s0 (1), preserve a (1), pass b correctly (1), call (1), add result (1), restore + ret (1).
(c) t0 is caller-saved: g may freely overwrite it, so a would be destroyed — a bug. s1 (callee-saved) survives the call, but f must itself save/restore s1 in prologue/epilogue because f is now the callee responsible for it. (1 + 1)
Question 4
(a) Big-endian: most-significant byte first. 0x3000=DE, 0x3001=AD, 0x3002=BE, 0x3003=EF. (3, −1 per wrong byte)
(b) Little-endian interprets first byte as least-significant: bytes DE AD BE EF → value 0xEFBEADDE. (3)
(c) Network byte order = big-endian. A portable program must convert (htonl/ntohl) because the wire format is fixed as big-endian regardless of host; on a little-endian host the raw in-memory bytes differ from wire order, so without conversion the value is byte-swapped/garbled — the conversion is defined relative to the standard, not relative to the peer. (1 big-endian, 1 fixed standard, 1 host mismatch, 1 must convert even LE↔LE reasoning)
Question 5
(a) ecall: causes a trap — environment call from U-mode; control transfers to M-mode (or S-mode) handler, privilege level raised, mepc/mcause set. (1 trap, 1 privilege raise, 1 handler entry)
div x_, x_, x0: divide-by-zero in RISC-V is defined, not a trap; result is all-ones (−1)/quotient per spec, no privilege change. (1 no trap, 1 defined result / no transition)
(b) M extension provides hardware mul/div; RV32I base has no multiply, so b*c must be done by a software shift-add routine — many instructions and cycles. M does it in one instruction, big speedup. (1 mul in HW, 1 base needs software loop) Omission reason: saves silicon area/power in a tiny core where multiply is rare. (1)
(c) The C (compressed) extension. Constraint: it enables 16-bit instruction alignment (2-byte aligned instructions), or it can only encode a restricted subset of registers/immediates. (1 name, 1 constraint)
[
{"claim":"addi x15,x8,-24 encodes to 0xFE840793","code":"imm=(-24)&0xFFF\nrs1=8\nfunct3=0\nrd=15\nopcode=0b0010011\nword=(imm<<20)|(rs1<<15)|(funct3<<12)|(rd<<7)|opcode\nresult = (word==0xFE840793)"},
{"claim":"B-type offset range is -4096..+4094 step 2","code":"lo=-(1<<12)\nhi=(1<<12)-2\nresult = (lo==-4096 and hi==4094)"},
{"claim":"lh sign-extends 0x80FF to 0xFFFF80FF","code":"v=0x80FF\nif v & 0x8000: v -= 0x10000\nresult = ((v & 0xFFFFFFFF)==0xFFFF80FF)"},
{"claim":"BE-stored 0xDEADBEEF read LE gives 0xEFBEADDE","code":"be=[0xDE,0xAD,0xBE,0xEF]\nle=be[0]|(be[1]<<8)|(be[2]<<16)|(be[3]<<24)\nresult = (le==0xEFBEADDE)"}
]