Instruction Sets and Assembly Language

Learn how software talks to hardware through an instruction set. Using RISC-V, read and write assembly language with registers, memory operands, constants, arithmetic, logic and branches, and translate C expressions, decisions, loops, pointers and strings into instructions. Encode instructions as 32-bit machine code and decode them again, build procedure calls with the stack, and follow a program through compiling, assembling, linking and loading. Finally, compare RISC-V with complex instruction sets such as x86 and judge what instruction set design means for performance.

IntermediateLesson 2 of 6 in Computer Organization and Architecture Course

What you will learn

  1. Explain what an instruction set is, read RISC-V assembly, and use registers, memory and constants as the operands of instructions.
    • Explain what an instruction set architecture is
      Describe the instruction set as the contract between software and hardware, the stored-program idea, and how assembly language, machine code and the RISC-V base set relate.
    • Use registers to evaluate expressions
      Name the 32 RISC-V integer registers and their conventional roles, and translate arithmetic expressions into sequences of register instructions.
    • Load and store data in memory
      Use byte addresses, data sizes, alignment and byte order to move words, halfwords and bytes between memory and registers, including array elements.
    • Work with constants and immediates
      Use immediate operands for small constants, and build full 32-bit constants and addresses with lui, auipc, addi and the li and la pseudo-instructions.
  2. Use arithmetic, logic, shift and comparison instructions, and translate C decisions and loops into branches and jumps.
    • Use arithmetic, logic and shift instructions
      Compute with add, subtract, bitwise logic, shifts and set-less-than, including overflow, signed and unsigned interpretation, masks and multiplying or dividing by powers of 2.
    • Branch and jump with conditional and unconditional instructions
      Use compare-and-branch instructions, signed and unsigned conditions, jumps and their pseudo-instructions to change which instruction runs next.
    • Translate C decisions and loops into assembly
      Turn if-else statements, compound conditions, while and for loops and switch statements into branches, and trace the resulting code with real values.
  3. Convert between assembly instructions and 32-bit machine code using the six RISC-V instruction formats, and explain why the formats are designed as they are.
    • Encode register, immediate and store instructions
      Lay out the fields of R-type, I-type and S-type instructions and convert instructions such as add, sub, addi, shifts, lw and sw into binary and hexadecimal.
    • Encode branches, jumps and upper immediates, and decode machine code
      Encode B-type, J-type and U-type instructions with pc-relative offsets and upper immediates, and decode any 32-bit RV32I word back into assembly.
  4. Follow the RISC-V calling convention to pass arguments, return results and preserve registers, use stack frames for nested and recursive procedures, and translate C pointers, arrays and strings.
    • Call and return from procedures using the calling convention
      Pass arguments and results in registers, call with jal and return with ret, and decide which registers the caller or the callee must preserve.
    • Build stack frames for nested and recursive procedures
      Allocate and free stack frames, save the return address and registers in nested and recursive calls, and place the stack in the memory layout of a program.
    • Translate C pointers, arrays and strings into assembly
      Turn pointer dereferences, pointer arithmetic, structure fields, array parameters and null-terminated strings into loads, stores and address calculations.
  5. Explain how a compiler, an assembler, a linker and a loader turn C source code into a program running in memory, including object files, relocation and dynamic linking.
    • Explain how compilers and assemblers produce object files
      Describe the translation hierarchy, what compilers and assemblers do, assembler directives and symbols, and the parts of an object file.
    • Explain how linkers and loaders create and start an executable
      Describe how a linker places modules and patches addresses, compare static and dynamic linking, and explain how a loader and system calls start and end a program.
  6. Contrast RISC-V with complex instruction sets such as x86, and judge instruction set trade-offs for performance, code size and compatibility.
    • Contrast RISC and CISC design features
      Describe the defining features of RISC and CISC instruction sets and compare RISC-V with x86 on operands, encoding, addressing modes, registers and condition codes.
    • Judge instruction set trade-offs for performance, code size and compatibility
      Use the performance equation to compare designs, explain how modern processors blur the RISC–CISC line, and evaluate common fallacies about instruction sets.

Instruction Sets and Assembly Language study guide

6 chapters

1

Instructions, registers and memory

An instruction set connects a program to a processor. Start by reading the destination and sources of a RISC-V instruction, then follow values through registers and memory. Byte addresses explain how arrays and structures are accessed; immediate fields explain why some constants need more than one instruction. Keeping values, addresses and memory contents distinct makes each assembly line easier to trace.

1.1 The instruction set as a software contract

A processor executes instructions represented by bits. An instruction set architecture (ISA) gives those bits a shared meaning, so software can request the same operations on different implementations.

What the ISA defines

The ISA specifies instructions, visible registers, data sizes, memory behavior and instruction encodings. An instruction such as add a0, a1, a2 must produce the defined result on every processor that supports it. The internal circuits and scheduling belong to the microarchitecture. Two chips can differ greatly in speed and power use while implementing the same ISA.

Binary compatibility also needs the software environment to match. A program that calls an operating-system service or library depends on more than instruction meanings. Register conventions and data layouts are part of the application binary interface, or ABI.

From readable code to bits

For C, a compiler selects assembly instructions; an assembler encodes them as machine code. Assembly is text with instruction names, registers, comments and labels. Machine code is the binary representation the processor fetches. A label such as Start: names an instruction address; it is not an instruction executed at run time. A comment begins with #.

Start:
    add a0, a1, a2   # a0 receives a1 + a2

The first register is the destination. If a1 holds 14 and a2 holds 5, the instruction writes 19 into a0; it leaves the sources unchanged. More complex expressions need several operations. Adding four register values takes three additions, with intermediate results held in registers.

Most instruction names correspond to one machine instruction. A pseudo-instruction is an assembler shorthand: mv requests a register copy and li requests a constant. A pseudo-instruction can expand into one or several real instructions, so counting assembly lines does not always give code size.

Instructions stored in memory

Under the stored-program concept, instructions and data are both represented as numbers in memory. The program counter, pc, identifies the instruction to fetch. For a sequence of 32-bit instructions, ordinary execution advances it by 4 bytes. Branches and jumps choose a different next address. The processor executes the encoding, not the assembly spelling that originally produced it.

RISC-V bases and extensions

RISC-V is an open, royalty-free ISA standard maintained by RISC-V International. RV32I supplies 40 base integer instructions and 32-bit integer registers; RV64I uses 64-bit integer registers. Both provide 32 integer registers. Optional extensions add capabilities: M supplies integer multiplication and division, A atomic operations, F and D floating point, C compressed instructions, and V vectors. The examples use RV32I and use M where mul appears.

RISC-V assembly is specific to RISC-V. A C program can be recompiled for x86 or ARM, but its RISC-V machine code cannot execute directly as those ISAs. A simulator can execute RISC-V instructions on a host with a different ISA and expose register and memory changes one step at a time.

Key Takeaways

  • The ISA defines visible behavior; microarchitecture implements it.
  • Assembly text is encoded by an assembler; pseudo-instructions may expand.
  • With 32-bit instructions, sequential execution advances the pc by 4 bytes.
  • RISC-V combines a base ISA with optional extensions.

1.2 Registers and expression results

Registers hold the values an instruction can use immediately. In RV32I each integer register holds 32 bits, and a register name identifies storage, not a C variable or a data type.

Register names and roles

There are 32 integer registers, x0 through x31. The program counter is separate. Five bits identify one integer register because there are 32 possible 5-bit patterns. A small register file also limits access cost and the space register fields consume in instructions.

RegistersABI namesConventional purpose
x0zeroAlways reads as 0
x1, x2ra, spReturn address and stack pointer
x3, x4gp, tpGlobal and thread pointers
x5–x7, x28–x31t0–t6Temporary values
x8–x9, x18–x27s0–s11Values preserved across calls
x10–x17a0–a7Arguments; a0–a1 also return results

Register s0 can also serve as fp, the frame pointer. Writes to zero are discarded, so it can supply a zero operand without first loading a constant. For example, add a0, a1, zero copies a1.

These names express a software agreement. In particular, the hardware does not preserve an s register automatically. A procedure that changes it must arrange to restore its incoming value. The standard ABI also reserves gp and tp for their environment roles; they are not ordinary scratch registers.

Break an expression into operations

Translate f = (g + h) - (i + j); with g, h, i, j in s1–s4 and the result in s0:

add t0, s1, s2
add t1, s3, s4
sub s0, t0, t1

With values 9, 4, 6 and 1, the first instruction leaves 13 in t0. The second leaves 7 in t1. The subtraction writes 6 into s0. Each temporary holds a result until the next instruction needs it.

The order of the two source operands matters for subtraction: sub s0, t1, t0 would give −6. The destination is always first, so sub t0, s0, t1 would instead overwrite a temporary and read the old s0. When tracing, write down the destination after each line; do not update both source registers.

Bits acquire meaning through use

A register has no signed or unsigned tag. The pattern 0xFFFFFFFF can mean signed −1 or unsigned 4,294,967,295. A signed comparison and an unsigned comparison can therefore disagree about the same stored bits. Addresses are also register bit patterns; a load interprets its base as an address.

Keep values alive for as long as needed

A compiler assigns frequently used values to registers. If it needs more live values than registers can hold, it stores some in memory, a process called spilling, and reloads them later. A spill is an actual store/load cost.

Calls create another boundary. A called procedure can overwrite t0–t6 and a0–a7. Keep a value needed after a call in a properly preserved s register, or save it to the stack before the call. Choosing t0 merely because it was free does not make its value survive.

Key Takeaways

  • RV32I has 32 integer registers of 32 bits; pc is separate.
  • Writes to zero disappear, and reads always give 0.
  • Register arithmetic writes the first operand and reads the following operands.
  • Temporary results must remain available until their last use.
  • Caller-saved registers may change across a procedure call.

1.3 Move values between memory and registers

Memory addresses identify bytes. Loads bring data into registers, and stores copy register bits back to memory. In a load-store architecture, arithmetic does not read its operands directly from memory.

Sizes and byte offsets

A byte occupies one address, a halfword two, and a 32-bit word four. RV32 has a 32-bit address space of up to 4 GiB. In the ILP32 C ABI, char uses 1 byte, short 2, and int, long and pointers 4.

The instructions lw and sw transfer words. Halfword loads are lh and lhu, and byte loads are lb and lbu. Stores use sh and sb for the smaller sizes. A store copies the low bits of its source; sb writes only the low 8 bits.

An address is a base register plus a signed 12-bit byte offset:

lw t0, 32(s3)
sw t0, 48(s3)

The load reads the word at the address in s3 plus 32. It does not add 32 to the loaded value, and it does not change s3. The store writes that value at the base plus 48. The offset range is −2048 through 2047.

Translate an array access

For an int array whose base is in s3, element A[i] begins at the base plus 4 times the index. To implement A[12] = h + A[8];, with h in s1:

lw t0, 32(s3)      # A[8]
add t0, s1, t0
sw t0, 48(s3)      # A[12]

If A[8] is 80 and h is 3, A[12] becomes 83. An offset of 8 would select A[2], since the address counts bytes. For a variable index in s4, form the address first:

slli t1, s4, 2
add t1, s3, t1
lw t0, 0(t1)

Scaling follows element size, not the processor name. An RV64 LP64 pointer or long occupies 8 bytes and uses ld/sd; an RV64 int still occupies 4.

Extend a small load

A register must hold 32 bits even when a load reads only 8. lb copies the byte's sign bit into every higher bit; lbu fills the higher bits with zeros. Thus byte 0xF0 becomes 0xFFFFFFF0 (−16) with lb, but 240 with lbu. The same distinction applies to lh and lhu. Unsigned bytes, including string bytes, use lbu.

Read the byte order

In little-endian memory, the least significant byte occupies the lowest address. Storing 0x12345678 at 0x1000 places 0x78, 0x56, 0x34, 0x12 at addresses 0x1000 through 0x1003. A byte load from the first address reads 0x78. Big-endian order places 0x12 first; RISC-V systems can define either order, while the usual teaching systems are little-endian.

