Skip to content

Commit 0140937

Browse files
committed
feat(c-to-raven): full bare-metal C template with raven.h runtime
raven.h now provides a complete nostdlib runtime: - Memory: memset, memcpy, memmove, memcmp - Strings: strlen, strcmp, strncmp, strcpy, strncpy, strcat, strchr, strrchr - Math: abs, min, max, umin, umax, ipow - I/O: print_char/str/int/uint/hex/ptr/float/bool/ln, read_line, read_int - Heap: malloc, calloc, realloc, free (first-fit + coalescing), raven_heap_free - Debug: raven_assert, raven_panic, raven_pause - Syscalls: sys_write, sys_read, sys_exit, sys_getrandom Makefile now auto-detects clang vs riscv64-unknown-elf-gcc using $(origin CC) so it doesn't fight make's implicit CC=cc default. Adds -fno-builtin to prevent compiler-generated calls to libc symbols. main.c updated to demonstrate malloc/calloc/realloc/free, string ops, memset, raven_assert, print_hex/ptr/bool alongside the sort example. float_demo.c updated to use raven.h's print_float(v, decimals) instead of a local copy; adds a tip about using Dyn view while stepping.
1 parent d51944e commit 0140937

7 files changed

Lines changed: 778 additions & 64 deletions

File tree

c-to-raven/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.elf
2+
.vscode/

c-to-raven/Makefile

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# ── Compiler detection ────────────────────────────────────────────────────────
2+
# We need a RISC-V cross-compiler. Detect automatically unless the user
3+
# passes CC= on the command line.
4+
#
5+
# Override example:
6+
# make CC=riscv64-unknown-elf-gcc
7+
# make CC=riscv64-none-elf-gcc
8+
9+
ifeq ($(origin CC), default)
10+
# make's default CC is "cc" — detect a proper RISC-V compiler instead
11+
ifneq ($(shell command -v clang 2>/dev/null),)
12+
CC := clang
13+
else ifneq ($(shell command -v clang-18 2>/dev/null),)
14+
CC := clang-18
15+
else ifneq ($(shell command -v riscv64-unknown-elf-gcc 2>/dev/null),)
16+
CC := riscv64-unknown-elf-gcc
17+
else ifneq ($(shell command -v riscv64-none-elf-gcc 2>/dev/null),)
18+
CC := riscv64-none-elf-gcc
19+
else
20+
$(error No RISC-V cross-compiler found. Install clang or riscv64-unknown-elf-gcc.)
21+
endif
22+
endif
23+
24+
# ── Flags ────────────────────────────────────────────────────────────────────
25+
# -fno-builtin: prevent the compiler from replacing loops with calls to
26+
# libc memset/memcpy/etc., which would fail to link without a C library.
27+
COMMON = -nostdlib -O2 -Wall -Wextra -fno-builtin
28+
29+
ifeq ($(findstring clang,$(CC)),clang)
30+
# Clang needs an explicit target triple + LLD linker
31+
CFLAGS_IM = --target=riscv32-unknown-elf -march=rv32im -mabi=ilp32 $(COMMON) -fuse-ld=lld
32+
CFLAGS_IMF = --target=riscv32-unknown-elf -march=rv32imf -mabi=ilp32f $(COMMON) -fuse-ld=lld
33+
else
34+
# GCC cross-compiler already targets RISC-V
35+
CFLAGS_IM = -march=rv32im -mabi=ilp32 $(COMMON)
36+
CFLAGS_IMF = -march=rv32imf -mabi=ilp32f $(COMMON)
37+
endif
38+
39+
LFLAGS = -e _start -Wl,--gc-sections
40+
41+
# ── Targets ───────────────────────────────────────────────────────────────────
42+
TARGET = c-to-raven.elf
43+
FLOAT_TARGET = float-demo.elf
44+
SRCS = crt0.S main.c
45+
FLOAT_SRCS = crt0.S float_demo.c
46+
47+
.PHONY: all float-demo clean
48+
49+
all: $(TARGET)
50+
51+
$(TARGET): $(SRCS) raven.h
52+
$(CC) $(CFLAGS_IM) $(LFLAGS) -o $@ $(SRCS)
53+
54+
float-demo: $(FLOAT_TARGET)
55+
56+
$(FLOAT_TARGET): $(FLOAT_SRCS) raven.h
57+
$(CC) $(CFLAGS_IMF) $(LFLAGS) -o $@ $(FLOAT_SRCS)
58+
59+
clean:
60+
rm -f $(TARGET) $(FLOAT_TARGET)

