|
| 1 | +# RISC-V Emulator - Browser Edition |
| 2 | + |
| 3 | +A pure browser-based RISC-V (RV32IMAC) emulator powered by Pyodide and xterm.js. |
| 4 | +Developed using [Claude Code](https://www.claude.com/product/claude-code). |
| 5 | + |
| 6 | +## Features |
| 7 | + |
| 8 | +- **Pure client-side**: No backend server needed, runs entirely in your browser |
| 9 | +- **Full RV32IMAC support**: Base instruction set plus M (multiply/divide), A (atomics), and C (compressed instructions) |
| 10 | +- **Interactive terminal**: Character-at-a-time input via xterm.js with raw TTY support |
| 11 | +- **ELF and binary loading**: Load compiled programs directly from your filesystem |
| 12 | +- **Debugging tools**: Optional tracing for syscalls, traps, and function calls |
| 13 | +- **Timer support**: Machine timer interrupts for time-based programming |
| 14 | +- **Performance**: ~200K-2M instructions/second typical performance |
| 15 | + |
| 16 | +## Quick Start |
| 17 | + |
| 18 | +1. **Serve the webapp directory**: |
| 19 | + ```bash |
| 20 | + cd advanced/webapp |
| 21 | + python3 -m http.server 8000 |
| 22 | + ``` |
| 23 | + |
| 24 | +2. **Open in browser**: |
| 25 | + Navigate to `http://localhost:8000` |
| 26 | + |
| 27 | +3. **Load a program**: |
| 28 | + - Click "Load ELF/BIN" and select a compiled RISC-V program |
| 29 | + - Programs must be compiled for RV32I (optionally with M, A, C extensions) |
| 30 | + |
| 31 | +4. **Run**: |
| 32 | + - Click "Run" to start execution |
| 33 | + - Interact with the program via the terminal |
| 34 | + - Click "Stop" to interrupt execution |
| 35 | + - Click "Reset" to clear emulator state |
| 36 | + |
| 37 | +## Building Programs |
| 38 | + |
| 39 | +Programs must be compiled for RISC-V RV32I. Example using the riscv-gnu-toolchain: |
| 40 | + |
| 41 | +```bash |
| 42 | +# Simple program |
| 43 | +riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 \ |
| 44 | + -nostartfiles -static -Tlinker.ld \ |
| 45 | + --specs=nosys.specs -o program.elf program.c |
| 46 | + |
| 47 | +# With multiply/divide support |
| 48 | +riscv64-unknown-elf-gcc -march=rv32im -mabi=ilp32 \ |
| 49 | + -nostartfiles -static -Tlinker.ld \ |
| 50 | + --specs=nosys.specs -o program.elf program.c |
| 51 | +``` |
| 52 | + |
| 53 | +See `../../tests/` for example programs. |
| 54 | + |
| 55 | +## UI Controls |
| 56 | + |
| 57 | +### Tracing Options |
| 58 | +- **Syscalls**: Log all syscall operations (exit, read, write, sbrk, etc.) |
| 59 | +- **Traps**: Log exceptions and interrupts |
| 60 | +- **Functions**: Log function calls (requires ELF with symbols) |
| 61 | + |
| 62 | +### Checking Options |
| 63 | +- **Invariants**: Check CPU invariants (x0=0, PC bounds, stack bounds) |
| 64 | +- **Memory bounds**: Validate all memory accesses |
| 65 | +- **Text integrity**: Detect self-modifying code |
| 66 | + |
| 67 | +### Features |
| 68 | +- **Timer**: Enable machine timer interrupts (CSR mode) |
| 69 | +- **RVC**: Enable compressed 16-bit instructions |
| 70 | + |
| 71 | +### Configuration |
| 72 | +- **Registers**: Comma-separated list of registers to log (e.g., "pc,sp,ra,a0") |
| 73 | +- **RAM Size**: Emulated RAM in kilobytes (default: 1024 KB) |
| 74 | + |
| 75 | +## Architecture |
| 76 | + |
| 77 | +### Files Structure |
| 78 | + |
| 79 | +``` |
| 80 | +webapp/ |
| 81 | +├── index.html # Main page |
| 82 | +├── css/ |
| 83 | +│ └── styles.css # UI styling |
| 84 | +├── js/ |
| 85 | +│ ├── main.js # Pyodide orchestration & execution loop |
| 86 | +│ ├── terminal.js # Xterm.js terminal with I/O bridging |
| 87 | +│ ├── fileloader.js # FileAPI for loading ELF/bin files |
| 88 | +│ └── controls.js # UI option management |
| 89 | +└── py/ |
| 90 | + ├── browser_entry.py # Python entry point for browser |
| 91 | + ├── browser_syscalls.py # Browser-adapted syscall handler |
| 92 | + ├── browser_logger.py # Logger outputting to JS console |
| 93 | + └── peripherals.py # Minimal peripherals (timer only) |
| 94 | +``` |
| 95 | + |
| 96 | +### Dependencies |
| 97 | + |
| 98 | +- **Pyodide** (v0.24.1+): Python runtime for WebAssembly |
| 99 | +- **pyelftools**: ELF file parsing |
| 100 | +- **xterm.js** (v5.3.0): Terminal emulator |
| 101 | +- **Parent directory modules**: cpu.py, ram.py, machine.py, rvc.py (imported dynamically) |
| 102 | + |
| 103 | +## Syscall Support |
| 104 | + |
| 105 | +### Implemented Syscalls |
| 106 | +- `exit` (93): Program termination |
| 107 | +- `write` (64): Write to stdout/stderr → terminal |
| 108 | +- `read` (63): Read from stdin ← terminal |
| 109 | +- `sbrk` (214): Heap expansion |
| 110 | +- `fstat` (80): File status (faked for stdin/stdout/stderr) |
| 111 | +- `isatty` (89): TTY check (returns true for 0/1/2) |
| 112 | +- `getpid` (172): Get PID (returns 1) |
| 113 | +- `umask` (60): File creation mask |
| 114 | + |
| 115 | +### Stubbed Syscalls (return -ENOSYS) |
| 116 | +- File operations: `openat`, `close`, `lseek`, `mkdirat`, `unlinkat` |
| 117 | +- Process control: `kill` |
| 118 | + |
| 119 | +Programs relying on file I/O will fail. Future versions may support virtual filesystem. |
| 120 | + |
| 121 | +## Performance |
| 122 | + |
| 123 | +- **Chunked execution**: 10,000 instructions per frame (configurable in main.js) |
| 124 | +- **Frame rate**: Target 60 FPS = ~600K instructions/second |
| 125 | +- **Actual performance**: 200K - 2M IPS depending on browser and options enabled |
| 126 | +- **Tracing overhead**: ~3x slower when logging is enabled |
| 127 | + |
| 128 | +## Limitations (Pilot Version) |
| 129 | + |
| 130 | +1. **No file I/O**: `open()`, `read()`, `write()` on files return `-ENOSYS` |
| 131 | +2. **No block device**: No persistent storage emulation |
| 132 | +3. **Single-threaded**: Execution on main thread (Web Worker support planned) |
| 133 | +4. **No debugging UI**: No step-through or breakpoints yet |
| 134 | +5. **No save/restore**: Cannot snapshot emulator state |
| 135 | + |
| 136 | +## Browser Compatibility |
| 137 | + |
| 138 | +Tested on: |
| 139 | +- Chrome 90+ |
| 140 | +- Firefox 88+ |
| 141 | +- Safari 14+ |
| 142 | +- Edge 90+ |
| 143 | + |
| 144 | +Requires modern JavaScript (ES6+) and WebAssembly support. |
| 145 | + |
| 146 | +## Troubleshooting |
| 147 | + |
| 148 | +### "Failed to initialize Pyodide" |
| 149 | +- Check browser console for detailed errors |
| 150 | +- Ensure internet connection (Pyodide loads from CDN) |
| 151 | +- Try reloading the page |
| 152 | + |
| 153 | +### "Error loading file" |
| 154 | +- Verify file is valid ELF or binary |
| 155 | +- Check file was compiled for RV32I (not RV64) |
| 156 | +- Ensure file size < RAM size |
| 157 | + |
| 158 | +### Terminal not responding |
| 159 | +- Check browser console for errors |
| 160 | +- Try clicking Reset and reloading the program |
| 161 | +- Verify Ctrl-C works to stop execution |
| 162 | + |
| 163 | +### Slow performance |
| 164 | +- Disable tracing and checking options |
| 165 | +- Reduce RAM size if not needed |
| 166 | +- Try a different browser (Chrome typically fastest) |
| 167 | + |
| 168 | +## Development |
| 169 | + |
| 170 | +To modify the emulator: |
| 171 | + |
| 172 | +1. **JavaScript changes**: Edit files in `js/` and reload page |
| 173 | +2. **Python changes**: Edit files in `py/` and reload page (Pyodide fetches on startup) |
| 174 | +3. **Core emulator changes**: Modify parent directory files (cpu.py, ram.py, machine.py) |
| 175 | + |
| 176 | +## Future Enhancements |
| 177 | + |
| 178 | +- Web Worker execution for better performance |
| 179 | +- Virtual filesystem (IndexedDB-backed) |
| 180 | +- Block device support |
| 181 | +- Step-through debugging UI |
| 182 | +- Breakpoints and memory inspector |
| 183 | +- State save/restore to localStorage |
| 184 | +- Preloaded example programs |
| 185 | + |
| 186 | +## License |
| 187 | + |
| 188 | +Same as parent project (see root directory). |
| 189 | + |
| 190 | +## Credits |
| 191 | + |
| 192 | +Built on the RISC-V emulator by Ciro Cattuto. |
| 193 | +Uses Pyodide, xterm.js, and pyelftools. |
0 commit comments