A naturally aligned address is a multiple of the access size: 4 for a word and 2 for a halfword. Naturally aligned accesses have architectural support. A misaligned access may work, take extra time or raise an exception, depending on the execution environment. Place words on 4-byte boundaries and advance by 4 to reach the next word.

Key Takeaways

  • Load and store offsets count bytes, not array elements.
  • Compute a variable-index address before issuing the load or store.
  • Signed loads sign-extend; unsigned loads zero-extend.
  • Little-endian memory puts the least significant byte first.
  • Natural alignment follows the access size.
  • RV64 LP64 widens pointers and long values, while int remains 4 bytes.

1.4 Fit constants into instruction fields

An immediate is a constant encoded inside an instruction. It lets a common operation such as incrementing a counter avoid a separate load.

Small constants

addi t0, t0, 1 adds 1 to t0. Its immediate is signed 12-bit two's complement, from −2048 to 2047. The processor sign-extends it to 32 bits before addition. Subtract a small constant with a negative immediate, as in addi t0, t0, -5; RV32I has no subtract-immediate instruction.

Loads, jalr, and ordinary arithmetic/logical immediate instructions use signed 12-bit immediates too. In particular, andi, ori and xori do not zero-extend their constants. Shift-immediate instructions instead encode an RV32 shift amount of 0 through 31.

Useful assembler shorthands include:

TextBase instruction or expansion
mv rd, rsaddi rd, rs, 0
nopaddi x0, x0, 0
not rd, rsxori rd, rs, -1
neg rd, rssub rd, x0, rs
li rd, c, for a small caddi rd, x0, c

The constant −1 becomes all ones when sign-extended, which is why xori with −1 flips every bit.

Build all 32 bits

lui places a 20-bit field in bits 31–12 and clears the low 12 bits. For 0x12345678:

lui t0, 0x12345
addi t0, t0, 0x678

The intermediate value is 0x12345000; adding 1,656 supplies the low part. A low part with bit 11 set needs more care. Its 12-bit pattern represents a negative addend, so increase the upper part before adding it.

For 0x00001800, the low pattern 0x800 means −2048. Use:

lui t0, 0x2
addi t0, t0, -2048

The first result is 0x2000. Subtracting 0x800 produces 0x1800. Using upper field 0x1 would produce 0x800, one block of 4096 too low.

For any 32-bit constant, read its low 12 bits as a signed value. Subtract that signed value from the desired constant, divide by 4096, and retain the low 20 bits for lui. For 0xDEADBEEF, the signed low part is −273, so use upper field 0xDEADC, then addi with −273. Assemblers perform this split for li.

Constants, addresses and contents

li t0, 100 puts the number 100 in a register. lw t0, 100(zero) reads the contents of memory at address 100. An address can itself be loaded as a value: la t0, Item asks the assembler for the address named by Item.

auipc adds an upper immediate shifted left 12 bits to the address of that instruction. Combined with an immediate operation or load, it provides pc-relative access across about ±2 GiB. A local la commonly uses auipc plus addi; other expansions support shared-library access.

A constant such as 5,000 cannot be an addi operand. Load it into a second register with li, then use add. A larger value does not gain a wider immediate just because it is written in hexadecimal.

Key Takeaways

  • A signed 12-bit immediate ranges from −2048 to 2047.
  • lui supplies the upper 20 bits with 12 low zeros.
  • If the low 12-bit part is negative, compensate in the upper field.
  • li loads a value, la forms an address, and lw reads memory.
  • auipc builds an address relative to its own pc.
2

Arithmetic and control flow

Once values are in registers, instructions calculate results and decide what runs next. Bitwise logic and shifts work on representations; signed and unsigned comparisons give those patterns different meanings. Branches and jumps turn C decisions and loops into explicit paths. Tracing a path means tracking both the current values and the next instruction address, including the final failed loop test.

2.1 Compute with bits and signed values

An arithmetic instruction reads register bits and writes a new bit pattern. Understanding that pattern requires both the operation and the intended signed or unsigned interpretation.

Addition, subtraction and overflow

In RV32I, add, sub and addi retain the low 32 result bits. Adding 1 to 0x7FFFFFFF produces 0x80000000, which represents −2,147,483,648 as a signed integer. The instruction raises no overflow exception.

For unsigned addition, software can detect wraparound:

add t0, a0, a1
bltu t0, a0, Overflow

A wrapped unsigned sum is smaller than an input. This unsigned test is not the rule for signed overflow, where input and result signs matter.

The M extension supplies mul, mulh, mulhu, div, divu, rem and remu. mul keeps the low product bits; the high-product instructions distinguish signed and unsigned inputs. Division by zero returns an all-ones quotient and the original dividend as remainder, without a division exception.

Select, set and flip bits

and, or and xor operate independently on each bit. AND with 1 keeps a bit; AND with 0 clears it. OR with 1 sets a bit, and XOR with 1 toggles it.

srli t0, a0, 4
andi t0, t0, 0xF

With a0 = 0xABCD, shifting right gives 0xABC. Masking with 0xF leaves 0xC, the original bits 7–4. The shift puts the field at the bottom; the mask removes everything above it. To set bit 4 without changing the others, use ori a0, a0, 0x10. xori with −1 flips all 32 bits.

Choose a shift by its fill rule

A left shift, sll or slli, fills low positions with zeros. A logical right shift, srl or srli, fills high positions with zeros. An arithmetic right shift, sra or srai, copies the sign bit into high positions. RV32 register shifts use only the low 5 bits of the shift-count register.

Shifting 13 left by 3 produces 104. This multiplies by 8 because no needed bits are lost. Left shifts that discard significant bits instead retain only the result that fits in the register.

For unsigned values, right shift divides by a power of 2 and rounds down. For signed values, arithmetic right shift rounds toward negative infinity: shifting −7 right by 1 gives −4. C signed division truncates toward zero, giving −3 for -7 / 2. A compiler needs a correction before replacing that signed division with a shift. A logical shift of the same negative pattern fills with zeros and gives a large positive result.

Compare to produce a value

slt writes 1 when its signed first source is smaller, and 0 otherwise. sltu uses unsigned interpretation. With 0xFFFFFFFF and 1 as inputs, slt writes 1, while sltu writes 0. Immediate versions are slti and sltiu; the latter still sign-extends its immediate before the unsigned comparison.

Bitwise AND is different from C's &&. A bitwise result can contain many set bits; logical AND produces 0 or 1 and may skip evaluating the second expression.

Key Takeaways

  • RV32 arithmetic keeps low 32 bits and does not trap on overflow.
  • AND selects bits, OR sets them, and XOR toggles them.
  • Arithmetic right shift preserves the sign; logical right shift fills zeros.
  • srai and C signed division round negative nonmultiples differently.
  • slt and sltu interpret the same register patterns differently.
  • M-extension multiplication and division are separate from RV32I.

2.2 Choose the next instruction

A conditional branch compares two registers and changes the program counter when its test succeeds. When it fails, execution continues with the next instruction.

Pick the comparison
InstructionBranch condition
beq, bneEqual, unequal
blt, bgeSigned less than, signed greater than or equal
bltu, bgeuUnsigned less than, unsigned greater than or equal

RISC-V combines comparison and branch in one instruction. It has no integer condition flags to set first. To test against zero, use zero as one source. beqz a0, L expands to beq a0, zero, L.

Greater-than and less-than-or-equal forms are assembler shorthands with swapped sources. bgt a0, a1, L expands to blt a1, a0, L. Swapping the source order matters just as much as selecting the correct mnemonic.

Signed and unsigned control flow

If a0 holds 0xFFFFFFFF and a1 holds 1, blt a0, a1, L takes the branch because signed −1 is less than 1. bltu does not, because the first pattern is a large unsigned value. Use comparisons consistent with the data's meaning, including unsigned comparison for addresses.

An unsigned bounds check can reject negative signed indexes as well as indexes at or above a small nonnegative length:

bgeu a0, a1, OutOfBounds

For a length of 20, an index of −1 appears as unsigned 4,294,967,295 and branches. An index of 19 does not; an index of 20 does. The length must represent the array's actual nonnegative length.

Jumps and return addresses

jal ra, f writes the next instruction address, pc + 4, into ra, then jumps to f. A plain j L expands to jal zero, L; its link value is discarded. This is appropriate for a loop back edge, but does not establish a return address for a call.

jalr rd, offset(rs1) adds a signed 12-bit offset to a register value, clears bit 0 of the target, and writes its own pc + 4 into rd. ret expands to jalr zero, 0(ra), returning to the saved address without keeping a new link.

Conditional branches and jal store offsets relative to their own instruction addresses. Their reach is about ±4 KiB and ±1 MiB respectively. jalr uses a register-based target and can reach across the address space. A far conditional transfer can invert the test to skip a longer jump:

bne a0, a1, Skip
jal zero, Far
Skip:

Here Far is within jal reach. If equality holds, the short branch falls through into the jump; otherwise it skips it.

Recognize a basic block

A basic block has one entry at its beginning and no transfer of control before its end. Inside it, instructions run in sequence. Branch targets start new blocks; branches and jumps end them. This makes it possible to reason about which assignments always run before a particular test or exit.

Key Takeaways

  • Choose signed or unsigned branches to match the meaning of the operands.
  • RISC-V branches compare registers directly without condition flags.
  • jal saves a link; j discards it.
  • jalr uses a register plus an immediate and clears target bit 0.
  • A conditional offset is relative to the branch instruction itself.

2.3 Turn C control flow into branches

Assembly makes every path through a decision or loop explicit. Labels mark destinations, conditional branches choose paths, and plain jumps prevent unwanted fall-through.

An if-else needs an exit path

Translate if (i == j) f = g + h; else f = g - h;, with f, g, h, i, j in s0–s4:

bne s3, s4, Else
add s0, s1, s2
j Exit
Else:
sub s0, s1, s2
Exit:

The first branch tests the opposite of the C condition so that equality falls through into addition. The jump after addition prevents execution of subtraction. With g = 10 and h = 3, equal indexes give 13; unequal indexes give 7. Removing j Exit makes the subtraction run after the addition too.

For a < b && b < c, branch to the else-part as soon as either comparison fails. For a < b || b < c, branch to the then-part as soon as either succeeds. This implements short-circuit evaluation, including skipping a second expression whose value is unnecessary.

A while loop tests before its body

For while (save[i] == k) i++;, keep i in s3, k in s5 and the array base in s6:

Loop:
slli t1, s3, 2
add t1, t1, s6
lw t0, 0(t1)
bne t0, s5, Exit
addi s3, s3, 1
j Loop
Exit:

Each visit forms the current element address, loads it and checks it. With the array {7, 7, 7, 2, 7}, initial index 0 and k = 7, the loop exits with index 3. The failed test does not increment the index. The array must contain a stopping element within its valid range.

Sum a counted array

For a nonnegative element count in a1 and the array base in a0:

li t1, 0
li t0, 0
Loop:
bge t0, a1, Done
slli t2, t0, 2
add t2, a0, t2
lw t3, 0(t2)
add t1, t1, t3
addi t0, t0, 1
j Loop
Done:
mv a0, t1
ret

The top test handles a zero length without a load. With four elements {5, -2, 10, 7}, the running sum is 5, 3, 13, then 20. The sequence executes 33 instructions: 2 setup instructions, 7 for each of four successful iterations, one failed test and 2 finish instructions.

A pointer version forms an end address once and advances the current address by 4. For a valid array range whose end address does not wrap, it can use unsigned address comparisons:

li t1, 0
slli t0, a1, 2
add t0, a0, t0
bgeu a0, t0, Done
Loop:
lw t3, 0(a0)
add t1, t1, t3
addi a0, a0, 4
bltu a0, t0, Loop
Done:
mv a0, t1
ret

There are 4 setup instructions, 4 per iteration and 2 finish instructions, totaling 22 for four elements. The initial branch skips the body for an empty range; the pointer advances by one element after each load.

Multiway decisions

A switch can use repeated comparisons or a jump table when case values are dense. A table dispatch checks the case range, scales an index by the 4-byte entry size, loads the case address and jumps through it with jalr zero, 0(t0). The range check must precede the table load so that an invalid case cannot select an unrelated address.

