This repository is designed to share the learnings regarding the fundamentals and advanced concepts of computer architecture, processors and RISC-V. It covers the core components of processors, memory systems, instruction sets, practical applications and much more.
- Introduction to digital world
- Processors
- Binary and Data Representation
- Registers: Processor's Workspace
- Memory System Architecture
- Processor Execution Models
- RISC-V ISA
- Control Flow and Program Structure
- Advanced RISC_V Features and Extensions
- Experimentation with Simulator
Processors are an integral part of our lives. They are present in almost all of the electronic devices we use on a day to day basis from our smartphones all the way to our cars
Termed as the "brain" of the computer, processor in simple words is an extremely fast calculator that can perform the following functions:
Arithmetic, Logic Operations, Comparison, Access memory, Make decisions and Input/Output
Every processor follows a basic this basic cycle billions of times per second
Fetch: Get the next instruction from memoryDecode: Figure out the instructionExecute: Perform the operationWriteback: Store the resultRepeat: Move to the next instruction
Indicates how many instruction cycles processor can attempt in one second. It is measured in Hertz (Hz)
- Simple Processors = 1 instr. per cycle
- Superscalar Processors = 2-4 instr. per cycle
- High-performance Processors = 4-8 instr. per cycle
CPU Performance is calculated using a formula
In computer chip, information is stored using voltage levels. High voltage is represented as 1 and low voltage as 0. It can be converted to other number systems
- Unsigned Numbers - Use all bits for magnitude
- 8 bit: 0 to 255
- 16 bit: 0 to 65535
- Signed Numbers - Use to represent negative numbers. Uses two's complement representation
- 8 bit: -127 to +127
- 16 bit: -32768 to +32767
Registers are tiny storage elements built directly into the processor chip using transistors arranged as flipflops
RISC-V has 32 general purpose registers (x0-x31) plus special registers
Register x0: Hardwired to contain zero only. It is used for copying, clearing, loading and discarding calculationsRegister x1: Return address. Cpu returns here after executing a functionRegister x2: Stack pointer. Points to the top of stackRegister x3: Global PointerRegister x4: Thread PointerRegister x5 - x7: Temporaries. To store temporary valuesRegister x8: Saved Register/Frame Pointer.Register x9: Saved Register. Used to preserve original valuesRegister x10 - x11: Function Arguments/Return Values. Stores first two arguements and return valuesRegister x12 - x17: Arguments. Stores 3-8 function arguementsRegister x18 - x27: Saved RegisterRegister x28 - x31: Temporaries
Caller Saved Registers: Registers: t0-t6, a0-a7 Used to store temporary values. Can be easily overwritten by Callee Function. The caller must save the value if it needs the value later on.
Callee Saved Register: Registers: s0-s11 Callee must save these registers if it modifies them and restore them before returning. Because caller expects these registers to remain unchanged
┌───────────────────────────┐
│ MAIN() │
├───────────────────────────┤
│ Uses t0, a0, a1 │ ← Caller-saved
│ Saves t0 before calling │
│ Calls FUNC() │
├───────────────────────────┤
↓ JAL
┌───────────────────────────┐
│ FUNC() │
├───────────────────────────┤
│ Uses s0, s1 │ ← Callee-saved
│ Saves s0, s1 on stack │
│ Restores before return │
└───────────────────────────┘
Computer memory is organized in a hierarchy based on speed, size and cost:
Virtual Memory creates an illusion that each program has access to a large, private memory space even though the physical memory is limited and shared.
Virtual View (Programs' Perspective)
Physical View (Reality)
Step 1 — Virtual Addresses Every program thinks it has access to a huge memory space. Program A sees addresses from 0x00000000 → 0xFFFFFFFF (4 GB). Program B also sees the same range. But physically, they share the same RAM.
Step 2 — MMU Translates Virtual → Physical The MMU (Memory Management Unit) converts virtual addresses into physical addresses using:
- Page tables (mapping virtual pages → physical pages).
- TLB (Translation Lookaside Buffer) for speed
MMU Translation Process
- Checks TLB for cached translation. If address found the translation of address is quick.
- If not found, it consults Page tables to find the pages:
Virtual Page: 0x10000andPhysical Page: 0x01200 - Combines page address with offset:
Virtual Address: 0x10000000 = Page 0x10000 + Offset 0x000Physical Address: 0x01200000 = Page 0x01200 + Offset 0x000 - Access memory at
0x01200000
In-order processors execute instructions exactly in the sequence they appear in the program one by one.
Advantages
- suitable for simple hardware designs
- lower power consumption
- easier to debug
- lower cost to manufacture
Disadvantages
- limited performance
- cannot perform instruction level parallelism
- stalls on memory access
Out-of-order processors can rearrange instruction execution to improve performance as long as dependencies are respected
Instruction Queue: Buffer(temp storage) holding multiple instructions so that CPU knows what instructions are upcomingReservation Stations: Hold instructions waiting for operands(data). Allows CPU to prepare instructions in advance and execute them when data is readyReorder Buffer: Ensure instructions complete in program order even if instructions finish out of orderRegister Renaming: Eliminate false dependencies. Hardware renames registers to avoid false dependencies
Advantages
- higher performance on typical programs
- better utilization of execution units
- hides memory latency
- exploits instruction-level parallelism
Disadvantages
- complex hardware design
- higher power consumption
- expensive to manufacture
Superscalar processors have multiple execution units and can execute multiple instructions simultaneously.
Resources
- 2-4 Integer ALU's
- 1-2 Multiply/Divide units
- 2-3 Load/Store units (memory access)
- 1-2 Branch units
- 2-4 Floating-point units
- Issue Width: How many instructions can be sent to execution units per cycle
- Execution Width: How many instructions can actually be completed per cycle
Example:
A "4-issue" processor can send 4 instructions to execution units per cycle, but due to dependencies and resource conflicts. might only complete 2.3 instructions per cycle on average
VLIW processors move the complexity of instruction scheduling from hardware to software (the compiler). Each instruction word contains multiple operations that are guaranteed to be independent
Example in RISC Asm:
Note:
Instruction Scheduling: deciding order in which instructions are executed
Execute instructions as soon as their operands(data) are available, regardless of program order. Execution is driven by data availability and not by instruction sequence

Execute same instructions on multiple data elements simultaneously

Instruction Width - no. of bits used to represent single instruction in an ISA.
RISC-V uses 32-bit fixed instruction-width for various reasons:
- simplicity: easier to decode and fetch
- performance: predictable instruction fetch
- alignment: instructions naturally align to 4-byte boundaries
- pipelining: uniform instruction size simplifies pipeline stages
Used when both operands come from register and the result is stored in another register

Detailed Field Analysis
opcode: 0-6 bits. Instruction family (0110011 for R-type arithmetic)rd: 7-11 bits. Destination register (00000-11111 for x0-x31)funct3: 12-14 bits. Operation type (000=ADD/SUB, 001=SLL, etc)rs1: 15-19 bits. First source register (00000-11111 for x0-x31)rs2: 20-24 bits. Second source register (00000-11111 for x0-x31)funct7: 25-31 bits. Additional operation info (0000000=ADD, 0100000=SUB)
Used when one operand is constant (immediate)

In RISC-V only dedicated load/store instructions can access memory. This has several advantages:
- simpler instruction formats
- easier pipeline
- more predictable timing
- better compiler optimization
Supposing at Adress 0x1000 : 0x87654321 (32-bit word). It is of 4 Bytes where:
Byte 0x1000 : 0x21
Byte 0x1001 : 0x34
Byte 0x1002 : 0x56
Byte 0x1003 : 0x78
Assuming x1 contains 0x1000
-
Load Word (32-bit)
lw x2, 0(x1) -> x2 = 0x87654321 -
Load Halfword (16-bit) with sign extension
lh x2, 0(x1) -> x2 = 0x4321
loads bytes from 0x1000 to 0x1001 = 0x4321
since bit 15 = 0 i.e. 0x4321 is positive
x2 = 0x00004321lh x2, 2(x1) -> x2 = 0x8765
loads bytes from 0x1002 to 0x1003 = 0x8765
since bit 15 = 1 i.e. 0x8765 is negative
x2 = 0xFFFF8765 -
Load Halfword Unsigned (16-bit)
lhu x2, 2(x1) -> x2 = 0x8765
loads bytes from 0x1002 to 0x1003 = 0x8765
always zero-extended regardless of sign bit
x2 = 0x00008765 -
Load Byte (8-bit) with sign extension
lw x2, 0(x1) -> x2 = 0x21
loads byte from 0x1000 = 0x21
since bit 7 = 0 i.e. 0x21 is positive
x2 = 0x00000021lw x2, 3(x1) -> x2 = 0x87
loads byte from 0x1003 = 0x87
since bit 7 = 1 i.e. 0x87 is negative
x2 = 0xFFFFFF87 -
Load Word (8-bit) Unsigned
lw x2, 3(x1) -> x2 = 0x87
loads byte 0x1003 = 0x87
always zero-extended regardless of sign bit
x2 = 0x00000087
The only mode in RISC-V to calculate address is:
Base + Offset addressing
Address = register_value + sign_extended_immediate
-
Array Access
Suppose an example
CASE 1: When index cannot be fit inside 12-bit offset
slli x12, x11, 2- x12 = index * 4 (shift left 2 means multiply by 4)
add x12, x10, x12- x12 = base + (index * 4)
lw x13, 0(x12)CASE 2: When index can be fit inside 12-bit offset
lw x13, 20(x10)- loads array[5] directly (20 = 5 * 4) -
Structure Member Access
Suppose an example
Note : int x, int y, int z are all of 4 bytes hence offset is 4lw x14, 4(x10) -
Stack Access
Local variables at negative offsets from frame pointer
lw x15, -8(s0)- loads local variable at fp-8
sw x16, -12(s0)- store to local variable at fp-12 -
Global Variable Access using global pointer
lw x17, 100(gp)- load global variable at gp+100
sw x17, 200(gp)- store global variable at gp+200
Branch Instruction is used when we have to jump to another location if a condition is true

Note
imm[11]: jump offset bitimm[4:1]: next 4-bit of jump offsetimm[10:5]: next 6-bit of jump offsetimm[12:11]: part of jump offset (sign bit)
Instructions
BEQ- branch if equal. checks equality of two registers by subtracting them and equating them to 0BNE- branch if not equal. checks non equality of two registers by subtracting them and ensuring result is not 0BLT- branch if less than (signed). checks if one register is less than other by subtracting them and branches if sign bit is set or 1BGE- branch if greater or equal (signed). checks if one register is greater than other by subtracting them if sign bit is clear or 0BLTU- branch if less than unsigned. checks if one register is less than other by subtracting them and if carry over takes placeBGEU- branch if greater or equal unsigned. checks if one register is greater than other by subtracting them and no carry over is generated
Other Instructions
BLE- branch if less than or equalBEQZ- branch if equal to zeroBNEZ- branch if not equal to zero
for (i = 0; i < n; i++)
x1 = i
x2 = n
li x1, 0 # initialize i = 0
# assume x2 contains n already (loop limit)
loop_start: # <--- loop label
bge x1, x2, loop_end # if i >= n, exit loop
# ---- Loop body ----
# process array[i] or do work here
# -------------------
addi x1, x1, 1 # i++
j loop_start # go back to start of loop
loop_end:
# loop finished
Notes
loop_start - this is a label. CPU doesn't execute it. It just marks the memory the address in memory so instructions inside it know where to go
j - unconditional jump. Always goes back to the instruction at loop_start. This creates the repetition
Instead of counting up (usual looping), countdown loop starts from i = n and ends at i = 0. It is more efficient compared to countup loop as equating to zero is more easier than any other number
RISC-V uses a modular approach where the base instruction set (RV32I or RV64I) is extended with additional instruction sets for specific needs
RISC-V has many instructions for meeting various purposes
| Extension | Description | Purpose and Insructions |
|---|---|---|
| A | Atomic instructions | Atomic memory operations for multithreading |
| C | Compressed instructions | Shorter 16-bit instructions to save memory |
| D | Double-precision floating-point | 64-bit floating math |
| F | Single-precision floating-point | 32-bit floating math |
| G | Shorthand for IMAFD extensions | Short for the combo of IMAFD |
| H | Hypervisor extension | For visual support |
| I | Integer | Basic integer instruction set (required) |
| M | Integer multiplication and division | Hardware multiplication and division |
| P | Packed-SIMD instructions | Multimedia and DSP tasks |
| Q | Quad-precision floating-point | 128-bit floating math |
| V | Vector operations | SIMD operations for AI & HFC |
Notes:
SIMD - Single instructions, multiple data
HFC - High Performance Computing
Examples
| Name | Description |
|---|---|
| RV32I | Supports only basic operations natively on 32 bits |
| RV32GC | General purpose uses on 32 bits with support for compressed instructions |
| RV64IMACV | For intensive and parallel integer-only computing |
| RV64GCV | Theoretically suitable for future personal computers |
I'm using the Venus Simulator for trying out simple programs to grasp more about RISC-V Assembly
Steps:
- Creating a
.datasection where variables are stored message:is a label which stores a string
Steps:
- Creating a
.datasection where variables are stored a:andb:are labels.word 8allocates 4 bytes and stores value8.word 7allocates 4 bytes and stores value7.textdeclares the code section.mainlabel marking the starting point of code
Output:
Answer is 15 or F stored in t3

Steps:
- Defines 4 arguments inside main
- allocate stack space first
- saves the return address
- saves the s0 and moves the stack pointer
- multiplies the arguements
- stores the result in a0
- restores s0, return address and deallocate stack space
Output:
Answer is 360 written as 168 in hexadecimal saved at a0

Steps:
- Defines two variables in the
.datasection - Loads their address first and then load the values of the variable in
a0anda1 - The program is that if
a >= 10andb <= 20thenresult = a + b, if both are falseresult = b*2, elseresult = a - b.
Output:
Since both the values satisfies the condition the result is a + b or 12 + 16 = 28




