RISC-V Architecture and Instruction Set Study Notes

Introduction to Architecture

Architecture is defined as the programmer’s view of a computer, which is primarily characterized by its instruction set and operand locations. This level of abstraction allows programmers to interact with the system without needing to understand the underlying hardware details. In contrast, microarchitecture refers to the specific hardware implementation of an architecture, which is explored in subsequent studies of hardware design.

Assembly and Machine Language

Computer instructions are commands in the language of the computer. They typically exist in two formats:

  • Assembly Language: A human-readable format of instructions (e.g., add a,b,cadd\, a, b, c).

  • Machine Language: A computer-readable format consisting entirely of binary digits (11's and 00's).

The RISC-V Architecture

RISC-V was developed in 20102010 by Krste Asanovic, David Patterson, and their colleagues at UC Berkeley. It is notable as the first widely accepted open-source computer architecture. Learning one architecture, such as RISC-V, simplifies the process of learning others due to shared fundamental principles.

Underlying Design Principles

As articulated by Hennessy and Patterson, four key principles guide architectural design:

  1. Simplicity favors regularity: Consistent instruction formats and operand counts make hardware encoding and handling easier.

  2. Make the common case fast: By including only simple, frequently used instructions, hardware can be smaller and faster. Complex operations are performed by combining these simple instructions.

  3. Smaller is faster: Limiting the number of registers helps maintain high speeds.

  4. Good design demands good compromises: Flexibility, such as supporting multiple instruction formats, requires balanced trade-offs between simplicity and functionality.

Instructions: Arithmetic and Logic

Basic Arithmetic

Arithmetic instructions in RISC-V follow a strict regularity where the mnemonic indicates the operation, followed by the destination and source operands.

  • Addition: The mnemonic is add. For the C code a=b+c;a = b + c;, the RISC-V assembly is add a,b,cadd\, a, b, c.

  • Subtraction: The mnemonic is sub. For the C code a=b−c;a = b - c;, the RISC-V assembly is sub a,b,csub\, a, b, c.

In these instructions:

  • Destination Operand: The register to which the result is written (aa).

  • Source Operands: The registers on which the operation is performed (b,cb, c).

Complex Expressions

Complex C code is broken down into multiple simple RISC-V instructions. For example, the operation a=b+c−d;a = b + c - d; would be translated as:

  • add t,b,cadd\, t, b, c (where tt is a temporary location for b+cb + c)

  • sub a,t,dsub\, a, t, d (where the result is stored in aa)

Logical Instructions

Logical operations permit bit-level manipulation:

  • and: Used for masking bits. For example, masking all but the least significant byte: 0xF234012F AND 0x000000FF=0x0000002F0xF234012F\, AND\, 0x000000FF = 0x0000002F.

  • or: Used for combining bit fields. For example: 0xF2340000 OR 0x000012BC=0x000012BC0xF2340000\, OR\, 0x000012BC = 0x000012BC.

  • xor: Used for inverting bits. Note that A XOR −1=NOT AA\, XOR\, -1 = NOT\, A, where −1-1 is represented as 0xFFFFFFFF0xFFFFFFFF.

Shift Instructions

Shifting moves bits within a register. Shifting left by NN is equivalent to multiplying by 2N2^N, while shifting right by NN is equivalent to dividing by 2N2^N.

  • sll / slli: Shift left logical (fills with zeros).

  • srl / srli: Shift right logical (fills with zeros).

  • sra / srai: Shift right arithmetic (sign-extends the most significant bit).

Multiplication and Division

RISC-V handles 32×3232 \times 32-bit multiplication, resulting in a 6464-bit product. This is stored in two parts:

  • mul: Returns the lower 3232 bits of the result.

  • mulh: Returns the upper 3232 bits, treating operands as signed.

  • Division/Remainder: div provides the 3232-bit quotient (s3=s1/s2s3 = s1 / s2), and rem provides the 3232-bit remainder (s4=s1%s2s4 = s1 \% s2).

Operands: Registers, Memory, and Immediates