Key Takeaways

  • Branching around the then-part commonly uses the opposite condition.
  • Jump past the else-part after completing the then-part.
  • Compound conditions must preserve short-circuit evaluation.
  • Check a counted loop before reading its first element.
  • Pointer loops can avoid repeated index scaling.
  • A jump table requires a range check before dispatch.
3

Instruction fields and machine code

Assembly names become fixed fields inside machine instructions. Work from the operation to its format, then assign registers and immediates to their bit positions. Loads, stores, branches and jumps use different layouts because they carry different information. Encoding and decoding in both directions exposes offset, sign and field-order mistakes that readable assembly can hide.

3.1 Encode register operations, loads and stores

A machine instruction is a 32-bit word divided into fields. Encoding means choosing the right layout, assigning each operand to a field, and writing every field at its full width.

Fixed positions simplify decoding

The low 7 bits, 6–0, hold the opcode. Where present, rd occupies bits 11–7, funct3 bits 14–12, rs1 bits 19–15 and rs2 bits 24–20. Five-bit register fields name x0 through x31. Function fields distinguish operations sharing an opcode.

RV32I uses R, I, S, B, U and J formats. They fit different operands into the same 32 bits while keeping register positions stable. For signed immediates, bit 31 carries the sign.

R-type: two sources and a destination

From bit 31 down to bit 0, the R layout is:

funct7 | rs2 | rs1 | funct3 | rd | opcode
   7      5     5       3     5       7 bits

For add s0, a0, a1, rd = 8, rs1 = 10, rs2 = 11. ADD uses opcode 0110011, funct3 = 000, funct7 = 0000000:

0000000 | 01011 | 01010 | 000 | 01000 | 0110011
0000 0000 1011 0101 0000 0100 0011 0011

The hexadecimal word is 0x00B50433. SUB changes only funct7 to 0100000, giving 0x40B50433 for sub s0, a0, a1. Source fields appear in a different order from the assembly operands, so do not simply concatenate the printed register names.

I-type: one source and an immediate

The I layout is imm[11:0] | rs1 | funct3 | rd | opcode. addi uses opcode 0010011 and funct3 = 000. In addi sp, sp, -16, both register fields contain 00010. The 12-bit immediate is 111111110000 (0xFF0), producing 0xFF010113.

A load uses I-type too, with the address base as rs1 and the loaded register as rd. lw t0, 32(s3) has immediate 32, base x19, destination x5, opcode 0000011 and funct3 = 010, giving 0x0209A283.

Shift-immediate operations use the low 5 bits of the immediate region as the shift amount. The upper control bits distinguish shifts: srli t0, t1, 3 is 0x00335293, while srai t0, t1, 3 is 0x40335293. Bit 30 selects the arithmetic fill rule.

S-type: store a source value

A store has no register destination. The S layout uses that space for part of its immediate:

imm[11:5] | rs2 | rs1 | funct3 | imm[4:0] | opcode

For sw t0, 48(s3), rs2 = 5 supplies the stored value and rs1 = 19 supplies the base. The 12-bit immediate 48 splits into 0000001 and 10000. With funct3 = 010 and opcode 0100011, the result is 0x0259A823. There is no rd to fill with t0.

After joining fields, group the complete 32-bit string into eight 4-bit nibbles and convert each to one hex digit. Then decode the result: confirm the opcode, register numbers and signed immediate. Padding a register field to 5 bits is essential; dropping a leading zero changes the positions of later fields.

Key Takeaways

  • Find the format from the operation before assigning operand fields.
  • Register fields are 5 bits and occupy stable positions.
  • R-type uses funct7 to distinguish add from sub.
  • I-type keeps a 12-bit immediate together.
  • S-type splits the offset and has rs2 but no rd.
  • Check an encoded word by decoding it back into operands.

3.2 Reassemble offsets and decode machine words

Branch and jump encodings save space by omitting an offset bit that is always zero. Their fields still represent byte distances, and the sign must be reconstructed before calculating the target.

B-type branch offsets

Compute the offset as the target address minus the branch address itself. A bne at 0x1010 targeting 0x1000 uses −16 bytes, not −20 and not −4 instructions.

B-type uses opcode 1100011. Its signed offset has 13 bits, including an unstored zero bit 0. The range is −4096 through +4094 bytes, in steps of 2. Placement is:

Offset bitsInstruction bits
imm[12]31
imm[10:5]30–25
imm[4:1]11–8
imm[11]7

The source fields stay in their familiar positions. The funct3 values are 000 for beq, 001 for bne, 100 for blt, 101 for bge, 110 for bltu and 111 for bgeu.

For bne t0, t1, -16, the 13-bit offset is 1 1111 1111 0000. Its scattered parts are 1, 111111, 1000 and 1 in the table's order. With rs1 = 5 and rs2 = 6, the instruction is 0xFE6298E3. Replacing the offset with +12 gives 0x00629663.

Offsets support 2-byte steps because compressed instructions can be 2 bytes long. A taken target must still satisfy the active instruction alignment: RV32I without compressed instructions requires 4-byte alignment.

J-type and U-type

jal uses opcode 1101111. Its 21-bit signed byte offset omits bit 0 and places bits in the order imm[20|10:1|11|19:12] across instruction bits 31–12. The range is −1,048,576 through +1,048,574 bytes. jal ra, 192 becomes 0x0C0000EF.

U-type places its upper 20-bit value directly in instruction bits 31–12, followed by rd and the opcode. lui uses 0110111; auipc uses 0010111. Thus lui t0, 0x12345 is 0x123452B7.

Decode by the opcode first

For 0x00A12423, the low 7 bits are 0100011: a store. funct3 = 010 selects sw. The source fields give base x2 (sp) and value x10 (a0). The two immediate fragments join to 8, so the assembly is sw a0, 8(sp).

For a branch, join the scattered pieces in offset-bit order, append the implied zero, and sign-extend the full 13-bit result. Treating the 12 stored bits as an ordinary I-type immediate loses both the correct arrangement and the scale. The sign bit's fixed position and reuse of other field positions reduce decoder work, although the printed layout is less convenient to read by hand.

Relocation preserves relationships

If a branch and its target both move by the same distance, subtracting their new addresses gives the old offset. A reference into another section may change when sections are laid out separately. A linker can therefore preserve an internal relative branch while patching a reference to an external symbol.

Key Takeaways

  • Branch and jal offsets count bytes from the instruction address.
  • B-type and J-type omit bit 0, which is zero.
  • Reassemble an offset before interpreting its sign.
  • The opcode determines how the remaining bits are divided.
  • Moving an instruction and its target together preserves their relative distance.
4

Procedure calls and stack storage

A call has to deliver arguments, preserve useful state and return to the right place. Register conventions let separate procedures cooperate; stack frames keep the state of nested and recursive calls apart. The same address calculations used for memory operands also implement pointers, structure fields and strings. Follow a value across a call as carefully as you follow it across an arithmetic instruction.

4.1 Pass arguments and preserve register values

A procedure call transfers control and data between independently written pieces of code. The calling convention tells both sides where arguments and results go and which register values must survive.

The call and return sequence

The caller places the first eight integer arguments in a0–a7; additional arguments go on the stack. The procedure returns an integer result in a0, using a1 as well for a 64-bit result in RV32.

jal ra, f stores its own address plus 4 in ra, then jumps to f. jal f is its short spelling. The pseudo-instruction call f normally expands to auipc plus jalr for longer reach; the linker may shorten it to one jal. In either case, the return address follows the actual call sequence.

ret is jalr zero, 0(ra). It jumps through the saved address, discarding a new link. A plain j f also discards its link, so it does not set up an ordinary call and return.

Decide who saves a value

Caller-saved registers include t0–t6, a0–a7 and ra. The callee can change them. If the caller needs one of their current values later, it saves that value before the call.

Callee-saved registers include s0–s11 and sp. A callee that changes an s register must save the incoming value and restore it before returning. Restoring sp returns the caller to its own frame. The processor does not enforce these promises; compiled modules cooperate because their software follows the ABI.

Suppose a caller has a loop counter in t0 and invokes a helper. The helper can legally use t0 for its own calculation. Moving the counter into s1 can make it survive helpers, but the surrounding procedure must then preserve the s1 value it received from its own caller.

A leaf procedure can need no frame

A leaf procedure calls no other procedure. This function computes (g + h) - (i + j) from arguments in a0–a3:

leaf:
add t0, a0, a1
add t1, a2, a3
sub a0, t0, t1
ret

It uses only caller-saved working registers and leaves ra unchanged. For inputs 9, 4, 6 and 1, it returns 6. It needs no stack frame.

If the calculation uses s0 instead, it must preserve that register:

addi sp, sp, -16
sw s0, 12(sp)
add t0, a0, a1
add t1, a2, a3
sub s0, t0, t1
mv a0, s0
lw s0, 12(sp)
addi sp, sp, 16
ret

The result is moved to a0 before the old s0 is restored. If the incoming s0 was 1234, it is still 1234 afterward. The allocated frame is 16 bytes to preserve standard stack alignment even though the saved word uses only 4.

A call can be read as six actions: place arguments, transfer control, acquire storage, perform the task, place the result, and return. At each boundary, distinguish the value being returned from the older values that the convention requires the procedure to preserve.

Key Takeaways

  • Integer arguments start in a0–a7, and the first result is in a0.
  • ra points after the actual call instruction or expanded call sequence.
  • Callers protect live caller-saved values; callees restore saved registers they change.
  • A leaf using only caller-saved registers may need no frame.
  • Move the result before restoring a saved register that held it.

4.2 Give each call its own stack frame

Nested calls need somewhere to keep the outer call's data. The stack provides last-in, first-out storage: the most recent call finishes first and releases its own frame.

Allocate below the old stack pointer

The stack grows toward lower addresses. addi sp, sp, -16 reserves 16 bytes; addi sp, sp, 16 releases them. The standard ABI keeps sp a multiple of 16 throughout execution. A frame can hold saved registers, the return address, spilled values, local objects and space for outgoing stack arguments.

A common nonleaf pattern is:

addi sp, sp, -16
sw ra, 12(sp)
sw s0, 8(sp)
# body, including calls
lw s0, 8(sp)
lw ra, 12(sp)
addi sp, sp, 16
ret

The offsets refer to the new sp. If it is 0x2000, the saved ra occupies 0x200C through 0x200F. The matching epilogue restores values while the frame still exists, then releases it. Every return path must balance the allocation.

A frame pointer, conventionally s0, can give local objects fixed offsets when sp changes within a procedure. Its incoming value must be preserved like any other use of s0.

Recursion keeps separate copies

Factorial needs the original n after computing fact(n - 1). It also needs its own return address after the recursive jal overwrites ra:

fact:
addi sp, sp, -16
sw ra, 12(sp)
sw a0, 8(sp)
li t0, 2
blt a0, t0, Base
addi a0, a0, -1
jal ra, fact
lw t1, 8(sp)
mul a0, t1, a0
j Done
Base:
li a0, 1
Done:
lw ra, 12(sp)
addi sp, sp, 16
ret

The M-extension mul multiplies the returned inner result by the original n. For fact(3), frames for 3, 2 and 1 coexist. The base call returns 1; the next returns 2; the outer call returns 6. With initial sp = 0x7FFFFF00, three frames lower it by 48 bytes to 0x7FFFFED0. Returns release them in reverse order.

The base test handles nonnegative 0 and 1. For this 32-bit result, factorial is exact through 12!: 479,001,600. The low product bits no longer represent the full result at 13!, which is 6,227,020,800.

Place the stack in program memory

A typical layout has code in text, globals in static data, dynamically allocated objects in the heap and call frames in the stack. The heap often grows upward and the stack downward. Actual placement belongs to the execution environment.

A pointer to a local stack object becomes invalid when that object's lifetime ends on return. Restoring sp does not erase the old bytes, but later calls can reuse them. Similarly, changing sp and continuing to use old save offsets can read the wrong frame's bytes.

