Skip to content

Commit d51944e

Browse files
committed
feat: replace [BP] sidebar with [Dyn] self-narrating step view
Dyn mode (cycle with `v`: RAM → REGS → DYN) watches each executed instruction and automatically flips the sidebar: - STORE → memory view centered on the written address, title "Memory [Dyn] @0x…" - LOAD / ALU / branch → register view, title "Registers [Dyn]" This makes single-stepping self-narrating: you always see the side of the machine that the current instruction affects, with no manual switching. Other changes: - classify_mem_access now returns (addr, size, is_store) so the distinction is clean throughout - Access (R/W) mode and Dyn-store mode center the accessed address in the middle of the visible RAM panel (same as Stack mode already did) - `k` (cycle RAM region) is now guarded: fires only in pure RAM mode, not while REGS or DYN are active - Region button in status bar hidden when DYN is active - render_bp_list() removed; F9 still sets/marks breakpoints in the instruction list - c-to-raven/README.md: full API reference, Clang vs GCC instructions, float support section, file table
1 parent 3b8b765 commit d51944e

8 files changed

Lines changed: 274 additions & 106 deletions

File tree

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ Requires Rust 1.75+. No other dependencies.
3838
- Breakpoints (`b`/`F9`), jump to address (`g`), execution trace (`t`)
3939
- All 32 integer registers with ABI names, change highlighting
4040
- Float registers (`f0–f31` / ABI names), toggled with `Tab`
41-
- RAM view, stack view, breakpoint list — cycle with `v`
41+
- Sidebar cycles with `v`: **RAM → Registers → Dyn**
42+
- **RAM**: scrollable memory view with region selector (Data / Stack / R/W)
43+
- **Registers**: integer or float register bank with per-register age highlighting
44+
- **Dyn**: self-narrating mode for single-stepping — shows the memory region written on every STORE, and the register bank on every LOAD, ALU, or branch instruction. The accessed address is always centered in the view.
4245
- Instruction memory panel: type badge `[R][I][S][B][U][J]`, execution heat `×N`, branch outcome
4346
- Instruction decoder: full field breakdown (opcode, funct3/7, rs1/rs2/rd, immediate, sign-extended)
4447

@@ -126,7 +129,7 @@ A ready-to-use project with `_start`, panic handler, allocator, and wrappers for
126129
| `F10` / `n` | Single step |
127130
| `F9` / `b` | Toggle breakpoint at PC |
128131
| `f` | Cycle speed: 1× → 2× → 4× → Instant |
129-
| `v` | Cycle sidebar: RAM → Registers → Stack → Breakpoints |
132+
| `v` | Cycle sidebar: RAM → Registers → Dyn |
130133
| `Tab` | Toggle integer / float register bank |
131134
| `t` | Toggle execution trace panel |
132135
| `g` | Jump to address |