Registers

RISC-V is a 32-bit architecture, meaning it operates on 3232-bit data. It includes 3232 registers, each 3232 bits wide (x0x0 to x31x31). Registers are significantly faster than memory.

Register Table and Usage:

  • zero (x0): Constant value 00.

  • ra (x1): Return address.

  • sp (x2): Stack pointer.

  • gp (x3): Global pointer.

  • tp (x4): Thread pointer.

  • t0-t2 (x5-x7): Temporaries.

  • s0/fp (x8): Saved register / Frame pointer.

  • s1 (x9): Saved register.

  • a0-a1 (x10-x11): Function arguments / Return values.

  • a2-a7 (x12-x17): Function arguments.

  • s2-s11 (x18-x27): Saved registers.

  • t3-t6 (x28-x31): Temporaries.

Memory Operands

Memory is used to store data that cannot fit in the registers. While large, memory is slower than registers.

  • Byte-Addressability: RISC-V is byte-addressable. Each byte has a unique address. A 3232-bit word consists of 44 bytes; therefore, the address of a word increments by 44.

  • Load Word (lw): Reads data from memory into a register. Format: lw destination,offset(base)lw\, destination, offset(base). Address = base+offsetbase + offset.

  • Store Word (sw): Writes data from a register into memory. Format: sw source,offset(base)sw\, source, offset(base).

Immediates (Constants)

Immediates are constants used directly in instructions, such as addi (add immediate). RISC-V uses 1212-bit signed immediates.

  • Generating 32-bit Constants: To load a full 3232-bit constant, the lui (load upper immediate) instruction puts a 2020-bit immediate into the upper bits of a register, followed by an addi to set the lower 1212 bits.

  • Sign Extension Caveat: Because addi sign-extends its 1212-bit immediate, if bit 1111 of the constant is 11, the programmer must increment the upper 2020 bits in the lui instruction by 11 to compensate.

Program Flow and Control Statements

Instructions are stored in memory, and the Program Counter (PC) holds the address of the current instruction. After execution, the PC is typically incremented by 44.

Branching

Branching allows the execution of instructions out of sequence.

  • Conditional Branches: beq (equal), bne (not equal), blt (less than), bge (greater than or equal).

  • Unconditional Jumps: j (jump), jal (jump and link), jr (jump register).

Loops and Conditionals
  • If Statement: Translated using a conditional branch (often testing the opposite of the high-level condition) and an unconditional jump to skip the else block.

  • While Loops: The condition is tested at the start using a branch (e.g., beq to exit).

  • For Loops: Consist of an initialization, a condition check, the loop body, and a loop operation (increment/decrement) before jumping back to the start.

Arrays and Strings

Array Access

Arrays are accessed by initializing a register with the base address of the array. The address of array[i] is calculated as: Base Address+(i×4)\text{Base Address} + (i \times 4).

ASCII Characters and Byte Access

Text characters are represented via ASCII (American Standard Code for Information Interchange), where each character is a unique byte (e.g., ′S′=0x53'S' = 0x53, ′a′=0x61'a' = 0x61).

  • lb / lbu: Load byte (signed/unsigned).

  • sb: Store byte. Strings are often arrays of characters ending in a null character (00).

Function Calls and the Stack

Function calls involve a Caller (the function making the call) and a Callee (the function being called).

Calling Conventions
  • Arguments: Passed in registers a0−a7a0 - a7.

  • Return Value: Placed in register a0a0.

  • Jump and Link (jal): Used to call a function, saving the return address in ra.

  • Return (jr ra): Used to return to the caller by jumping to the address in ra.

The Stack

The stack is a Last-In-First-Out (LIFO) queue in memory used to save variables or registers. It grows downwards from higher to lower addresses. The Stack Pointer (sp) points to the top of the stack.

  • Preserving Registers: Functions must restore registers they overwrite if those registers are needed by the caller.

    • Preserved (Callee-Saved): s0−s11s0-s11, spsp, rara, and the stack above spsp.

    • Nonpreserved (Caller-Saved): t0−t6t0-t6, a0−a7a0-a7, and the stack below spsp.