Key Takeaways

  • Subtract from sp to allocate; add the same amount to release.
  • The standard ABI requires 16-byte stack alignment.
  • A nested call overwrites ra, so preserve the outer return address.
  • Each recursive activation owns separate saved arguments and return state.
  • All exit paths must restore the frame and required registers.
  • A local object does not outlive its stack frame.

4.3 Use addresses for arrays, structures and strings

A pointer is an address value. Assembly implements a dereference by using that address as a load or store base, and implements pointer arithmetic by adding a byte distance.

Follow the pointer, then access the value

With int *p in a0, lw t0, 0(a0) reads *p, while sw t0, 0(a0) writes it. The register holds the address; the memory at that address holds the integer. If a pointer variable itself is stored in memory, first load that pointer value, then use it as the base for a second load.

Taking the address of a local C object requires addressable storage, normally a stack slot. A hardware register has no memory address that can be passed as &x.

C pointer arithmetic scales automatically. For RV32 ILP32, incrementing int *p adds 4 bytes, while incrementing char *p adds 1. Assembly has no pointer type to provide the scale:

addi a0, a0, 4    # next int
addi a1, a1, 1    # next char

An array argument is its base address, not a copy of its elements. Pass the element count separately when a procedure needs it; the pointer alone does not encode the array length.

Structure fields have byte offsets

For struct item { int id; int score; };, id occupies bytes 0–3 and score bytes 4–7. With the object's address in a0, lw t0, 4(a0) reads the score. In general, field offsets follow the ABI's layout and padding rules. Do not infer every field offset just by counting source declarations.

For an array of these 8-byte structures, the next element starts 8 bytes later. Within each element the score remains 4 bytes from that element's beginning. Element stride and field offset are separate parts of the address calculation.

Copy a null-terminated string

A C byte string ends with a zero byte. To copy it, read and store one byte before deciding whether the copy is finished:

Loop:
lbu t0, 0(a1)
sb t0, 0(a0)
addi a0, a0, 1
addi a1, a1, 1
bnez t0, Loop
ret

Here a1 is the source and a0 the destination. The destination must have enough space. lbu reads an unsigned byte; sb stores only its low 8 bits. Both pointers advance by 1, including on the final iteration. The branch tests the copied byte, so the terminating zero is stored too.

Copying "RISC" takes five iterations for four letters and the zero byte. At five instructions per iteration plus ret, it executes 26 instructions. An empty string still needs one iteration to copy its terminator. A word load would read four bytes at once and would not implement this byte-at-a-time algorithm.

UTF-8 can encode one character in several bytes. This loop copies those bytes unchanged; it does not count displayed characters. Pointer-based array loops can similarly advance by the element stride without recomputing a scaled index each time. Optimizing compilers can make that transformation even when the C program uses ordinary array indexes.

Key Takeaways

  • Dereferencing uses the address held by the pointer.
  • Assembly pointer arithmetic must include the element size.
  • Array parameters need a separate length when the procedure uses a count.
  • Structure stride and field offset serve different parts of an address.
  • A string-copy loop must store the terminating zero byte.
5

From source code to execution

A program passes through tools that know different things about its final form. A compiler chooses instructions, an assembler encodes them, a linker joins modules, and a loader prepares execution. Symbols and relocations carry unfinished references between stages. Following one call from source to its final address makes the roles of object files, shared libraries and startup code concrete.

5.1 Translate source into a relocatable object

A source file becomes executable instructions through several translation stages. Each stage has a different responsibility, and intermediate files retain information needed by later stages.

The compiler chooses instructions

For C, the usual path is source, assembly, object file, executable, then a running process. A compiler analyzes source code, optimizes it, allocates registers and selects instructions for a target ISA. A compiler driver can run several stages in one command.

gcc -S program.c stops after producing assembly. gcc -c program.c produces an object file without linking. The target compiler determines which ISA that assembly uses; a RISC-V program needs a RISC-V-targeting toolchain.

At -O0, values often pass through stack slots and the assembly closely follows individual source statements. At -O2, register allocation, eliminated work and restructured loops can obscure that correspondence. A C statement has no single fixed assembly translation.

The assembler encodes and records

The assembler turns mnemonics and register names into bits, expands pseudo-instructions and resolves labels it can locate. A forward branch refers to a label encountered later. An assembler can first collect positions and then encode references, or record places to fill in once the target is known.

Directives tell the assembler how to organize output; the processor does not execute them:

.data
.align 2
count:
.word 5
message:
.string "hi"
.text
.globl main
main:
# instructions follow

.word 5 emits a 4-byte data value. .string "hi" emits the character bytes and a terminating zero. On the GNU RISC-V assembler, .align 2 aligns the next item to a 4-byte boundary. .globl makes a symbol visible to other object files. Labels name positions; they do not reserve bytes by themselves.

What an object file keeps

A relocatable object typically contains a header, code in text sections, static data, a symbol table and relocation information. The symbol table distinguishes names defined here from references defined elsewhere. Optional debugging information connects instructions to source lines; -g requests it.

Common object formats include ELF, Mach-O and PE/COFF. The format organizes machine code and metadata; it is distinct from the ISA that defines each instruction's bits.

For call printf, the assembler can emit an auipc/jalr sequence without knowing the final function address. It records an undefined symbol and relocation entries so the linker can finish the reference. A local branch's distance can already be known when its target moves with the same section, though later layout changes such as relaxation can require adjustment.

Other execution paths

An interpreter performs operations through another running program. A just-in-time compiler generates machine code during execution, often after identifying frequently used code. Java commonly uses bytecode followed by interpretation and JIT compilation; Python commonly uses an interpreter. A JIT trades compilation work during execution for faster repeated execution.

An object file is therefore more than a raw instruction list, but it is not yet a complete running process. Unresolved symbols and final layout still need attention from the linker and loader.

Key Takeaways

  • The compiler selects and optimizes instructions for a target ISA.
  • The assembler encodes instructions and expands pseudo-instructions.
  • Directives arrange output; labels name positions.
  • Symbols and relocations preserve references that later stages must resolve.
  • -S stops at assembly, while -c stops at an object file.
  • Interpreters and JIT compilers provide different routes to execution.

A linker constructs an executable file from object modules and libraries. A loader uses that file to create a running process. Knowing which stage owns an address helps explain both relocation and startup errors.

Lay out modules and patch references

The linker places input sections in the executable's address space, resolves symbol definitions and uses relocation entries to patch references. A name that no linked module defines produces an "undefined reference" error.

Suppose text starts at 0x00010000 and data at 0x10000000. Object A contributes 0x100 text bytes and 0x20 data bytes; B follows immediately. B's text starts at 0x00010100 and its data at 0x10000020.

A jal in A at text offset 0x40 has address 0x00010040. If procB begins at B's text start, the call's offset is 0xC0, or 192 bytes, encoded with ra as 0x0C0000EF. A variable at offset 0x10 in B's data lands at 0x10000030. With gp = 0x10000800, its displacement is −2000, which fits a signed 12-bit load offset. These are separate text and data calculations.

Choose how library code is supplied

Static linking copies required library object code into the executable. The program carries that code, increasing file size; applying a library fix requires relinking it. Static libraries do not eliminate the program's dependency on its operating-system interface.

Dynamic linking keeps library code in shared files, such as .so, .dll or .dylib. Compatible library code pages can be shared by processes. A compatible updated library can serve programs without relinking each executable, but the required version must be available when it is needed.

With ELF lazy binding, the first call goes through a procedure linkage table (PLT) stub. The dynamic linker resolves the target and records it in the global offset table (GOT); later calls use that recorded address. Other configurations resolve symbols at load time.

Position-independent code reaches local targets using relative addressing, including auipc sequences, and uses indirection for external targets. A library's own relative distances remain valid when it moves as a unit. Binding external references may still require relocation work.

The loader creates the running state

The loader reads executable metadata, creates an address space, maps or copies code and data, sets up arguments and the initial stack, initializes required register state and transfers control to startup code. Startup code calls main; after it returns, runtime code requests process exit. Modern systems can map file pages and bring them into memory as needed.

ecall requests an environment service. On RISC-V Linux, a7 holds the system-call number and a0–a5 hold arguments. li a7, 93, li a0, 0, ecall requests exit with status 0; service 64 is write. Simulator service numbers come from the simulator's own interface.

A library missing during linking causes a link failure. A required shared library removed after linking can instead prevent loading or cause failure during runtime binding. The executable may have been built successfully even though it cannot now start.

Key Takeaways

  • The linker lays out an executable and resolves and patches symbol references.
  • The loader maps that executable and creates initial process state.
  • Static linking copies library code; dynamic linking needs compatible shared libraries.
  • Lazy binding records a resolved target for later calls.
  • Position independence preserves local relative references across movement.
  • ecall service numbers belong to the execution environment.
6

Instruction set design trade-offs

Instruction sets make different choices about how much work one instruction expresses. Compare RISC-V and x86 through their operands, encodings and address calculations, then connect those choices to real constraints. Performance needs instruction count, CPI and clock timing together; code density and software compatibility answer different questions. A useful design judgment names the workload and the implementation being compared.

6.1 Compare RISC-V and x86 instructions

RISC and CISC describe broad instruction-set design approaches. The useful comparison is how each represents work: operand locations, instruction length, address calculation and control flow.

Regular operations or richer instructions

RISC designs favor simple operations, many registers and load-store access. The approach developed through IBM's 801 work in the mid-1970s and Berkeley RISC and Stanford MIPS in the early 1980s. RISC-V, ARM AArch64, MIPS and Power follow this broad tradition.

CISC designs offer variable-length instructions, richer addressing and operations that can combine several steps. x86 grew from the 8086 introduced in 1978; earlier CISC families include VAX and Motorola 68000.

In Intel x86 syntax, add eax, [ebx+8] reads a 32-bit value from memory and adds it to eax. An equivalent RISC-V operation needs a load followed by arithmetic:

lw t0, 8(s1)
add a0, a0, t0

The x86 instruction combines a memory access with addition. RISC-V exposes them as separate instructions. x86 calls a 32-bit value a doubleword; its word is 16 bits. A word in the RV32 examples is 32 bits.

Encoding and addressing

An x86 instruction is 1 to 15 bytes long. Base RISC-V instructions occupy 4 bytes, with 2-byte compressed forms available through C. Regular field positions simplify decoding. Variable lengths can save space by using short encodings for common operations.

RISC-V uses register operands, immediates, base-plus-displacement addresses, and pc-relative addresses. jalr belongs to register-base addressing, while branches, jal and auipc use the pc.

x86 also combines a base, scaled index and displacement. mov eax, [ebx + esi*4 + 8] reads an indexed 4-byte element with an extra offset in one instruction. RISC-V forms the scaled index and effective address with separate instructions before loading. The work still has to occur internally in either processor.

Registers and comparisons

RISC-V has 32 integer registers, including its fixed zero register. Conventional x86-64 has 16 general-purpose registers; 32-bit x86 has 8. Many x86 arithmetic instructions use a destination that is also a source: add eax, ebx changes eax. RISC-V's three-operand add can keep both source values while writing a third register.

x86 arithmetic and cmp set flags; a later branch such as jl tests those flags. RISC-V uses blt to compare and branch directly. ARM AArch64 also uses flags, so the presence of flags alone cannot classify an ISA as CISC.

What reduced means

RISC does not mean a modern ISA must have a tiny total instruction count. RV32I has 40 base instructions, but optional extensions add many more. The central idea is regular, relatively simple operations and encodings. Compressed encodings also show that fixed instruction length is a design tendency, not a definition that every RISC instruction must satisfy.

Key Takeaways

  • RISC-V separates memory access from arithmetic.
  • x86 can combine memory operands and arithmetic in one instruction.
  • Instruction length and addressing modes affect decoding and code density.
  • RISC-V compares within a branch; x86 commonly uses condition flags.
  • Instruction count alone does not define a RISC design.

6.2 Judge speed, code size and compatibility

An instruction set shapes the work a program expresses, but execution speed also depends on the processor that implements it. Compare measured or modeled program behavior, not the number of operations promised by one instruction.

