Level 4 — ApplicationInstruction Set Architecture (ISA)

Instruction Set Architecture (ISA)

printable — key stays hidden on paper

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 0x30000x3003. (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) aa0 (x10), ba1 (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)"}
]