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., ).
Machine Language: A computer-readable format consisting entirely of binary digits ('s and 's).
The RISC-V Architecture
RISC-V was developed in 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:
Simplicity favors regularity: Consistent instruction formats and operand counts make hardware encoding and handling easier.
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.
Smaller is faster: Limiting the number of registers helps maintain high speeds.
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 , the RISC-V assembly is .Subtraction: The mnemonic is
sub. For the C code , the RISC-V assembly is .
In these instructions:
Destination Operand: The register to which the result is written ().
Source Operands: The registers on which the operation is performed ().
Complex Expressions
Complex C code is broken down into multiple simple RISC-V instructions. For example, the operation would be translated as:
(where is a temporary location for )
(where the result is stored in )
Logical Instructions
Logical operations permit bit-level manipulation:
and: Used for masking bits. For example, masking all but the least significant byte: .
or: Used for combining bit fields. For example: .
xor: Used for inverting bits. Note that , where is represented as .
Shift Instructions
Shifting moves bits within a register. Shifting left by is equivalent to multiplying by , while shifting right by is equivalent to dividing by .
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 -bit multiplication, resulting in a -bit product. This is stored in two parts:
mul: Returns the lower bits of the result.mulh: Returns the upper bits, treating operands as signed.Division/Remainder:
divprovides the -bit quotient (), andremprovides the -bit remainder ().
Operands: Registers, Memory, and Immediates
Registers
RISC-V is a 32-bit architecture, meaning it operates on -bit data. It includes registers, each bits wide ( to ). Registers are significantly faster than memory.
Register Table and Usage:
zero (x0): Constant value .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 -bit word consists of bytes; therefore, the address of a word increments by .
Load Word (lw): Reads data from memory into a register. Format: . Address = .
Store Word (sw): Writes data from a register into memory. Format: .
Immediates (Constants)
Immediates are constants used directly in instructions, such as addi (add immediate). RISC-V uses -bit signed immediates.
Generating 32-bit Constants: To load a full -bit constant, the
lui(load upper immediate) instruction puts a -bit immediate into the upper bits of a register, followed by anaddito set the lower bits.Sign Extension Caveat: Because
addisign-extends its -bit immediate, if bit of the constant is , the programmer must increment the upper bits in theluiinstruction by 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 .
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
elseblock.While Loops: The condition is tested at the start using a branch (e.g.,
beqto 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: .
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., , ).
lb / lbu: Load byte (signed/unsigned).
sb: Store byte. Strings are often arrays of characters ending in a null character ().
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 .
Return Value: Placed in register .
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): , , , and the stack above .
Nonpreserved (Caller-Saved): , , and the stack below .
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 and the return address on the stack before making the recursive call to .
Machine Language Formats
RISC-V instructions are bits long and follow six primary formats:
R-Type (Register): used for register-register operations ().
I-Type (Immediate): used for immediate arithmetic and loads ().
S-Type (Store): used for stores ().
B-Type (Branch): used for branches, with a split immediate to represent offsets ().
U-Type (Upper Immediate): used for
lui().J-Type (Jump): used for
jal().
Addressing Modes
RISC-V uses four addressing modes:
Register Only: Operands are in registers (e.g., ).
Immediate: A -bit signed immediate is the operand (e.g., ).
Base Addressing: Address is the sum of a register (base) and an immediate (offset) (e.g., ).
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 ().
Global Data: Global and static variables ().
Heap: For dynamic data allocated during runtime.
Stack: For local variables and function call management ().
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 (-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 (-bit).
RVD: Double-precision (-bit).
RVQ: Quad-precision (-bit). It includes separate floating-point registers (
f0 - f31). Instructions are suffixed by precision, such asfadd.sorfadd.d. A critical instruction for signal processing isfmadd(floating-point multiply-add), which calculates .