Combine all three performance factors

CPU time equals instruction count times average cycles per instruction (CPI) times clock cycle time. Equivalently, divide instruction count times CPI by clock frequency. A lower instruction count can be outweighed by a higher CPI or a slower clock.

At 2 GHz, each cycle takes 0.5 ns. A version with 1.0 billion instructions and CPI 2.0 uses 2.0 billion cycles and takes 1.0 s. Another with 1.3 billion instructions and CPI 1.2 uses 1.56 billion cycles and takes 0.78 s. The second executes 30% more instructions but is about 1.28 times as fast. CPI is an average for that program on that implementation, not a universal property of an ISA.

Complex instructions still need internal work

Many modern x86 processors translate most instructions into simpler internal micro-operations. These can then use execution techniques similar to those in modern RISC cores. The external ISA still matters for encoding, decoding and compatibility even when internal operations look similar.

A complex instruction is not a promise of a fixed cycle count. A block-copy instruction and a simple copy loop can trade places in performance across processors and workloads. The processor version, data size and memory behavior matter; a one-instruction source listing cannot decide the result.

Code density is a separate measure

Shorter machine-code sequences can reduce storage and instruction-cache pressure. CISC variable lengths often help density. RISC-V's C extension provides 16-bit encodings for common operations alongside 32-bit instructions. The specification reports typical use for 50–60% of instructions, giving roughly 25–30% smaller code.

For 100 instructions with half compressed, 50 use 2 bytes and 50 use 4: 300 bytes instead of 400. The executed instruction count remains 100. A size reduction does not by itself specify a speedup, because runtime depends on more than bytes fetched.

Compatibility and tools shape the choice

Existing x86 binaries and libraries give compatibility considerable value. RISC-V freezes base sets and adds extensions. RV32I code can run on an implementation supporting RV32I when the ABI and operating environment match; using M or V adds those requirements. RV32E is a different reduced-register base and does not promise RV32I compatibility.

ARM is widely used in mobile devices as well as laptops and servers; x86 is widely used in PCs and servers; RISC-V serves embedded systems, accelerators, research and teaching. Software availability, licensing and the quality of specific chips all matter when choosing a platform.

Hand-written assembly can help a small critical routine or expose a special instruction, but it ties code to an ISA and increases maintenance work. Optimizing compilers handle register allocation and code restructuring well. Use measurement to justify replacing clear source code with assembly.

Key Takeaways

  • Compare instruction count, CPI and cycle time together.
  • Micro-operations do not change the ISA visible to software.
  • Compressed encodings reduce bytes without necessarily reducing executed instructions.
  • Binary compatibility also requires matching ABI and environment.
  • Measure a specific program and implementation before claiming an ISA is faster.

Instruction Sets and Assembly Language examples

6 Walkthroughs

1

Trace operands from an expression to memory

15 steps
Read an instruction, track temporary values, update an array and construct a larger constant.
  1. 1
    IntroductionFollow one value at a time

    An assembly instruction names both its operation and its operands. Start with a readable register calculation, then bring a value in from memory and store an updated result. Finally, construct a constant that is too large for one immediate. At each step, track exactly what changes: a destination register, a group of memory bytes, or the next instruction address.

  2. 2
    ExplanationRead the instruction contract

    The ISA defines what add a0, a1, a2 must do: read the two sources and write their sum to the destination. Microarchitecture determines how the chip carries out that request. Assembly gives the request readable names; the assembler produces its encoded bits. Different chips implementing the same instruction contract must agree on the visible result.

  3. 3
    ChallengeCount instructions after assembly

    An assembler expands one li into two machine instructions, followed by one add. With only 32-bit encodings, how many instruction bytes do these two assembly lines produce?

    ---reveal---

    There are three actual instructions, each 4 bytes, for 12 bytes. The text line count is two, but the expansion determines the machine-code size. Labels and comments add no executed instructions.

  4. 4
    ExampleKeep the intermediate sum

    Compute r = (u + v) - w with u = 18, v = 7, w = 6 in a1, a2, a3:

    add t0, a1, a2
    sub a0, t0, a3
    

    After the first instruction, t0 holds 25. After the second, a0 holds 19. The three inputs remain available because neither instruction writes their registers. t0 has one purpose: preserve the first result until the subtraction reads it.

  5. 5
    ChallengeChange the destination, keep the sources

    The same inputs now run sub a3, t0, a3 after the addition. What changes compared with writing the result to a0?

    ---reveal---

    The numerical result is still 19, but it replaces the old value 6 in a3. The instruction reads the source values before writing its destination. a0 is unchanged, so a caller expecting the result there would not receive this new result.

  6. 6
    ExplanationTurn an index into an address

    An integer array starts at the address in s1. Each RV32 ILP32 int occupies 4 bytes, so element 2 begins 8 bytes after the base and element 5 begins 20 bytes after it. The load lw t0, 8(s1) reads the value at element 2; it does not put the address itself into t0. The base register stays unchanged.

  7. 7
    ExampleLoad, calculate, store

    Implement A[5] = A[2] + r with r = 19 in a0:

    lw t0, 8(s1)
    add t0, t0, a0
    sw t0, 20(s1)
    

    If A[2] is 11, the load gives 11, the addition gives 30, and the store writes 30 into A[5]. Reading and writing memory require separate instructions around the arithmetic. The value of A[2] remains 11.

  8. 8
    ChallengeSpot the wrong unit in an offset

    Someone changes the first line to lw t0, 2(s1) because the expression names A[2]. What has gone wrong?

    ---reveal---

    The offset is 2 bytes, not two elements. Element 2 needs 8 bytes from the base. The altered address is also not naturally aligned for a word when the base is aligned, so the access may fail or require special handling before it could even supply useful data.

  9. 9
    ExampleRead a byte from a stored word

    Store 0x7193D2A6 at an aligned address on a little-endian machine. Its increasing-address bytes are 0xA6, 0xD2, 0x93, 0x71. A byte load at offset 1 selects 0xD2. lbu yields 210, while lb yields −46 by extending bit 7 into the high register positions. The memory byte is identical; the load instruction supplies the interpretation.

  10. 10
    ExplanationFind the signed low part

    Now construct 0x00003D20 in a register. Its low 12 bits are 0xD20, which represent −736 as a signed 12-bit immediate. The upper step must therefore start one 4096-byte block higher than the visible 0x3 suggests. Choosing 0x4 gives 0x4000, from which subtracting 0x2E0 reaches the desired value.

  11. 11
    ExampleBuild and check the constant

    Use:

    lui t1, 0x4
    addi t1, t1, -736
    

    The first instruction writes 16,384; the second subtracts 736, leaving 15,648, or 0x3D20. The low part is an addend with a sign, not just digits to paste into place. li t1, 0x3D20 lets the assembler choose the construction, but its expansion still occupies real instructions.

  12. 12
    ChallengeRepair a constant split

    A split uses lui t1, 0x3 and addi t1, t1, -736. How far is its result from 0x3D20, and which field needs repair?

    ---reveal---

    It produces 0x2D20, exactly 4096 too small. Increase the upper field from 0x3 to 0x4 and keep the signed low addend. Changing the addend to positive 0xD20 would exceed addi’s signed positive range.

  13. 13
    TipTrack a register through its last use

    When tracing a sequence, write a value next to the register that currently holds it. Cross it out only when that register is overwritten. A later procedure call is an overwrite risk for temporary and argument registers even if no explicit assignment appears beside the call. Save a still-needed value before crossing that boundary.

  14. 14
    ChallengeCombine the address and value steps

    With array base s1, element A[6] is 17. t1 holds the correctly built constant 0x3D20. What does lw t0, 24(s1); add t0, t0, t1; sw t0, 4(s1) change?

    ---reveal---

    It reads A[6], adds 17 to 15,648 and stores 15,665 into A[1]. The two offsets count bytes, while the constant is an arithmetic value. The original element at index 6 and the base address in s1 remain unchanged.

  15. 15
    RecapTrace the next sequence yourself

    For a new assembly sequence, mark the registers read and written, expand any pseudo-instructions whose size matters, and translate each memory offset into bytes. Check small-load extension separately from byte order. When constructing a constant, verify the intermediate upper value plus its signed low addend before using the result as data or an address.

2

Trace arithmetic into decisions and loops

14 steps
Extract a field, choose signed comparisons and follow every path through a counted loop.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionMake the execution path visible
  2. 2
    ExampleMove the field into position
  3. 3
    ExampleKeep only the field width
  4. 4
    ChallengePredict a signed shift
  5. 5
    ExplanationCompare using the right interpretation
  6. 6
    ExampleSeparate a jump from a call
  7. 7
    ChallengeTest one pattern two ways
  8. 8
    ExampleDraw the two paths of an if
  9. 9
    ExplanationPlace the bound before the load
  10. 10
    ExampleTrace three body executions
  11. 11
    ChallengeCount the whole executed path
  12. 12
    ConnectionReplace repeated scaling with a moving address
  13. 13
    ChallengeProtect a load with short-circuit control
  14. 14
    RecapCheck values and paths together
3

Encode an instruction and reconstruct a branch

12 steps
Place operands in fields, check an encoded word and rebuild a scattered branch offset.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionTranslate in both directions
  2. 2
    ExplanationIdentify the format and operands
  3. 3
    ExampleWrite the fields at full width
  4. 4
    ExampleConvert and decode the word
  5. 5
    ChallengeLocate a store’s value source
  6. 6
    ExplanationMeasure the backward distance
  7. 7
    ExampleSplit the branch immediate
  8. 8
    ExampleAssemble the complete branch
  9. 9
    ChallengeRecover the missing zero bit
  10. 10
    ConnectionKeep the relative distance when code moves
  11. 11
    ChallengeUse the opcode to reject a false decode
  12. 12
    RecapCheck a machine word in reverse
4

Keep values alive across nested calls

15 steps
Build a correct wrapper, follow recursive frames and trace a byte string through memory.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionProtect what the caller still needs
  2. 2
    ExampleSpecify the wrapper’s result
  3. 3
    ExplanationApply the preservation promises
  4. 4
    ChallengeChoose the state that crosses the call
  5. 5
    ExampleAllocate and fill the frame
  6. 6
    ExampleCombine results before releasing storage
  7. 7
    ExplanationRepeat the pattern recursively
  8. 8
    ChallengeUnwind the saved arguments
  9. 9
    ExplanationSeparate an address from its object
  10. 10
    ExampleChoose byte operations for a string
  11. 11
    ChallengeFinish at the right addresses
  12. 12
    TipRestore the saved value, not the new result
  13. 13
    ConnectionObject lifetime follows the frame
  14. 14
    ChallengeKeep a destination pointer across a helper
  15. 15
    RecapAudit one full call boundary
5

Follow a call from source to startup

11 steps
Track a symbol through compilation, assembly, linking, dynamic resolution and process loading.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionAsk what each tool knows
  2. 2
    ExplanationCompile for a target ISA
  3. 3
    ExampleLeave the external call unfinished
  4. 4
    ChallengeDistinguish data emission from execution
  5. 5
    ExamplePlace the target module
  6. 6
    ExamplePatch the relative reference
  7. 7
    ExplanationChoose when to supply library code
  8. 8
    ChallengeLocate the stage of failure
  9. 9
    ConnectionCreate a process before calling main
  10. 10
    ChallengeExplain why an object is not the finished program
  11. 11
    RecapTrace a new error to its stage
6

Compare designs using the right measure

10 steps
Distinguish visible instructions, encoded bytes and execution time when evaluating an ISA choice.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionKeep three measurements separate
  2. 2
    ExampleCompare the visible memory operation
  3. 3
    ExplanationRelate encoding to the design choice
  4. 4
    ChallengeClassify a feature carefully
  5. 5
    ExampleCalculate the first execution time
  6. 6
    ExampleCompare a larger instruction count
  7. 7
    ChallengeSeparate byte savings from speedup
  8. 8
    ConnectionCompatibility crosses the ISA boundary
  9. 9
    ChallengeEvaluate an assembly optimization claim
  10. 10
    RecapState the evidence behind the comparison

