Skip to content

Commit 48b24c0

Browse files
committed
Doc: add doc about features
1 parent ae589ce commit 48b24c0

File tree

5 files changed

+211
-0
lines changed

5 files changed

+211
-0
lines changed

README.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# RISC-V ISA Simulator
2+
3+
This project is a course assignment for the **Computer Architecture and Organization Lab** at **Peking University** in the **Fall semester of 2025**, developed as an extension of the framework provided by the course.
4+
5+
## ✨ Features
6+
7+
* Implements a majority of the instructions from the RV64IM instruction set.
8+
* Implements the **`exit`** and **`write`** system calls (with `write` currently supporting output only to **stdout**), and enables the use of **`printf`** within the virtual RISC-V environment.
9+
* Includes a simple built-in debugger with support for single-stepping, printing registers, and scanning memory.
10+
* Supports function call tracing (**ftrace**).
11+
* Supports instruction execution history logging (**itrace**).
12+
13+
```
14+
.
15+
├── .github/workflows/ # GitHub CI/CD workflow configurations
16+
├── build/ # Directory for compiled artifacts
17+
├── doc/ # Directory for docs about the implement of some features
18+
├── sim/ # Simulator core source code
19+
│ ├── include/ # Header files
20+
│ └── src/ # Source files
21+
├── test/ # Test cases
22+
│ ├── include/
23+
│ ├── lib/
24+
│ ├── src/ # Test program source code
25+
│ └── trm/ # Trap and Run Machine environment
26+
├── trace/ # Reference trace files for instruction execution
27+
├── Dockerfile # Dockerfile for building the development environment
28+
├── docker-compose.yml # Docker Compose configuration file
29+
└── Makefile # Main project Makefile
30+
```
31+
32+
## 🚀 Getting Started
33+
34+
### Prerequisites
35+
36+
This project is best built and run using Docker to avoid the complexities of local environment setup.
37+
38+
* [Docker](https://www.docker.com/)
39+
* [Docker Compose](https://docs.docker.com/compose/)
40+
41+
If you prefer to build locally, you will need to install the RISC-V GNU toolchain and other build tools.
42+
* `riscv64-unknown-elf-gcc`
43+
* `make`
44+
* `gcc`/`g++`
45+
* lib `llvm` and `libelf`
46+
47+
### Build and Run (Docker)
48+
49+
1. **Start the Docker container:**
50+
This command will build the image and create a container named `simulator_dev` based on `docker-compose.yml`.
51+
52+
```bash
53+
docker-compose up --build -d
54+
```
55+
56+
2. **Build the project and run all test:**
57+
58+
```bash
59+
docker-compose exec dev make test_all
60+
```
61+
62+
### Build and Run (Local)
63+
64+
1. **Install dependencies:**
65+
Ensure you have installed all the tools listed in the [Prerequisites](#prerequisites) section.
66+
67+
2. **Build the project and run all test**
68+
Run the `make` command in the project root directory.
69+
70+
```bash
71+
make test_all
72+
```
73+
74+
## 🎮 Usage
75+
76+
### Running Test Programs
77+
78+
You can run any test program located in the project's root directory. All test programs are compiled into the `test/build` directory.
79+
80+
For example, to run the `quicksort` test:
81+
```bash
82+
docker-compose exec dev make T=quicksort
83+
```
84+
85+
### Debug Mode
86+
87+
Start the simulator in debug mode by the make command below:
88+
```bash
89+
docker-compose exec dev make debug T=XXX
90+
```
91+
92+
In debug mode, you can use the following commands:
93+
94+
- `help`: Print the help message of debug commands
95+
96+
* `si [N]`: Execute a single instruction if `arg N` not provided, or execute N instructions.
97+
* `c`: Continue execution until the program finishes.
98+
* `info r`: Print the status of all general-purpose registers (include PC).
99+
* `x <N> <EXPR>`: Scan `N` 4-byte words of memory starting at address `EXPR`.
100+
101+
### Other Commands
102+
103+
To run the Simulator in `itrace`/`ftrace` mode:
104+
105+
```sh
106+
docker-compose exec dev make itrace T=XXX
107+
docker-compose exec dev make ftrace T=XXX
108+
```
109+
110+
## ✅ Testing
111+
112+
If you want to add your own test cases, place your test source file (`.c` file) in the **`/test/src`** directory.
113+
For example, if your test is named **`sample`**, you can run it with the following command:
114+
115+
```bash
116+
docker-compose exec dev make T=sample
117+
```

doc/debug.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# debug
2+
3+
The **debug** feature is designed to closely align with the behavior of **GDB**.
4+
As anyone who has worked on the **ICS Shell Lab** would know, the debugging functionality is implemented within a **REPL-style framework**.
5+
All the commands have been implemented according to the documentation requirements, and you can try them out yourself to see how the debugger performs in action.

doc/ftrace.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# ftrace
2+
3+
The implementation of **ftrace** involves two key components:
4+
(1) **loading and parsing the symbol table**, and
5+
(2) **identifying `call` and `ret` instructions** — which, unlike in x86, are not represented by explicit opcodes in RISC-V.
6+
7+
I used the **libelf** library to parse the **ELF file’s symbol table** and designed a data structure to store function **(name, address)** tuples. After loading, the addresses are **sorted** to enable efficient lookup during execution.
8+
9+
The **ftrace** implementation is defined in **`sim/include/ftrace.h`** and **`sim/src/ftrace.c`**, where you can check the detailed logic.
10+
11+
Similar to **itrace**, **ftrace** enhances the **debug mode**: the `si` (step instruction) command now supports displaying **which function the current instruction belongs to**, offering improved traceability during debugging.

doc/itrace.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# itrace
2+
3+
Implementing the **itrace** feature is relatively straightforward — the main task is to **disassemble RISC-V machine code**.
4+
Initially, I wrote a custom disassembly function, but the documentation suggested that the **LLVM library** could be linked to handle this task. Therefore, I ultimately chose to integrate **LLVM** for the implementation.
5+
6+
This approach has an additional benefit: in **debug mode**, the `si` (step instruction) command can now print the **current instruction being executed**, making debugging much more convenient.
7+
8+
The disassembly implementation is defined in **`sim/include/disasm.h`** and **`sim/src/disasm.c`**.

doc/syscall.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Syscall
2+
3+
The implementation of system calls involves two key aspects:
4+
(1) triggering system calls within the **RISC-V environment**, and
5+
(2) handling those system calls inside the **simulator** itself.
6+
7+
Since the provided **RISC-V cross toolchain** targets a **bare-metal environment**, it does not support **glibc** and thus lacks C function definitions for system calls. Therefore, we need to implement them ourselves. In the **`test`** section, I implemented **`syscall.h`** to encapsulate system calls, providing good extensibility for future additions.
8+
9+
```c
10+
static inline int64_t syscall(int64_t no, int64_t a0, int64_t a1, int64_t a2) {
11+
register int64_t a7 asm("a7") = no;
12+
register int64_t a0_ asm("a0") = a0;
13+
register int64_t a1_ asm("a1") = a1;
14+
register int64_t a2_ asm("a2") = a2;
15+
asm volatile("ecall"
16+
: "+r"(a0_)
17+
: "r"(a7), "r"(a1_), "r"(a2_)
18+
: "memory");
19+
return a0_;
20+
}
21+
22+
static inline void exit(int64_t code) {
23+
syscall(SYSCALL_EXIT, code, 0, 0);
24+
}
25+
26+
static inline int64_t write(int64_t fd, const void *buf, int64_t count) {
27+
return syscall(SYSCALL_WRITE, fd, (int64_t)buf, count);
28+
}
29+
```
30+
31+
In the RISC-V environment, system calls are invoked using the **`ecall`** instruction, which differs from the **`trap`** mechanism used in x86. This means that the simulator’s **instruction decoding logic** must handle `ecall` explicitly. Within the `ecall` handler, the simulator interprets the system call according to the **RISC-V ABI specification** and the **system call number**, simulating the corresponding effect on the host machine. (in `sim/src/syscall.c`)
32+
33+
```c
34+
void handle_syscall(Decode *s) {
35+
uint64_t syscall_no = cpu.reg[17]; // a7 register
36+
switch (syscall_no) {
37+
case SYSCALL_EXIT:
38+
printf("Syscall: exit with code %lu\n", cpu.reg[10]); // a0 register
39+
halt_trap(s->pc, cpu.reg[10]);
40+
break;
41+
42+
case SYSCALL_WRITE:
43+
{
44+
uint64_t fd = cpu.reg[10]; // a0
45+
uint64_t buf = cpu.reg[11]; // a1
46+
uint64_t count = cpu.reg[12]; // a2
47+
if (fd == 1) { // stdout
48+
for (uint64_t i = 0; i < count; i++) {
49+
// printf("Syscall: write byte %02x from addr %016lx\n", mem_read(buf + i, 1), buf + i);
50+
uint8_t byte_to_write = mem_read(buf + i, 1);
51+
putchar(byte_to_write);
52+
}
53+
cpu.reg[10] = count; // return value in a0
54+
} else {
55+
printf("Syscall: write to unsupported fd %lu\n", fd);
56+
cpu.reg[10] = -1; // error
57+
}
58+
}
59+
break;
60+
61+
default:
62+
printf("Unknown syscall: %lu\n", syscall_no);
63+
halt_trap(s->pc, -1);
64+
break;
65+
}
66+
}
67+
```
68+
69+
Some system calls involve memory operations, which would require support for **paging**, an **MMU**, and related mechanisms within the simulator. Since that would move the focus toward operating system–level topics—beyond the scope of this course—we only implemented **`exit`** and **`write`**.
70+
Other system calls can be easily added by extending the existing framework.

0 commit comments

Comments
 (0)