c-to-raven/README.md

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# From C to Raven
2+
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.
4+
5+
No OS, no libc. `raven.h` is the only runtime you need.
6+
7+
---
8+
9+
## Files
10+
11+
| File | Purpose |
12+
|------|---------|
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` |
18+
19+
---
20+
21+
## Building
22+
23+
```sh
24+
make # → c-to-raven.elf (RV32IM, integer only)
25+
make float-demo # → float-demo.elf (RV32IMF, hardware float)
26+
make clean # remove .elf files
27+
```
28+
29+
Load the resulting ELF into Raven: Editor tab → **[BIN]** button → select the file.
30+
31+
---
32+
33+
## Requirements
34+
35+
### Linux (Ubuntu / Debian)
36+
37+
The Makefile uses **Clang 18** + **LLD**. Both are available from the standard LLVM packages:
38+
39+
```sh
40+
sudo apt install clang-18 lld
41+
```
42+
43+
If you prefer GCC instead, change the first two lines of the Makefile:
44+
45+
```makefile
46+
CC = riscv64-unknown-elf-gcc
47+
CFLAGS = -march=rv32im -mabi=ilp32 -nostdlib -O2 -Wall -Wextra
48+
```
49+
50+
Install GCC with:
51+
52+
```sh
53+
sudo apt install gcc-riscv64-unknown-elf
54+
```
55+
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.
63+
64+
### Windows — Option 2: MSYS2
65+
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+
```
75+
76+
---
77+
78+
## `raven.h` API reference
79+
80+
### Syscall wrappers
81+
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) |
88+
89+
All wrappers use the Linux RISC-V ABI (`a7` = syscall number, `a0``a2` = args, `a0` = return value).
90+
91+
### I/O helpers
92+
93+
| Function | Description |
94+
|----------|-------------|
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 |
99+
| `print_ln()` | Print a newline |
100+
| `read_line(char *buf, int max)` | Read a line from stdin; null-terminates; returns bytes read |
101+
102+
### Simulator control
103+
104+
| Function | Description |
105+
|----------|-------------|
106+
| `raven_pause()` | Emit `ebreak` — Raven pauses execution so you can inspect registers and memory |
107+
108+
---
109+
110+
## Adding more source files
111+
112+
Edit the `SRCS` variable in the Makefile:
113+
114+
```makefile
115+
SRCS = crt0.S main.c mylib.c another.c
116+
```
117+
118+
---
119+
120+
## Float support
121+
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:
126+
127+
```makefile
128+
CC = clang-18
129+
CFLAGS = --target=riscv32-unknown-elf -march=rv32imf -mabi=ilp32f \
130+
-nostdlib -fuse-ld=lld -O2
131+
```
132+
133+
---
134+
135+
## How it works
136+
137+
`crt0.S` sets up the bare minimum before `main()`:
138+
139+
```asm
140+
_start:
141+
call main # sp is set by the ELF loader (Raven initialises it)
142+
li a7, 93 # exit(return value of main)
143+
ecall
144+
```
145+
146+
Raven zeroes BSS automatically when loading the ELF, so no explicit BSS-clear loop is needed in the startup code.