Instruction Sets and Assembly Language practice questions

60 Practice Questions in 6 sets

Set 1: Registers, memory and constants10 Practice Questions

Question 1 of 10

In RV32, the sequence below runs with t1 = 0x7000; the word at 0x7030 is 6. What value is stored at 0x7034?

lui t0, 0x4
addi t0, t0, -1984
lw t2, 48(t1)
add t2, t2, t0
sw t2, 52(t1)
  1. 0x00003840
  2. 0x00003846
  3. 0x00004846
  4. 0x00004006
Show answerHide answer
Answer: B
The constant becomes 0x4000 minus 0x7C0, or 0x3840. The load supplies 6, so the addition produces 0x3846, which the final store writes. 0x3840 is only the constructed constant and omits the loaded operand.

Question 2 of 10

Two processors implement RV32I using different internal pipelines. What shared specification determines the result of an RV32I add instruction?
  1. The operating system’s process layout
  2. The instruction set architecture
  3. The compiler’s register allocation
  4. The processor’s clock frequency
Show answerHide answer
Answer: B
The ISA defines the visible effect of add, so both implementations must produce that result. Their internal pipelines can differ. Clock frequency affects timing, but it does not redefine what addition means to a program.

Question 3 of 10

Before sub a2, a0, a1, a0 contains 28 and a1 contains 9. What value is written to a2?
  1. 37
  2. −19
  3. 9
  4. 19
Show answerHide answer
Answer: D
The first operand is the destination, and subtraction takes the second operand minus the third. The instruction therefore writes 28 minus 9, or 19, into a2. Reversing the sources would give −19, but that is not their order here.

Question 4 of 10

An RV32 ILP32 array contains 4-byte integers. How many bytes after the base does element A[7] begin?
  1. 7 bytes
  2. 11 bytes
  3. 28 bytes
  4. 24 bytes
Show answerHide answer
Answer: C
Each preceding element occupies 4 bytes, so index 7 begins 28 bytes from the base. Using 7 as the byte displacement would confuse an element index with an address offset. Index zero is already at the base and does not add another element to the count.

Question 5 of 10

In little-endian memory, sw stores 0x86C4217D at 0x3000. What value does lbu t0, 2(s0) produce when s0 = 0x3000?
  1. 0x000000C4
  2. 0x00000021
  3. 0xFFFFFFC4
  4. 0x00000086
Show answerHide answer
Answer: A
The stored bytes are 0x7D, 0x21, 0xC4 and 0x86 at increasing addresses. Offset 2 selects 0xC4, and lbu zero-extends it to 0x000000C4. The sign-extended result would require lb, not the unsigned byte load used here.

Question 6 of 10

What lui upper field and signed addi immediate construct 0x00002ABC in RV32?
  1. 0x2 and +2748
  2. 0x2 and −1348
  3. 0x3 and −1348
  4. 0x3 and +2748
Show answerHide answer
Answer: C
The low 12-bit pattern 0xABC represents −1348. Starting from 0x3000 and subtracting 1348 reaches 0x2ABC, so the upper field is 0x3. Treating 2748 as a positive addi operand fails because it exceeds the signed 12-bit positive limit.

Question 7 of 10

A caller keeps a value in t3, calls a conforming helper, then needs the original value. What must the caller arrange?
  1. Restore t3 only inside the helper
  2. Treat t3 as a callee-saved register
  3. Save t3 after the helper returns
  4. Protect the value before the call
Show answerHide answer
Answer: D
t3 is caller-saved, so a conforming helper may overwrite it. The caller must save the live value or arrange an equivalent preserved location before calling. The helper has no obligation to restore temporary registers merely because its caller will need them.

Question 8 of 10

In RV32, an auipc t0, 0x2 executes at 0x4000, followed by addi t0, t0, -24. What final value is in t0?
  1. 0x00005FEC
  2. 0x00005FE8
  3. 0x00006018
  4. 0x00002000
Show answerHide answer
Answer: B
auipc adds 0x2000 to its own address, producing 0x6000. Subtracting 24 leaves 0x5FE8. Using the address of the following instruction as the pc base would incorrectly increase the result by 4 bytes.

Question 9 of 10

A byte containing 0xB2 is loaded once with lb and once with lbu. What decimal results do the two loads produce, in that order?
  1. −78 and 178
  2. 178 and 178
  3. −78 and −78
  4. −50 and 178
Show answerHide answer
Answer: A
As an 8-bit signed value, 0xB2 is 178 minus 256, or −78. lb sign-extends that value, while lbu produces unsigned 178. Making both results positive ignores the sign-extension rule of the signed load.

Question 10 of 10

An array of structures uses struct rec { int key; int value; }; under RV32 ILP32, with no extra padding. Its base is 0x5000. What address is read for A[3].value, and what signed byte does lb return from that address if the field holds 0x000000E9 in little-endian memory?
  1. 0x5018 and −23
  2. 0x501C and 233
  3. 0x5010 and −23
  4. 0x501C and −23
Show answerHide answer
Answer: D
Each structure occupies 8 bytes, so element 3 starts 24 bytes from the base; its second field adds 4, giving 0x501C. The first byte there is 0xE9, which lb sign-extends to −23. Returning 233 would apply the unsigned extension rule.
Set 2: Arithmetic and paths through a program10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

What 32-bit hexadecimal result does RV32I produce when add adds 1 to 0xFFFFFFFF?
  1. 0xFFFFFFFF
  2. 0x80000000
  3. 0x00000000
  4. 0x7FFFFFFF
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

What real instruction implements bgt a2, a3, Done?
  1. blt a2, a3, Done
  2. blt a3, a2, Done
  3. bge a3, a2, Done
  4. bge a2, a3, Done
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

After srli t0, a0, 8 and andi t0, t0, 0x1F, what value is in t0 if a0 = 0x0000D6B9?
  1. 0x00000016
  2. 0x000000D6
  3. 0x00000019
  4. 0x00000006
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

For signed a0 = -11, what results do srai t0, a0, 2 and C integer -11 / 4 produce, respectively?
  1. −3 and −2
  2. −2 and −3
  3. −3 and −3
  4. −2 and −2
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

a0 = 0xFFFFFFFE and a1 = 0x00000003. Which of blt a0, a1, L and bltu a0, a1, L takes its branch?
  1. Only bltu
  2. Both branches
  3. Neither branch
  4. Only blt
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

A translation of if (x != y) r = 8; else r = 3; branches to Else when equality holds. What goes after the assignment of 8 so that r stays 8 on the unequal path?
  1. A branch back to the comparison
  2. A jump to after the else-part
  3. A second assignment of 3
  4. A reload of x before Else
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

The loop while (A[i] == 6) i++; starts at i = 1 with A = {6, 6, 6, 4, 6}. What is i when it exits?
  1. 2
  2. 4
  3. 3
  4. 5
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

A leaf runs the code below on a 4-byte integer array {2, -5, 9}, with its base in a0 and count 3 in a1. Each displayed instruction is one machine instruction. What result and dynamic instruction count does it produce?

addi t0, zero, 0
addi t1, zero, 0
L: bge t0, a1, D
slli t2, t0, 2
add t2, a0, t2
lw t3, 0(t2)
add t1, t1, t3
addi t0, t0, 1
jal zero, L
D: addi a0, t1, 0
jalr zero, 0(ra)
  1. 6 and 25
  2. 9 and 26
  3. 6 and 24
  4. 6 and 26
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

At 0x8000, jalr ra, 7(t0) runs with t0 = 0x903A. What target is used, what goes into ra, and what instruction can return through that link?
  1. 0x9041; 0x8004; jalr zero, 0(ra)
  2. 0x9040; 0x8004; jalr zero, 0(ra)
  3. 0x9040; 0x8000; jalr zero, 0(ra)
  4. 0x9040; 0x8004; jal zero, 0(ra)
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

The C condition is p != 0 && *p == 9, with p in a0. For the then-path, what control-flow order preserves the condition and avoids dereferencing a null pointer?
  1. Reject null p; load *p; reject unequal 9
  2. Load *p; reject null p; compare with 9
  3. Reject nonnull p; load *p; reject unequal 9
  4. Reject null p; compare p with 9; load *p
Show answerHide answer

Save this lesson to see this answer.

Set 3: Instruction fields and relative targets10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

A B-type bne a2, zero, Loop is at 0x4018, with Loop at 0x400C. Using opcode 0x63 and funct3 = 001, what encoded word reaches Loop?
  1. 0xFE0618E3
  2. 0x00061663
  3. 0xFE061AE3
  4. 0xFE061CE3
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

At address 0x6000, the word 0x000022B7 is followed by 0xFE028293 at 0x6004. What value is left in t0? These decode as U-type LUI and I-type ADDI, respectively.
  1. 0x00002020
  2. 0x00001FDC
  3. 0x00007FE0
  4. 0x00001FE0
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

Decode 0xFEC12623. The opcode 0x23 selects a store and funct3 = 010 selects a word. What assembly instruction does it represent?
  1. sw a2, -12(sp)
  2. lw a2, -20(sp)
  3. sw sp, -20(a2)
  4. sw a2, -20(sp)
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

In sw a2, 20(sp), which register supplies the rs1 field?
  1. a2 (x12)
  2. zero (x0)
  3. ra (x1)
  4. sp (x2)
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

What is the 12-bit immediate pattern for addi t2, s1, -36?
  1. 1111 1101 1100
  2. 0000 0010 0100
  3. 1111 1101 1011
  4. 1111 1110 1100
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

What single change converts an R-type ADD into SUB with the same registers?
  1. Set opcode to 0100011
  2. Set funct3 to 100
  3. Set funct7 to 0100000
  4. Set rd to the first source
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

A branch at 0x308C targets 0x3068. What signed byte offset is encoded?
  1. −40
  2. −36
  3. −9
  4. +36
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

A jal at 0x1200 has signed offset +84 and destination ra. What target and return address result?
  1. 0x1258 and 0x1204
  2. 0x1254 and 0x1204
  3. 0x1254 and 0x1258
  4. 0x1350 and 0x1204
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

Using I-type fields, what word encodes addi t2, s1, -36? Here t2 = x7, s1 = x9, ADDI opcode is 0x13, and funct3 = 000.
  1. 0xFDC48393
  2. 0x02448393
  3. 0xFDC38493
  4. 0xFDC483B3
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

A branch and its target begin in the same section at offsets 0x30 and 0x54. The section moves from 0x9000 to 0xB000 without changing its internal layout. What branch offset and target address result?
  1. +0x24 and 0x9054
  2. +0x2024 and 0xB054
  3. +0x24 and 0xB054
  4. +0x20 and 0xB054
Show answerHide answer

Save this lesson to see this answer.

Set 4: Calls, recursion and pointer access10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

A conforming RISC-V procedure changes s4. What value must s4 hold when it returns?
  1. The caller’s incoming s4 value
  2. The procedure’s first result
  3. The address after the call
  4. The current top-of-stack address
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

In RV32 ILP32, what instruction advances a0 to the next element when it holds an int *?
  1. addi a0, a0, 1
  2. addi a0, a0, 2
  3. addi a0, a0, 8
  4. addi a0, a0, 4
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

A procedure enters with sp = 0x8000, allocates 32 bytes, then executes sw ra, 28(sp). At what address does the saved word begin?
  1. 0x7FE0
  2. 0x801C
  3. 0x7FFC
  4. 0x7FF0
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

A leaf receives an argument in a0, computes into s1, then restores its incoming s1 before returning. What must it do before that restoration to return the computed integer?
  1. Save the caller’s a0 on every exit
  2. Move the computed s1 into a0
  3. Restore s1, then copy s1 into a0
  4. Return with the computed value left in s1
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

A byte-at-a-time string loop stores a byte, increments both pointers, and branches back if that byte is nonzero. How many iterations copy "logic" including its C terminator?
  1. 5
  2. 6
  3. 7
  4. 10
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