Non-Leaf and Recursive functions
  • Non-leaf functions: Functions that call other functions. They must save their own return address (ra) on the stack before making a call.

  • Recursive functions: Functions that call themselves. For example, a factorial function must save the current value of nn and the return address on the stack before making the recursive call to factorial(n−1)factorial(n-1).

Machine Language Formats

RISC-V instructions are 3232 bits long and follow six primary formats:

  1. R-Type (Register): used for register-register operations (funct7,rs2,rs1,funct3,rd,opfunct7, rs2, rs1, funct3, rd, op).

  2. I-Type (Immediate): used for immediate arithmetic and loads (imm,rs1,funct3,rd,opimm, rs1, funct3, rd, op).

  3. S-Type (Store): used for stores (imm[11:5],rs2,rs1,funct3,imm[4:0],opimm[11:5], rs2, rs1, funct3, imm[4:0], op).

  4. B-Type (Branch): used for branches, with a split immediate to represent offsets (imm[12,10:5],rs2,rs1,funct3,imm[4:1,11],opimm[12, 10:5], rs2, rs1, funct3, imm[4:1, 11], op).

  5. U-Type (Upper Immediate): used for lui (imm[31:12],rd,opimm[31:12], rd, op).

  6. J-Type (Jump): used for jal (imm[20,10:1,11,19:12],rd,opimm[20, 10:1, 11, 19:12], rd, op).

Addressing Modes

RISC-V uses four addressing modes:

  1. Register Only: Operands are in registers (e.g., add s0,t2,t3add\, s0, t2, t3).

  2. Immediate: A 1212-bit signed immediate is the operand (e.g., addi s4,t5,−73addi\, s4, t5, -73).

  3. Base Addressing: Address is the sum of a register (base) and an immediate (offset) (e.g., lw s4,72(zero)lw\, s4, 72(zero)).

  4. PC-Relative: Address is the sum of the PC and an immediate offset, used for branches and jumps.

Compilation and Memory Layout

Software Life Cycle

A C program is compiled into assembly (.s), then assembled into an object file (.o), and finally linked into an executable file. Using GCC, common flags include:

  • -O1: Basic optimization.

  • -g: Debugging info.

  • -S: Output assembly code only.

  • -c: Output object file only.

Memory Map

A typical RISC-V memory map includes:

  • Text: Program instructions (starts at 0x00010000starts\, at\, 0x00010000).

  • Global Data: Global and static variables (aligned with gpaligned\, with\, gp).

  • Heap: For dynamic data allocated during runtime.

  • Stack: For local variables and function call management (starts at high memory,grows downstarts\, at\, high\, memory, grows\, down).

  • OS & I/O: Reserved regions at the highest and lowest addresses.

Odds and Ends

Endianness
  • Little-Endian: The least significant byte is stored at the lowest address.

  • Big-Endian: The most significant byte is stored at the lowest address. RISC-V typically utilizes little-endian numbering.

Signed vs. Unsigned Operations

RISC-V provides specific instructions for unsigned operations, such as bltu (branch less than unsigned) and lbu (load byte unsigned). Signed variants include blt and lb (which sign-extends).

Compressed Instructions

RISC-V includes a compressed extension (1616-bit instructions prefixed with c.) to reduce code size. Compilers use these common versions (like c.add or c.lw) whenever the operands and immediates fit within the reduced bit fields.

Floating-Point Instructions

RISC-V offers floating-point extensions for different precisions:

  • RVF: Single-precision (3232-bit).

  • RVD: Double-precision (6464-bit).

  • RVQ: Quad-precision (128128-bit). It includes 3232 separate floating-point registers (f0 - f31). Instructions are suffixed by precision, such as fadd.s or fadd.d. A critical instruction for signal processing is fmadd (floating-point multiply-add), which calculates f1=f2×f3+f4f1 = f2 \times f3 + f4.