src/ui/app.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ pub(super) enum EditorMode {
341341
pub(super) enum MemRegion {
342342
Data,
343343
Stack,
344+
Access, // auto-follows last memory read/write
344345
Custom,
345346
}
346347

@@ -537,8 +538,9 @@ pub(super) struct RunState {
537538
// Feature: register write trace (Feature 8)
538539
pub(super) reg_last_write_pc: [Option<u32>; 32],
539540

540-
// Feature: breakpoint list view (Feature 10)
541-
pub(super) show_bp_list: bool,
541+
// Feature: dynamic sidebar view (Dyn)
542+
pub(super) show_dyn: bool,
543+
pub(super) dyn_mem_access: Option<(u32, u32, bool)>, // last step's mem access (addr, size, is_store); None = non-mem instr
542544

543545
// Mouse hover row in register sidebar (visual row index, 0-based within inner area)
544546
pub(super) hover_reg_row: Option<usize>,
@@ -821,7 +823,8 @@ impl App {
821823
reg_cursor: 0,
822824
block_comments: std::collections::HashMap::new(),
823825
reg_last_write_pc: [None; 32],
824-
show_bp_list: false,
826+
show_dyn: false,
827+
dyn_mem_access: None,
825828
hover_reg_row: None,
826829
show_float_regs: false,
827830
prev_f: [0u32; 32],
@@ -1627,7 +1630,7 @@ impl App {
16271630
// Update memory access log (age existing entries, insert new if load/store)
16281631
for entry in &mut self.run.mem_access_log { entry.2 = entry.2.saturating_add(1); }
16291632
self.run.mem_access_log.retain(|e| e.2 < 3);
1630-
if let Some((addr, size)) = mem_access {
1633+
if let Some((addr, size, _)) = mem_access {
16311634
self.run.mem_access_log.push((addr, size, 0));
16321635
}
16331636

@@ -1654,6 +1657,21 @@ impl App {
16541657
let sp = self.run.cpu.x[2];
16551658
self.run.mem_view_addr = sp & !(self.run.mem_view_bytes - 1);
16561659
}
1660+
// Auto-follow last memory R/W when Access region is active
1661+
if self.run.mem_region == crate::ui::app::MemRegion::Access {
1662+
if let Some((addr, _, _)) = mem_access {
1663+
self.run.mem_view_addr = addr & !(self.run.mem_view_bytes - 1);
1664+
}
1665+
}
1666+
// Dyn view: remember last access; update mem_view_addr for memory sub-view
1667+
self.run.dyn_mem_access = mem_access;
1668+
if self.run.show_dyn {
1669+
if let Some((addr, _, is_store)) = mem_access {
1670+
if is_store {
1671+
self.run.mem_view_addr = addr & !(self.run.mem_view_bytes - 1);
1672+
}
1673+
}
1674+
}
16571675

16581676
// Check breakpoints: stop if the new PC is a breakpoint
16591677
if alive && self.run.breakpoints.contains(&self.run.cpu.pc) {
@@ -1733,7 +1751,8 @@ impl App {
17331751
/// Decode a raw instruction word and return the memory address + byte size that
17341752
/// the instruction accesses (load or store), or `None` for non-memory instructions.
17351753
/// Uses pre-step register values from `cpu`.
1736-
fn classify_mem_access(word: u32, cpu: &crate::falcon::Cpu) -> Option<(u32, u32)> {
1754+
/// Returns `Some((addr, size, is_store))` for load/store instructions.
1755+
fn classify_mem_access(word: u32, cpu: &crate::falcon::Cpu) -> Option<(u32, u32, bool)> {
17371756
let opcode = word & 0x7F;
17381757
let funct3 = (word >> 12) & 0x7;
17391758
let rs1 = ((word >> 15) & 0x1F) as usize;
@@ -1749,7 +1768,7 @@ fn classify_mem_access(word: u32, cpu: &crate::falcon::Cpu) -> Option<(u32, u32)
17491768
2 => 4, // lw / flw
17501769
_ => return None,
17511770
};
1752-
Some((addr, size))
1771+
Some((addr, size, false))
17531772
}
17541773
// STORE (sb sh sw) and STORE-FP (fsw)
17551774
0x23 | 0x27 => {
@@ -1763,7 +1782,7 @@ fn classify_mem_access(word: u32, cpu: &crate::falcon::Cpu) -> Option<(u32, u32)
17631782
2 => 4, // sw / fsw
17641783
_ => return None,
17651784
};
1766-
Some((addr, size))
1785+
Some((addr, size, true))
17671786
}
17681787
_ => None,
17691788
}

src/ui/input/keyboard.rs

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -733,19 +733,19 @@ pub fn handle_key(app: &mut App, key: KeyEvent) -> io::Result<bool> {
733733
app.run.is_running = true;
734734
}
735735
}
736-
// v: cycle sidebar view — REGSRAMBPREGS
736+
// v: cycle sidebar view — RAMREGSDYNRAM
737737
(KeyCode::Char('v'), Tab::Run) => {
738-
if app.run.show_bp_list {
739-
app.run.show_bp_list = false;
738+
if app.run.show_dyn {
739+
app.run.show_dyn = false;
740740
app.run.show_registers = true;
741741
} else if app.run.show_registers {
742742
app.run.show_registers = false;
743743
} else {
744-
app.run.show_bp_list = true;
744+
app.run.show_dyn = true;
745745
}
746746
}
747747
// Tab (in register view): toggle between int and float registers
748-
(KeyCode::Tab, Tab::Run) if app.run.show_registers && !app.run.show_bp_list => {
748+
(KeyCode::Tab, Tab::Run) if app.run.show_registers && !app.run.show_dyn => {
749749
app.run.show_float_regs = !app.run.show_float_regs;
750750
}
751751
// t: toggle execution trace panel
@@ -760,18 +760,25 @@ pub fn handle_key(app: &mut App, key: KeyEvent) -> io::Result<bool> {
760760
(KeyCode::Char('y'), Tab::Run) => {
761761
app.run.show_instr_type = !app.run.show_instr_type;
762762
}
763-
// k: toggle Stack region in the RAM memory view
764-
(KeyCode::Char('k'), Tab::Run) => {
765-
if app.run.mem_region == MemRegion::Stack {
766-
app.run.mem_region = MemRegion::Data;
767-
app.run.mem_view_addr = app.run.data_base;
768-
} else {
769-
app.run.mem_region = MemRegion::Stack;
770-
let sp = app.run.cpu.x[2];
771-
app.run.mem_view_addr = sp & !(app.run.mem_view_bytes - 1);
763+
// k: cycle memory region DATA → STACK → R/W → DATA (only in pure RAM mode)
764+
(KeyCode::Char('k'), Tab::Run)
765+
if !app.run.show_registers && !app.run.show_dyn => {
766+
match app.run.mem_region {
767+
MemRegion::Data | MemRegion::Custom => {
768+
app.run.mem_region = MemRegion::Stack;
769+
let sp = app.run.cpu.x[2];
770+
app.run.mem_view_addr = sp & !(app.run.mem_view_bytes - 1);
771+
}
772+
MemRegion::Stack => {
773+
app.run.mem_region = MemRegion::Access;
774+
}
775+
MemRegion::Access => {
776+
app.run.mem_region = MemRegion::Data;
777+
app.run.mem_view_addr = app.run.data_base;
778+
}
772779
}
773780
app.run.show_registers = false;
774-
app.run.show_bp_list = false;
781+
app.run.show_dyn = false;
775782
}
776783
// P (shift+p): pin/unpin the currently selected register
777784
(KeyCode::Char('P'), Tab::Run) if app.run.show_registers => {
@@ -1032,26 +1039,34 @@ pub fn handle_key(app: &mut App, key: KeyEvent) -> io::Result<bool> {
10321039
}
10331040
// Sidebar / region shortcuts (same behaviour as Run tab)
10341041
(KeyCode::Char('v'), Tab::Cache) if !matches!(app.cache.subtab, CacheSubtab::Config) => {
1035-
if app.run.show_bp_list {
1036-
app.run.show_bp_list = false;
1042+
if app.run.show_dyn {
1043+
app.run.show_dyn = false;
10371044
app.run.show_registers = true;
10381045
} else if app.run.show_registers {
10391046
app.run.show_registers = false;
10401047
} else {
1041-
app.run.show_bp_list = true;
1048+
app.run.show_dyn = true;
10421049
}
10431050
}
1044-
(KeyCode::Char('k'), Tab::Cache) if !matches!(app.cache.subtab, CacheSubtab::Config) => {
1045-
if app.run.mem_region == MemRegion::Stack {
1046-
app.run.mem_region = MemRegion::Data;
1047-
app.run.mem_view_addr = app.run.data_base;
1048-
} else {
1049-
app.run.mem_region = MemRegion::Stack;
1050-
let sp = app.run.cpu.x[2];
1051-
app.run.mem_view_addr = sp & !(app.run.mem_view_bytes - 1);
1051+
(KeyCode::Char('k'), Tab::Cache)
1052+
if !matches!(app.cache.subtab, CacheSubtab::Config)
1053+
&& !app.run.show_registers && !app.run.show_dyn => {
1054+
match app.run.mem_region {
1055+
MemRegion::Data | MemRegion::Custom => {
1056+
app.run.mem_region = MemRegion::Stack;
1057+
let sp = app.run.cpu.x[2];
1058+
app.run.mem_view_addr = sp & !(app.run.mem_view_bytes - 1);
1059+
}
1060+
MemRegion::Stack => {
1061+
app.run.mem_region = MemRegion::Access;
1062+
}
1063+
MemRegion::Access => {
1064+
app.run.mem_region = MemRegion::Data;
1065+
app.run.mem_view_addr = app.run.data_base;
1066+
}
10521067
}
10531068
app.run.show_registers = false;
1054-
app.run.show_bp_list = false;
1069+
app.run.show_dyn = false;
10551070
}
10561071
(KeyCode::Char('e'), Tab::Cache) if !matches!(app.cache.subtab, CacheSubtab::Config) => {
10571072
app.run.show_exec_count = !app.run.show_exec_count;

0 commit comments

Comments
 (0)