A standard-ABI nonleaf must save only ra and s2, each one word. What is the smallest positive frame size that preserves the required stack alignment?
  1. 4 bytes
  2. 8 bytes
  3. 16 bytes
  4. 12 bytes
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

A function receives n in a0 and must return helper(n) + n. helper follows the ABI and may overwrite all caller-saved registers. The function’s incoming ra must also survive. Which plan works with a 16-byte frame?
  1. Save ra and n; call; reload n to t0; add; restore ra and sp
  2. Save ra only; copy n to t0; call; add t0; restore ra and sp
  3. Save n only; call; reload n; add; restore sp; return through ra
  4. Save ra and n; call; restore n to a0; add a0 to itself; restore ra
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

A recursive function allocates a 16-byte frame on every entry, saves n and ra, returns 1 for n < 2, and otherwise returns n * f(n - 1). With initial n = 4 and sp = 0x9000, what result and lowest sp occur?
  1. 24 and 0x8FD0
  2. 6 and 0x8FC0
  3. 24 and 0x8FB0
  4. 24 and 0x8FC0
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

Under RV32 ILP32, memory at 0xA000 holds pointer value 0xB000. At 0xB000 begins an array of 4-byte integers, with element 4 equal to 27. With a0 = 0xA000, what sequence leaves that element in a0?
  1. lw a0, 16(a0); lw a0, 0(a0)
  2. lw t0, 0(a0); lw a0, 16(t0)
  3. addi t0, a0, 16; lw a0, 0(t0)
  4. lw t0, 0(a0); lw a0, 4(t0)
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

A procedure allocates 32 bytes, stores ra at 28(sp) and s0 at 24(sp), then calls a helper. Its exit first adds 16 to sp, then loads from 24(sp) and 28(sp), then adds another 16 and returns. Why can this fail even though sp ends with its original value?
  1. The loads use addresses 16 bytes too high
  2. The helper must preserve every temporary
  3. A frame cannot contain unused bytes
  4. The final stack pointer is misaligned
Show answerHide answer

Save this lesson to see this answer.

Set 5: Build, link and start a program10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

What file does gcc -c module.c produce without linking?
  1. An executable process image
  2. An assembly source listing
  3. A relocatable object file
  4. A shared-library symbol cache
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

Which component maps executable code and data into a new process and prepares its initial stack?
  1. The compiler
  2. The loader
  3. The assembler
  4. The static linker
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

An object file defines main but calls mix, which is defined in a second object. What lets the linker connect that call to the definition?
  1. The debug line table and source file positions
  2. The instruction opcodes and a shared register convention
  3. The initial stack layout and executable entry point
  4. The symbol information and call relocation records
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

A local label is referenced before its definition later in the same assembly section. How can an assembler finish that branch?
  1. Record the reference and resolve its position later
  2. Require the loader to parse the source label
  3. Treat the label name as an immediate integer
  4. Emit the branch with a permanently zero offset
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

A program was statically linked with a library’s implementation of filter. The library archive is then fixed, but the executable is left untouched. What code does the executable contain for filter?
  1. The original code copied during linking
  2. The fixed archive code on its next call
  3. A newly interpreted version of the source
  4. A PLT stub that loads shared replacement code
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

A Linux-targeting RISC-V program puts 93 in a7, 7 in a0, then executes ecall. What service is requested?
  1. Write 7 bytes to a standard stream
  2. Return 93 to the current caller
  3. Exit the process with status 7
  4. Allocate a stack frame of 7 bytes
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

A linker places text at 0x20000 and data at 0x30000000. Module P contributes 0x180 text bytes and 0x30 data bytes; Q follows immediately. A call in P is at text offset 0x70; its Q target is at Q text offset 0x20. Q’s variable is at Q data offset 0x14. What are the call’s byte displacement and the variable’s address?
  1. +0x1A0 and 0x30000044
  2. +0x130 and 0x30000194
  3. +0x12C and 0x30000044
  4. +0x130 and 0x30000044
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

An RV32I listing at 0x1000 contains the following expansion choices: li t0, 5000 uses two instructions, mv a0, t0 one, and call Helper an unrelaxed two-instruction pair. Helper returns normally. At what address does execution resume after the call, and how many code bytes precede that resumption?
  1. 0x100C and 12 bytes
  2. 0x1014 and 20 bytes
  3. 0x1010 and 16 bytes
  4. 0x1018 and 24 bytes
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

A library function starts at offset 0x2C0 in a shared library. A local caller is at offset 0x90; a function pointer outside the library is stored in a GOT slot. The library base changes from 0x40000000 to 0x50000000. Which description correctly handles both references?
  1. Add 0x10000000 to the local displacement and keep every GOT entry
  2. Keep the local target at 0x400002C0; relocate only the calling instruction
  3. Subtract 0x10000000 from the local distance and clear the whole GOT
  4. Keep the +0x230 local distance; bind the external GOT target as needed
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

An executable linked successfully against a shared library. Later, that library is absent, and loading the executable fails before main. What explains the failure, and what would change for code statically included from that library?
  1. Loading needs the shared file; included code would already be in the executable
  2. Linking repeats at launch; included code would still need the original archive
  3. The input object is missing; included code would still need every input object
  4. The file has no machine code; included code would bypass the process loader
Show answerHide answer

Save this lesson to see this answer.

Set 6: Evaluate designs and diagnose assembly10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

What RISC-V instruction pair performs the memory-add operation expressed by x86 add eax, [ebx+12], with eax corresponding to a0 and ebx to s1?
  1. addi a0, a0, 12; add a0, a0, s1
  2. lw a0, 12(s1); add a0, a0, a0
  3. sw a0, 12(s1); add a0, a0, s1
  4. lw t0, 12(s1); add a0, a0, t0
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

A processor has a load-store ISA and condition flags. What can be concluded about the claim that flags make it CISC?
  1. Flags imply two-operand arithmetic, so the claim holds.
  2. Flags can appear in RISC designs, so the claim fails.
  3. A load-store ISA cannot use flags, so the description conflicts.
  4. Flags imply combined memory arithmetic, so the claim holds.
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

Version P executes 0.8 billion instructions at CPI 1.5 and 2 GHz. Version Q executes 1.0 billion instructions at CPI 1.0 and 2 GHz. What execution times result for P and Q?
  1. 0.60 s and 0.50 s
  2. 0.40 s and 0.50 s
  3. 0.60 s and 1.00 s
  4. 1.20 s and 1.00 s
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

A program has 120 machine instructions. If 72 use 16-bit encodings and the rest use 32-bit encodings, how many code bytes does it occupy?
  1. 240 bytes
  2. 384 bytes
  3. 336 bytes
  4. 408 bytes
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

Design P executes 1.2 billion instructions at CPI 1.1 and 2.2 GHz. Design Q executes 0.9 billion instructions at CPI 1.8 and 2.7 GHz. A proposed optimization cuts Q’s instruction count by 10% but raises its CPI by 20%, leaving its frequency fixed. How does optimized Q compare with P?
  1. Q takes 0.648 s and is slower than P
  2. Q takes 0.54 s and is faster than P
  3. Q takes 0.60 s and ties P
  4. Q takes 0.72 s and is slower than P
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

A 200-instruction routine originally uses only 32-bit encodings. After 110 encodings become 16-bit, the instruction sequence and measured runtime are unchanged. What are the new size and the justified performance conclusion?
  1. 580 bytes; 27.5% faster execution
  2. 620 bytes; fewer bytes with unchanged measured speed
  3. 400 bytes; half as many executed instructions
  4. 580 bytes; fewer bytes with unchanged measured speed
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

A binary uses RV32I plus mul, expects the ILP32 calling convention, and invokes RISC-V Linux system calls. A board advertises RV32I, uses ILP32, and runs a simulator with its own service interface. What additional requirements must be met before that binary can run as intended?
  1. mul support and the simulator’s current service interface
  2. mul support and the expected Linux service interface
  3. RV32I alone and the expected Linux service interface
  4. mul support and the matching ILP32 convention alone
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

An RV32 ILP32 array contains struct sample { int id; int reading; }; without extra padding. A pointer to element 2 is in a0. The next element’s reading is 31. What address calculation and load retrieve that reading without changing a0?
  1. addi t0, a0, 4; lw t1, 4(t0)
  2. addi t0, a0, 8; lw t1, 0(t0)
  3. addi t0, a0, 8; lw t1, 4(t0)
  4. addi t0, a0, 12; lw t1, 4(t0)
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

This leaf must return the sum of n integers, with n nonnegative. It is called with n = 0 and an address that must not be read. What minimal control-flow repair avoids the invalid access while preserving positive-count behavior?

addi t1, zero, 0
L: lw t0, 0(a0)
add t1, t1, t0
addi a0, a0, 4
addi a1, a1, -1
bne a1, zero, L
D: addi a0, t1, 0
ret
  1. Use an unsigned byte load in the loop
  2. Insert beq a1, zero, D before L
  3. Change the final bne to a signed blt
  4. Initialize t1 to the first array element
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

A hand-written routine reduces executed instructions from 50 million to 40 million on the same 2 GHz processor. Average CPI rises from 1.2 to 1.6. What happens to time, and what does that show about the instruction-count claim?
  1. It falls from 30 ms to 24 ms; the count reduction determines time
  2. It rises from 60 ms to 64 ms; CPI offsets the count reduction
  3. It rises from 30 ms to 32 ms; CPI offsets the count reduction
  4. It rises from 24 ms to 32 ms; CPI offsets the count reduction
Show answerHide answer

Save this lesson to see this answer.

Stuck on your own question? Homework Help

Key Terms

82 Key Terms, each with its definition

A

Application binary interface
An application binary interface is the software agreement on calling conventions, data layout and other rules for binary cooperation.
An ISA match cannot repair a disagreement about which register contains a function’s return value.
Arithmetic right shift
An arithmetic right shift moves bits toward lower positions and fills high positions with the original sign bit.
Shifting signed −7 right by 1 gives −4 because the result rounds toward negative infinity.
Assembler
An assembler converts assembly text to encoded instructions and data while recording symbols and references for later resolution.
An assembler expands call and can leave relocation entries for a function defined in another module.
Assembler directive
An assembler directive controls data emission, section selection, alignment or symbol metadata without being executed by the processor.
The directive .word 5 emits a 4-byte value, while .text selects where subsequent code belongs.
Assembly language
Assembly language is readable, ISA-specific text that names instructions and operands before an assembler encodes them as bits.
The text add a0, a1, a2 names an operation that has a defined RISC-V machine-code encoding.

B

Basic block
A basic block is a straight-line instruction sequence entered at its beginning and left only at its end during normal control flow.
A label targeted by a branch begins a new block that a compiler can analyze separately.
Binary compatibility
Binary compatibility allows existing machine-code software to run under matching ISA, ABI and environment requirements without recompilation.
Using an optional extension adds a hardware requirement beyond support for the base ISA.
Bit mask
A bit mask is a pattern used with a bitwise operation to select, clear, set or toggle particular positions in another pattern.
ANDing with 0xFF retains the low byte while clearing all higher register positions.
Branch displacement
A branch displacement is the signed byte distance from the branch instruction’s own address to its intended target address.
A backward target gives a negative displacement even when both addresses are positive.
Byte addressing
Byte addressing is a memory organization in which each address identifies one byte, so larger values span consecutive addresses.
Two adjacent 32-bit array elements begin at addresses that differ by 4, not by 1.

C