c-to-raven/README.md

Lines changed: 104 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
# From C to Raven
22

3-
A bare-metal C project that compiles to a RISC-V ELF binary ready to run in the [Raven](https://github.com/Gaok1/Raven) simulator.
3+
A bare-metal C project that compiles to a RISC-V ELF binary ready to run in the [Raven](https://github.com/Gaok1/Raven-RiscV) simulator.
44

5-
No OS, no libc. `raven.h` is the only runtime you need.
5+
No OS. No libc. `raven.h` is the only runtime you need — it gives you syscalls, I/O, strings, memory utilities, a heap allocator, and simulator control, all as `static inline` functions.
66

77
---
88

99
## Files
1010

1111
| File | Purpose |
1212
|------|---------|
13-
| `raven.h` | Syscall wrappers, I/O helpers, `raven_pause()` |
14-
| `crt0.S` | Minimal startup: calls `main()`, then `exit(return_value)` |
15-
| `main.c` | Example: fill array with random numbers, bubble-sort, print result |
16-
| `float_demo.c` | Example: sum, dot product and basic arithmetic using RV32F hardware floats |
17-
| `Makefile` | Builds both demos with Clang/LLD targeting `rv32im` and `rv32imf` |
13+
| `raven.h` | The entire runtime: syscalls, I/O, strings, memory, malloc, assert |
14+
| `crt0.S` | Minimal startup: calls `main()`, forwards return value to `exit` |
15+
| `main.c` | Example: malloc/free, string ops, sorting, raven_pause |
16+
| `float_demo.c` | Example: RV32F hardware float — sum, dot product, basic arithmetic |
17+
| `Makefile` | Builds both demos; auto-detects clang or riscv64-unknown-elf-gcc |
1818

1919
---
2020

@@ -23,94 +23,141 @@ No OS, no libc. `raven.h` is the only runtime you need.
2323
```sh
2424
make # → c-to-raven.elf (RV32IM, integer only)
2525
make float-demo # → float-demo.elf (RV32IMF, hardware float)
26-
make clean # remove .elf files
26+
make clean
2727
```
2828

29-
Load the resulting ELF into Raven: Editor tab → **[BIN]** button → select the file.
29+
Load the ELF into Raven: Editor tab → **[BIN]** → select the file.
3030

3131
---
3232

3333
## Requirements
3434

35-
### Linux (Ubuntu / Debian)
35+
The Makefile auto-detects the compiler. Install one of:
3636

37-
The Makefile uses **Clang 18** + **LLD**. Both are available from the standard LLVM packages:
37+
### Linux (Ubuntu / Debian) — recommended: Clang + LLD
3838

3939
```sh
40-
sudo apt install clang-18 lld
40+
sudo apt install clang lld
4141
```
4242

43-
If you prefer GCC instead, change the first two lines of the Makefile:
43+
### Linux — alternative: GCC cross-compiler
4444

45-
```makefile
46-
CC = riscv64-unknown-elf-gcc
47-
CFLAGS = -march=rv32im -mabi=ilp32 -nostdlib -O2 -Wall -Wextra
45+
```sh
46+
sudo apt install gcc-riscv64-unknown-elf
4847
```
4948

50-
Install GCC with:
49+
### Windows — WSL (recommended)
5150

5251
```sh
53-
sudo apt install gcc-riscv64-unknown-elf
52+
wsl --install # then follow Linux instructions inside WSL
5453
```
5554

56-
### Windows — Option 1: WSL (recommended)
57-
58-
1. Install WSL with Ubuntu:
59-
```sh
60-
wsl --install
61-
```
62-
2. Inside WSL, follow the Linux instructions above.
55+
### Windows — MSYS2
6356

64-
### Windows — Option 2: MSYS2
57+
Open the **UCRT64** terminal:
6558

66-
1. Install [MSYS2](https://www.msys2.org/).
67-
2. Open the **UCRT64** terminal and run:
68-
```sh
69-
pacman -S mingw-w64-ucrt-x86_64-clang mingw-w64-ucrt-x86_64-lld
70-
```
71-
3. Build from the UCRT64 terminal:
72-
```sh
73-
make
74-
```
59+
```sh
60+
pacman -S mingw-w64-ucrt-x86_64-clang mingw-w64-ucrt-x86_64-lld
61+
```
7562

7663
---
7764

7865
## `raven.h` API reference
7966

8067
### Syscall wrappers
8168

82-
| Function | Signature | Description |
83-
|----------|-----------|-------------|
84-
| `sys_write` | `int sys_write(int fd, const void *buf, int len)` | Write `len` bytes to file descriptor (use `STDOUT` = 1) |
85-
| `sys_read` | `int sys_read(int fd, void *buf, int len)` | Read up to `len` bytes from file descriptor (use `STDIN` = 0) |
86-
| `sys_exit` | `void sys_exit(int code)` | Terminate with exit code (no return) |
87-
| `sys_getrandom` | `int sys_getrandom(void *buf, int len, unsigned flags)` | Fill buffer with random bytes (flags = 0) |
69+
| Function | Description |
70+
|----------|-------------|
71+
| `sys_write(fd, buf, len)` | Write `len` bytes to file descriptor (`STDOUT` = 1, `STDERR` = 2) |
72+
| `sys_read(fd, buf, len)` | Read up to `len` bytes from file descriptor (`STDIN` = 0) |
73+
| `sys_exit(code)` | Terminate (no return) |
74+
| `sys_getrandom(buf, len, flags)` | Fill buffer with random bytes; flags = 0 |
8875

89-
All wrappers use the Linux RISC-V ABI (`a7` = syscall number, `a0``a2` = args, `a0` = return value).
76+
### Simulator control
77+
78+
| Function | Description |
79+
|----------|-------------|
80+
| `raven_pause()` | Emit `ebreak` — Raven pauses so you can inspect registers and memory |
9081

9182
### I/O helpers
9283

9384
| Function | Description |
9485
|----------|-------------|
95-
| `print_char(char c)` | Print a single character to stdout |
96-
| `print_str(const char *s)` | Print a null-terminated string to stdout |
97-
| `print_int(int n)` | Print a signed integer (decimal) to stdout |
98-
| `print_uint(unsigned int n)` | Print an unsigned integer (decimal) to stdout |
86+
| `print_char(c)` | Print a single character |
87+
| `print_str(s)` | Print a null-terminated string |
88+
| `print_int(n)` | Print a signed decimal integer |
89+
| `print_uint(n)` | Print an unsigned decimal integer |
90+
| `print_hex(n)` | Print unsigned int as `0x00000000` |
91+
| `print_ptr(p)` | Print a pointer as hex address |
92+
| `print_float(v, decimals)` | Print a float with the given number of decimal places |
93+
| `print_bool(v)` | Print `"true"` or `"false"` |
9994
| `print_ln()` | Print a newline |
100-
| `read_line(char *buf, int max)` | Read a line from stdin; null-terminates; returns bytes read |
95+
| `read_line(buf, max)` | Read a line from stdin; null-terminates; returns byte count |
96+
| `read_int()` | Read a decimal integer from stdin |
10197

102-
### Simulator control
98+
### Memory utilities
99+
100+
| Function | Description |
101+
|----------|-------------|
102+
| `memset(dst, c, n)` | Fill `n` bytes with value `c`; returns `dst` |
103+
| `memcpy(dst, src, n)` | Copy `n` bytes from `src` to `dst`; returns `dst` |
104+
| `memmove(dst, src, n)` | Copy `n` bytes, safe for overlapping regions; returns `dst` |
105+
| `memcmp(a, b, n)` | Compare `n` bytes; returns 0 if equal |
106+
107+
### String utilities
108+
109+
| Function | Description |
110+
|----------|-------------|
111+
| `strlen(s)` | Length of string (not counting `'\0'`) |
112+
| `strcmp(a, b)` | Lexicographic comparison; returns 0 if equal |
113+
| `strncmp(a, b, n)` | Comparison limited to `n` characters |
114+
| `strcpy(dst, src)` | Copy string; returns `dst` |
115+
| `strncpy(dst, src, n)` | Copy at most `n` characters, zero-pad; returns `dst` |
116+
| `strcat(dst, src)` | Append `src` to `dst`; returns `dst` |
117+
| `strchr(s, c)` | First occurrence of `c` in `s`, or `NULL` |
118+
| `strrchr(s, c)` | Last occurrence of `c` in `s`, or `NULL` |
119+
120+
### Math utilities
103121

104122
| Function | Description |
105123
|----------|-------------|
106-
| `raven_pause()` | Emit `ebreak` — Raven pauses execution so you can inspect registers and memory |
124+
| `abs(n)` | Absolute value of signed int |
125+
| `min(a, b)` / `max(a, b)` | Integer min/max |
126+
| `umin(a, b)` / `umax(a, b)` | Unsigned int min/max |
127+
| `ipow(base, exp)` | Integer power: `base^exp` |
128+
129+
### Assert / panic
130+
131+
| Function / Macro | Description |
132+
|----------|-------------|
133+
| `raven_assert(expr)` | If `expr` is false: print message, pause, exit(1) |
134+
| `raven_panic(msg)` | Print `msg` to stderr, pause for inspection, exit(1) |
135+
136+
### Heap allocator
137+
138+
A first-fit free-list allocator backed by a static `64 KB` heap.
139+
140+
| Function | Description |
141+
|----------|-------------|
142+
| `malloc(size)` | Allocate `size` bytes; returns `NULL` on out-of-memory |
143+
| `calloc(nmemb, size)` | Allocate `nmemb * size` bytes, zero-initialised |
144+
| `realloc(ptr, new_size)` | Resize allocation; copies existing data |
145+
| `free(ptr)` | Release allocation; coalesces adjacent free blocks |
146+
| `raven_heap_free()` | Returns approximate bytes remaining in the heap |
147+
148+
Change the heap size before including the header:
149+
150+
```c
151+
#define RAVEN_HEAP_SIZE (128 * 1024) // 128 KB
152+
#include "raven.h"
153+
```
154+
155+
> **Tip:** single-step with Raven's **[Dyn]** view active (`v` until `DYN` shows in the status bar). Every `sw` that writes a malloc header will flip the sidebar to show exactly what was written in RAM and where.
107156
108157
---
109158
110159
## Adding more source files
111160
112-
Edit the `SRCS` variable in the Makefile:
113-
114161
```makefile
115162
SRCS = crt0.S main.c mylib.c another.c
116163
```
@@ -119,28 +166,21 @@ SRCS = crt0.S main.c mylib.c another.c
119166

120167
## Float support
121168

122-
`float_demo.c` is compiled with `-march=rv32imf -mabi=ilp32f`, enabling hardware `f`-registers.
123-
Open the Run tab in Raven and press `Tab` on the register sidebar to switch from integer to float registers and watch `f0``f31` update in real time as the program runs.
124-
125-
To add float support to your own program, change the Makefile target:
169+
`float_demo.c` is compiled with `-march=rv32imf -mabi=ilp32f` for hardware `f`-registers. In Raven's Run tab, press `Tab` on the register sidebar to switch between integer and float register banks.
126170
127-
```makefile
128-
CC = clang-18
129-
CFLAGS = --target=riscv32-unknown-elf -march=rv32imf -mabi=ilp32f \
130-
-nostdlib -fuse-ld=lld -O2
131-
```
171+
To enable float in your own program, change `make float-demo` or add the float target to the Makefile pointing at your files.
132172
133173
---
134174
135175
## How it works
136176
137-
`crt0.S` sets up the bare minimum before `main()`:
177+
`crt0.S` is the only startup code:
138178
139179
```asm
140180
_start:
141-
call main # sp is set by the ELF loader (Raven initialises it)
142-
li a7, 93 # exit(return value of main)
181+
call main # Raven initialises sp; main's return value lands in a0
182+
li a7, 93 # exit(a0)
143183
ecall
144184
```
145185

146-
Raven zeroes BSS automatically when loading the ELF, so no explicit BSS-clear loop is needed in the startup code.
186+
Raven zeroes BSS automatically when loading the ELF, so no explicit BSS-clear loop is needed.

c-to-raven/crt0.S

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# crt0.S — minimal C runtime startup for Raven
2+
#
3+
# Raven already zeroes BSS when loading the ELF, so the only job here is
4+
# to call main() and forward its return value to exit (syscall 93).
5+
6+
.section .text._start
7+
.globl _start
8+
9+
_start:
10+
call main # call user's main(); return value lands in a0
11+
li a7, 93 # exit(a0)
12+
ecall
13+
1: j 1b # unreachable — keeps the simulator from running off

0 commit comments

Comments
 (0)