# CA2025 Quiz6
> 古孝正
## How does RISC-V support position-independent code?
>My Response:
>The PIC is that code can execute in anywhere in the memory without modfication,but i don't know what relocate is
**Position-Independent Code (PIC)** refers to program code that does not assume a fixed load address in memory. Instead of embedding absolute addresses in instructions, PIC relies on **PC-relative addressing** and **indirection mechanisms** so that all address computations are performed relative to the current program counter (PC).
### How RISC-V Achieves PIC
RISC-V supports position-independent code through a combination of instruction set design and toolchain support. Instead of relying on absolute addressing, RISC-V enables PIC using the following mechanisms:
1. **PC-relative control-flow instructions**, which allow branches and jumps to target locations relative to the current PC.
2. **PC-relative address construction**, which enables access to global data without knowing absolute addresses at compile time.
3. **Relocation**, which defers the resolution of symbol addresses until the linking or loading stage. The assembler records unresolved symbol references, and the linker later computes the correct PC-relative offsets once the final memory layout is determined.
Together, these mechanisms ensure that RISC-V programs can be relocated in memory without requiring changes to the text segment.
---
### PC-Relative Data Access and Relocation in RISC-V
While PC-relative branches and jumps handle control flow, RISC-V accesses global data in a position-independent manner using PC-relative address construction. This is typically achieved with the AUIPC instruction, which computes a base address by adding a 20-bit immediate (shifted left by 12) to the current program counter.
The value generated by AUIPC can be further adjusted using instructions such as addi or combined with load and store instructions to form complete addresses. By computing addresses relative to the PC instead of embedding absolute addresses, RISC-V supports position-independent access to global symbols.
To enable this mechanism, the assembler emits PC-relative relocation entries (e.g., %pcrel_hi and %pcrel_lo), which are resolved by the linker once the final locations of the code and symbols are known.
At link time, the linker computes the offset:
`
offset = symbol_address − PC_of_AUIPC
`
This offset is then split into:
- a high 20-bit portion, placed into the immediate field of the AUIPC instruction, and
- a low 12-bit portion, applied by a subsequent instruction such as addi, lw, or sw.
Bit-Level Example
Assume the following values after linking:
```
PC_of_AUIPC = 0x40001000
symbol_address = 0x40002345
offset = 0x40002345 − 0x40001000 = 0x1345
```
This offset is split as:
```
high 20 bits = 0x1 → used by AUIPC (shifted left by 12)
low 12 bits = 0x345 → used by ADDI or LOAD
```
Resulting instruction sequence:
```
auipc a0, 0x1 # a0 = PC + (0x1 << 12)
addi a0, a0, 0x345 # a0 = symbol_address
```
Because both the PC and the symbol move together when the code is relocated, their relative offset remains unchanged. As a result, the same instruction sequence continues to compute the correct address without modifying the instruction encoding. This demonstrates how AUIPC, combined with relocation, enables position-independent access to global data in RISC-V.
---
### Experimental Validation
```asm=
ebreak
test1:
j test1
test2:
j test2
test3:
j test3
```
This experiment validates that RISC-V jump instructions are position-independent.
Each label (test1, test2, test3) contains a jump instruction that jumps to itself, but each instance resides at a different absolute memory address. If position-independent code (PIC) is correctly supported, the assembled machine code for these jump instructions should be identical, because the PC-relative offset to the target is zero in all cases.
The corresponding dump file is shown below:
```asm=
# Symbols
# 0x40000004 test1
# 0x40000008 test2
# 0x4000000c test3
{ 0x40000000: 00100073 } ebreak
test1:
{ 0x40000004: 0000006f } j test1
test2:
{ 0x40000008: 0000006f } j test2
test3:
{ 0x4000000c: 0000006f } j test3
```
As shown above, all three jump instructions are encoded as 0x0000006f, which corresponds to jal x0, 0. Although the labels are located at different absolute addresses, the assembler encodes each jump using a PC-relative offset of zero. This demonstrates that the jal instruction does not embed absolute addresses and can execute correctly at different memory locations without modifying the instruction code, confirming its position-independent behavior.
The following RISC-V assembly program is used to demonstrate the behavior of auipc
```
1:
auipc a0, %pcrel_hi(foo)
addi a0, a0, %pcrel_lo(1b)
ebreak
foo:
.word 0x12345
```
In this program:
- AUIPC computes a base address relative to the current program counter (PC).
- %pcrel_hi(foo) and %pcrel_lo(1b) generate PC-relative relocation entries instead of fixed immediates.
- The addi instruction completes the address calculation.
- ebreak is used to stop execution so that register values can be inspected.
- foo is a global symbol placed in the data section.
During assembly, the assembler does not know the final address of either the AUIPC instruction or the symbol foo. Therefore:
- It emits relocation entries for %pcrel_hi(foo) and %pcrel_lo(1b)
- No absolute address of foo is embedded in the instruction stream
At link time, the linker determines the final memory layout and resolves the PC-relative offsets. The resulting dump is shown below:
```
# Symbols
# 0x4000000c foo
# 0x40000000 1.1
1:
{ 0x40000000: 00000517 } auipc a0, %pcrel_hi(foo)
{ 0x40000004: 00c50513 } addi a0, a0, %pcrel_lo(1b)
{ 0x40000008: 00100073 } ebreak
foo:
.word 0x12345
```
Decode 0x00000517:
- opcode = 0010111 (AUIPC)
- rd = 01010 → x10 (a0)
- imm = 0x00000
which means `auipc a0 ,0`
Since The AUIPC instruction is located at address 0x40000000 The target symbol foo is located at address 0x4000000c
The distance between the two addresses is:
`foo − PC = 0x4000000c − 0x40000000 = 0x0000000c`
Since this offset is less than 4096 bytes (2¹²), it can be fully represented using the 12-bit immediate of the following ADDI instruction.
Decode 0x00c50513:
- opcode = 0010011 (OP-IMM)
- funt3 = 000(ADDI)
- rd = 01010 → x10 (a0)
- rs1 = 01010 → x10 (a0)
- imm = 0x00c(12)
So this addi instuction means `a0 = 0x40000000 + 0x0000000c = 0x4000000c` which is equal to the address of foo
By decoding the instruction fields and verifying the runtime register values, we confirm that AUIPC combined with PC-relative relocation correctly reconstructs global symbol addresses without relying on absolute addressing.
---
Reference:
https://dramforever.github.io/easyriscv/#position-independence
https://docs.google.com/presentation/d/1rYJeuDUPezDdk01M8ib3XTo15IGLdsMJclzD4Vv9XGQ/edit?slide=id.p8#slide=id.p8
https://docs.google.com/presentation/d/1uAURy-tL-K9oMtUP1gY9034gsOrxbhhDL9MnoTm1NKM/edit?pli=1&slide=id.p17#slide=id.p17
---
## What are the privilege levels defined in RISC-V and why are they needed?
> My Response:
> I answered that the privilege levels are Machine mode and Supervisor mode (I did not successfully answer User mode and Hypervisor mode).
> I explained that privilege levels are used to restrict instruction execution, meaning that some instructions can only be executed in certain privilege modes, while others cannot.
RISC-V defines multiple privilege levels to provide isolation and protection between different layers of the software stack. At any given time, a RISC-V hardware thread (hart) executes in exactly one privilege level, which determines the set of instructions and hardware resources accessible to the running software. Privilege violations are detected by hardware and handled through exceptions and traps.
The RISC-V privileged architecture defines four privilege levels: **User mode (U-mode)**, **Supervisor mode (S-mode)**, **Hypervisor mode (H-mode)**, and **Machine mode (M-mode).** Among these, Machine mode is mandatory, while the others are optional depending on system requirements.
---
### Overview of RISC-V Privilege Levels
- **User mode (U-mode)**
Intended for application programs. Code running in U-mode has the lowest privilege and cannot directly access hardware resources or privileged control registers.
- **Supervisor mode (S-mode)**
Intended for operating system kernels. S-mode provides controlled access to system resources such as virtual memory and enables the kernel to manage processes and system calls.
- **Hypervisor mode (H-mode)**
An optional mode designed to support virtualization. H-mode allows a hypervisor to manage multiple guest operating systems and mediate access to hardware.
- **Machine mode (M-mode)**
The highest privilege level and the only mandatory mode in all RISC-V implementations. M-mode has unrestricted access to the hardware and is responsible for low-level system control.
---
### Experimental Validation
MyCPU currently supports only Machine mode (M-mode) and does not implement User or Supervisor modes. As a result, it is not possible to directly observe privilege separation or privilege violations on MyCPU
To validate the correctness of the RISC-V privilege mechanism, a web-based RISC-V emulator([Easy RISC-V](https://dramforever.github.io/easyriscv/#privileged-architecture-fundamentals)) that supports multiple privilege levels was used instead. The emulator implements both Machine mode and User mode, along with privilege checking for CSR accesses and instructions.
The following program is used to demonstrate privilege transitions and enforcement:
```asm=
la t0, handler
csrw mtvec, t0
lui t0, %hi(0x1800)
addi t0, t0, %lo(0x1800)
# Clear MPP to 0 (User mode)
csrrc zero, mstatus, t0
la t0, user_entry
csrw mepc, t0
mret
handler:
ebreak
user_entry:
# Attempt to access a Machine-mode CSR
csrr a0, mstatus
```
The execution proceeds as follows:
1. The program first sets mtvec to the Machine-mode trap handler, ensuring that all exceptions are redirected correctly.
2. The mstatus.MPP field is cleared to zero, configuring the subsequent mret instruction to return execution to User mode.
3. The address of user_entry is written into mepc, defining the entry point for User-mode execution.
4. Executing mret transfers control to user_entry and lowers the privilege level from Machine mode to User mode.
5. In User mode, the program attempts to access the Machine-mode CSR mstatus, which is illegal and triggers an exception.
6. The processor traps back into Machine mode via mtvec, and execution terminates at the trap handler for observation.
Validation Result:
```
[ Stopped ]
[ Started ]
[ Exception: Illegal instruction (2) | tval = 0x30002573, epc = 0x40000030 ]
```
The emulator correctly reports an illegal instruction exception when the User-mode program attempts to access a Machine-mode CSR, and control is transferred back to the Machine-mode trap handler. This behavior confirms that privilege enforcement between User mode and Machine mode operates as specified in RISC-V systems that support multiple privilege levels.
---
Reference:
https://dramforever.github.io/easyriscv/#privileged-architecture-fundamentals
MYCPU 2-MMIO-TRAP
---
## What is the CSR space and how is it structured?
> My Response:
> I answered that the CSR space consists of a set of registers used to record processor state, such as interrupt-related information.
> However, the instructor asked me to be more specific about which CSRs are defined. At that time, I was not able to name even a single CSR, and I could not answer which CSRs are implemented in MyCPU.
What is the CSR space?
The Control and Status Register (CSR) space in RISC-V is a dedicated address space used to hold processor control state, status information, and configuration registers. CSRs are accessed using special CSR instructions (e.g., CSRRW, CSRRS, CSRRC) rather than normal load/store instructions.
CSRs are used for:
- Privilege and mode management
- Exception and interrupt handling
- Performance monitoring
- Memory protection and configuration
### CSR Address Space Structure
RISC-V defines a **12-bit Control and Status Register (CSR) address space**, allowing up to **4096 CSRs**.
The CSR address itself encodes both the **minimum required privilege level** and the **intended usage** of the register.
### Privilege-based Partitioning
The upper bits of the CSR address determine the **minimum privilege level** required to access the CSR.
Accessing a CSR from a lower privilege level than required will trigger an **illegal instruction exception**.
| CSR Address Range | Privilege Level | Description |
|------------------|-----------------|-------------|
| `0x000 – 0x0FF` | User (U-mode) | User-level CSRs (e.g., performance counters) |
| `0x100 – 0x1FF` | Supervisor (S-mode) | Supervisor-level CSRs (e.g., OS control) |
| `0x200 – 0x2FF` | Hypervisor (H-mode, optional) | Hypervisor-level CSRs for virtualization |
| `0x300 – 0xFFF` | Machine (M-mode) | Machine-level CSRs for system control and trap handling |
### CSRS in MYCPU
Implemented CSRs in MyCPU (Machine Mode)
| CSR | Address | Function |
|-----------|---------|----------|
| `mstatus` | `0x300` | Machine status register; holds global machine state and interrupt enable information (e.g., MIE/MPIE). |
| `mie` | `0x304` | Machine interrupt enable register; enables or disables individual interrupt sources such as timer and external interrupts. |
| `mtvec` | `0x305` | Machine trap-vector base address; specifies the entry point of the trap handler (direct mode only). |
| `mscratch`| `0x340` | Machine scratch register; used by trap handlers to save temporary state or context. |
| `mepc` | `0x341` | Machine exception program counter; stores the return address when a trap occurs. |
| `mcause` | `0x342` | Machine trap cause register; records whether the trap was an interrupt or exception and its cause code. |
| `cycle` | `0xC00` | Lower 32 bits of the 64-bit cycle counter (conceptually read-only). |
| `cycleh` | `0xC80` | Upper 32 bits of the 64-bit cycle counter (conceptually read-only). |
### How CSRs handle exception
To validate the functionality of the CSR space and its role in trap handling, we conduct an experiment that intentionally triggers an exception and observes how machine-mode CSRs are updated and used by the processor.
Test Program (Running on web emulator)
```asm=
# Install trap handler
la t0, handler
csrw mtvec, t0
# Intentionally cause an exception:
# Writing to a read-only CSR (cycle) is illegal
csrw cycle, x0
# These instructions will never be executed
addi a0, a0, 1
addi a0, a0, 1
handler:
# Exception handler
la a0, msg
call puts
ebreak
msg:
.byte 0x4f, 0x68, 0x20, 0x6e, 0x6f, 0x21, 0x0a, 0x00 # "Oh no!\n"
# Simple output routine using MMIO
puts:
lui t1, %hi(0x10000000) # UART base address
1:
lb t0, 0(a0)
beq t0, zero, 2f
sw t0, 0(t1)
addi a0, a0, 1
j 1b
2:
ret
```
The execution of the test program proceeds as follows:
1. **Trap Handler Installation**
The program writes the address of handler into the mtvec CSR, thereby configuring the machine-mode trap entry point. Any subsequent exception will redirect control flow to this handler.
2. **Exception Generation**
The instruction csrw cycle, x0 attempts to write to the read-only cycle CSR. According to the RISC-V privileged specification, this operation is illegal and immediately triggers an Illegal Instruction exception.
3. **Hardware-Managed Trap Entry**
Upon detecting the exception, the processor automatically records the trap state in machine-mode CSRs, including mcause, mepc, mtval, and mstatus. At the same time, the program counter is updated to the address stored in mtvec.
4. **Trap Handler Execution**
Execution continues at the trap handler in Machine mode. The handler prints an error message (“Oh no!”) and terminates the program using ebreak to allow inspection of the processor state.
5. **CSR State Observation**
By inspecting the CSRs through the emulator’s debug interface, the following can be confirmed:
- mcause correctly records the illegal instruction exception,
- mepc stores the address of the faulting instruction,
- mstatus.MPP indicates that the exception occurred while executing in Machine mode.
```asm=
(priv) = 3 (Machine)
mstatus = 0x00001800 = { MPP = 3 (Machine) }
mscratch = 0x00000000 | mtvec = 0x40000018
mepc = 0x4000000c | mtval = 0xc0001073
mcause = 0x00000002
cycle = 0x00000000_00000004
instret = 0x00000000_00000003
```
reference:
https://dramforever.github.io/easyriscv/#control-and-status-registers-csrs
MYCPU 2-MMIO-TRAP