Callee-saved register
A callee-saved register must retain its incoming value after a call, requiring restoration by a procedure that changes it.
A function can use s1 internally if it restores the caller’s original s1 before returning.
Caller-saved register
A caller-saved register may be overwritten by a called procedure, so its caller must protect any current value needed afterward.
A live value in t2 needs saving before a helper that is free to use that register.
Calling convention
A calling convention is the agreement specifying argument locations, result locations and register preservation across procedure calls.
RISC-V integer procedures pass their first arguments in a0 through a7.
CISC
CISC is an instruction-set design approach supporting richer operations and addressing forms, commonly through variable-length encodings.
An x86 instruction can combine reading a memory operand with adding it to a register.
Code density
Code density describes how compactly machine instructions represent a program, commonly compared through the bytes needed for its code.
Compressed instructions can reduce code bytes while preserving the number of operations executed.
Compiler
A compiler translates a program into another representation, commonly selecting and optimizing machine instructions for a target ISA.
A RISC-V-targeting compiler can replace repeated index calculations with a pointer increment.
Compressed instruction
A compressed instruction is a shorter encoding of an operation, such as a 16-bit RISC-V C form used alongside 32-bit instructions.
If half of 100 instructions use 2 bytes, total code size is 300 bytes instead of 400.
Condition flags
Condition flags are state bits recording properties of a result or comparison that later instructions can test.
x86 can compare registers and then branch on flags, while RISC-V branches compare directly.
Conditional branch
A conditional branch changes the next instruction address only when its comparison succeeds, otherwise continuing in sequence.
bne t0, t1, Loop returns to Loop only while the two register patterns differ.
Cycles per instruction
Cycles per instruction is the average number of clock cycles used per executed instruction for a particular program and implementation.
A program with more instructions can finish sooner if its CPI is sufficiently smaller.

D

Dereference
A dereference accesses the object identified by a pointer value, implemented by an appropriate memory load or store.
lw t0, 0(a0) reads a 4-byte integer through the address currently held in a0.
Disassembly
Disassembly is converting encoded instruction bits back into instruction names and operands using the ISA’s decoding rules.
A disassembler identifies the opcode before treating other bits as registers or immediate fragments.
Displacement addressing
Displacement addressing forms an effective memory address by adding a signed constant offset to the value in a base register.
In lw t0, 20(s1), the loaded word begins 20 bytes beyond the address stored in s1.
Dynamic instruction count
Dynamic instruction count is the number of instructions actually executed along a program’s path, including repeated loop visits.
A loop’s final failed test contributes to execution count even though its body does not run again.
Dynamic linking
Dynamic linking connects a program to separate shared-library code during loading or execution, requiring compatible libraries to be available.
A missing shared library can prevent a previously linked executable from starting.

E

Element stride
An element stride is the byte distance between corresponding positions in consecutive elements of an array or repeated layout.
An array of two-int structures has an 8-byte stride while its second field has offset 4.
Epilogue
An epilogue is the exit sequence that restores saved state, releases the current stack frame and returns control to the caller.
Restoring registers before increasing sp keeps their offsets relative to the allocated frame.

F

Fall-through
Fall-through is execution continuing into the next instruction or code block because no control transfer skips it.
An if-else translation needs a jump after the then-part to avoid falling through into the else-part.
Function field
A function field refines an opcode to select a specific operation, such as addition or subtraction within register arithmetic.
The same registers and opcode can represent ADD or SUB depending on the funct7 bits.

I

Immediate
An immediate is a constant represented by bits within an instruction, available without first fetching the constant from memory.
The final operand in addi t0, t0, 1 is encoded in the instruction as an immediate.
Implied bit
An implied bit is a value reconstructed from an encoding rule without occupying a stored bit position of its own.
The low bit of a B-type branch offset is implied zero because the byte distance is even.
Indirect jump
An indirect jump obtains its target from a register-based calculation, allowing a stored address to choose the next instruction.
jalr can implement a function-pointer call, a return or a jump-table dispatch.
Instruction format
An instruction format is the arrangement of fields inside an encoded instruction, assigning bit positions to operands and controls.
S-type has a split immediate where R-type would have a function field and destination register.
Instruction set architecture
An instruction set architecture is the contract defining the instructions, registers and machine behavior visible to software.
Two processors can implement the same ISA with different internal circuits and different execution speeds.
Integer register
An integer register is a small processor storage location holding a bit pattern used by integer operations or address calculations.
In RV32I, each of the 32 integer registers holds 32 bits, whether used for a value or an address.

J

Jump and link
Jump and link is a control transfer that also writes the following instruction’s address into a destination register for later use.
jal ra, Helper records where the caller should resume when Helper returns.
Jump table
A jump table is an indexed collection of code addresses used to transfer control efficiently among several cases.
A switch dispatch must check its case range before reading an address from its jump table.
Just-in-time compilation
Just-in-time compilation generates machine code during execution, often focusing compilation effort on code that runs repeatedly.
A virtual machine can first interpret bytecode and then compile a frequently executed method.

L

Lazy binding
Lazy binding delays resolving a shared-library procedure until its first call and records the result for later calls.
The first call can invoke the dynamic linker, while subsequent calls use an already stored address.
Leaf procedure
A leaf procedure is a procedure that makes no calls to other procedures, allowing it to keep its incoming return address untouched.
A leaf using only argument and temporary registers may return without allocating a stack frame.
Linker
A linker combines object modules and libraries, lays out executable content, resolves symbols and patches address-dependent references.
An undefined-reference error means the final link could not find a required symbol definition.
Little-endian byte order
Little-endian byte order stores a multibyte value’s least significant byte at its lowest memory address.
A word store of 0x12345678 places 0x78 in the first byte and 0x12 in the fourth.
Load address
Load address is an assembler operation that forms a label’s address as a register value without reading the object stored there.
After la t0, Item, a separate load is needed to read the value held at Item.
Load-store architecture
A load-store architecture uses dedicated memory-transfer instructions while arithmetic and logic operate on register values and immediates.
To add an array element, RISC-V loads it into a register before issuing an arithmetic instruction.
Loader
A loader prepares an executable as a running process by mapping its contents and establishing initial memory and register state.
Startup begins only after the program’s code, data and initial stack have been prepared.
Logical right shift
A logical right shift moves bits toward lower positions and fills vacated high positions with zeros, supporting unsigned scaling.
A logical right shift of a negative signed pattern can produce a large positive result.
Loop bound
A loop bound limits the iterations permitted by a loop’s comparison, determining which indexes or addresses its body can access.
A test that exits when the index reaches the length keeps an array index below that length.

M

Micro-operation
A micro-operation is an internal operation into which a processor can translate an ISA instruction for execution by its core.
One externally visible x86 instruction can require several simpler internal micro-operations.
Microarchitecture
A microarchitecture is a processor’s internal design for implementing the operations and behavior required by its instruction set.
Changing execution hardware can improve a chip’s speed while preserving its existing machine-code interface.

N

Natural alignment
Natural alignment places a memory access at an address divisible by the access size, such as a multiple of 4 for a word.
A compiler can insert padding so a halfword or word begins at a suitably aligned address.
Nibble
A nibble is a group of 4 bits, matching one hexadecimal digit and making binary instruction words easier to convert to hex.
A complete 32-bit instruction divides into eight nibbles after all its fields have been joined.
Null terminator
A null terminator is the zero byte marking the end of a C byte string, occupying storage after the last nonzero byte.
A string-copy loop stores the terminator so the destination has a recognizable end too.

O

Object lifetime
An object’s lifetime is the period during which its storage represents a valid object that the program is allowed to access.
Returning a pointer to a local object does not keep that object alive after its function returns.
Opcode
An opcode is an instruction field identifying an operation group and helping determine the layout used to decode its other bits.
The low 7 bits distinguish an RV32I word store from a register arithmetic instruction.
Overflow
Overflow occurs when an arithmetic result lies outside the representable range for the chosen width and signed interpretation.
RV32 addition retains low result bits even when the signed sum cannot fit in 32 bits.

P

PC-relative address
A pc-relative address is formed from an instruction’s own address plus an encoded displacement, keeping a location relative to code.
An auipc sequence can reach local data even when the whole code-and-data group moves.
Pointer
A pointer is a value representing an object or code address, used to locate what a load, store or indirect transfer will access.
A pointer register supplies an address, while a load retrieves the value stored at that address.
Position-independent code
Position-independent code uses relative references and indirection so that its code can execute correctly at different load addresses.
Moving a library and its local targets together preserves the distances used by its local references.
Program counter
The program counter is the register holding the address of the instruction being fetched or executed in the instruction sequence.
A taken branch replaces sequential pc progression with the address calculated for its target.
Prologue
A prologue is the entry sequence that establishes a procedure’s frame and saves the incoming register state it must preserve.
Saving ra before a nested call protects the address needed to return to the outer caller.
Pseudo-instruction
A pseudo-instruction is assembler shorthand that expands into one or more instructions actually defined by the processor’s ISA.
The number of machine instructions produced by li depends on the value of its constant operand.

R

Recursion
Recursion occurs when a procedure calls itself directly or indirectly, creating separate active invocations with their own state.
Factorial reloads an outer call’s saved argument after the inner call returns its result.
Register allocation
Register allocation is the compiler’s assignment of program values to the limited registers available at each point in execution.
An optimized loop can keep its index and accumulated sum in registers across many iterations.
Register field
A register field is a fixed-width part of an instruction encoding that identifies a source or destination register by number.
A 5-bit field represents a0 as register number 10, written 01010 in binary.
Relative relocation
Relative relocation is adjustment of a reference based on the distance between a placed instruction or data location and its target.
Moving one section alone can change a call displacement even when the instruction bytes began unchanged.
Relocatable object file
A relocatable object file holds machine code, data and metadata whose final placement and external references can be completed later.
Producing an object with -c stops before the library and module combination needed for an executable.
RISC
RISC is an instruction-set design approach favoring regular encodings, relatively simple operations and separate memory-transfer instructions.
Adding extensions can enlarge a RISC ISA without removing its load-store organization.

S

Scaled-index addressing
Scaled-index addressing combines a base with an index multiplied by a stride, often adding a displacement to form a memory address.
An x86 memory operand can express base plus four times an index plus 8 in one instruction.
Set less than
Set less than is a comparison operation that writes 1 when the first source is smaller than the second and writes 0 otherwise.
slt and sltu can disagree when a source’s top bit gives it different signed and unsigned values.
Short-circuit evaluation
Short-circuit evaluation stops testing a compound condition once its overall truth value is already determined.
A false first operand of && prevents evaluation of the second operand in C.
Sign extension
Sign extension widens a signed bit pattern by copying its highest bit into the new high positions, preserving its signed value.
Loading byte 0xF0 with lb produces 0xFFFFFFF0, the 32-bit representation of −16.
Spilling
Spilling is storing a live register value in memory so that its register can be reused, then reloading the value when needed.
Extra live values can add stack stores and loads even when the original C has no explicit memory access.
Stack frame
A stack frame is the region allocated for one procedure activation’s saved state, local objects, spills and stack argument space.
Three active calls can hold three distinct copies of the same function’s saved argument.
Stack pointer
The stack pointer is the register marking the current boundary of allocated stack storage, moving as frames are acquired or released.
Subtracting 16 from sp reserves an aligned frame at lower addresses in the RISC-V convention.
Static linking
Static linking includes required library object code in the executable, making that included code part of the program’s own file.
Replacing a static library file does not update code already copied into an older executable.
Stored-program concept
The stored-program concept represents instructions as binary values in memory, allowing a processor to fetch and execute them.
The program counter supplies the address from which the next encoded instruction will be fetched.
Structure field offset
A structure field offset is the byte distance from a structure’s beginning to one member, determined by its ABI layout.
A load adds the field offset to the current object’s address to reach that object’s member.
Symbol table
A symbol table records named definitions and unresolved references used to connect instructions and data across translated modules.
A function can be undefined in one object and defined in another object supplied to the linker.
System call
A system call is a program’s request for an operating-system service through the interface defined by its execution environment.
On RISC-V Linux, ecall with 93 in a7 requests exit, using a0 for the status.

U

Upper immediate
An upper immediate supplies a 20-bit field that lui places above 12 low zeros when constructing an RV32 register value.
A following addi can supply a signed low part to complete a full 32-bit constant.

Z

Zero extension
Zero extension widens a bit pattern by filling new high positions with zeros, preserving the value interpreted as unsigned.
Loading byte 0xF0 with lbu produces the positive value 240 in a 32-bit register.
Zero register
The zero register is the RISC-V register x0, which always reads as zero and discards every value an instruction writes to it.
A jump can write its link to zero when the program does not need to save a return address.

About this lesson

By StudeliReport a mistake

Make your own Study Materials

Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.

Get Started