RV1 Verilator to WASM Build Notes
This document captures the full working path and all workarounds used to run the copied RV1 RTL in the browser as WebAssembly.
Quick Start
From rv1_copy/wasm:
make rv1-install
cd ../../
npm run buildExpected copied runtime assets in content/rv1:
- rv1_verilated.wasm
- program.bin
- data.bin
Expected generated site assets in build/rv1:
- verilated.html
- rv1_verilated.js
- rv1_verilated.wasm
- program.bin
- data.bin
Goal
End-to-end flow:
- Build a bare-metal RV32IM program from C.
- Verilate RV1 RTL to C++.
- Compile the Verilated model plus harness to standalone WASM.
- Load program/data binaries into IMEM/DMEM in-browser.
- Run cycles and blit framebuffer to canvas.
Workspace Paths
- Primary integration: content/rv1
- RTL/toolchain work area: rv1_copy
- WASM build orchestration: rv1_copy/wasm/Makefile
- RV32 compile script: rv1_copy/scripts/compile_c.sh
Prerequisites
- Emscripten available in shell, example check: emcc --version
- Verilator installed, example check: verilator --version
- RISC-V GCC toolchain available, example check: riscv64-unknown-elf-gcc --version
On macOS/Homebrew, the RISC-V toolchain needed additional runtime libs:
- Install isl:
brew install isl- Install libmpc:
brew install libmpcIf Homebrew tap trust blocks package install, trust only required formulas from the tap and retry.
Required Source Workarounds
1. Make IMEM/DMEM writable from Verilated C++
File: rv1_copy/rtl/riscv_top.v
- Mark memories as Verilator-public:
- imem: /* verilator public_flat_rw */
- dmem: /* verilator public_flat_rw */
- Guard file-based memory init so browser runtime can load binaries directly:
- Wrap $readmemh calls with RV1_NO_MEM_INIT.
This enables direct memory pointer access from the C++ harness and avoids runtime file I/O assumptions in WASM.
2. Add browser-safe RV32 linker and startup
- Startup: rv1_copy/sw/start.S
- Linker script: rv1_copy/scripts/linker_harvard_browser.ld
These provide bare-metal entry and IMEM/DMEM placement suitable for the browser simulation memory map.
3. Extend RV32 compile script outputs
File: rv1_copy/scripts/compile_c.sh
Important details:
- Uses riscv64-unknown-elf-* tools with:
- -march=rv32im
- -mabi=ilp32
- -ffreestanding -nostdlib -nostartfiles
- Uses -O1 for stable output in this flow.
- Emits both hex and binary artifacts:
- program.hex and data.hex
- program.bin and data.bin
- Ensures data.hex exists even when no data sections are present.
4. Add Verilated WASM harness
File: rv1_copy/wasm/rv1_verilated_harness.cpp
Exports C ABI functions for JS:
- init/reset/tick: rv1_init, rv1_reset, rv1_run_cycles
- memory metadata: rv1_get_imem_words, rv1_get_dmem_words
- memory pointers: rv1_get_imem_bytes_ptr, rv1_get_dmem_bytes_ptr
- helpers: rv1_clear_memories, rv1_get_pc
- framebuffer info: rv1_get_fb_width, rv1_get_fb_height, rv1_get_fb_ptr
5. Add POSIX affinity stubs for WASM link
File: rv1_copy/wasm/rv1_wasm_posix_shims.c
Provided stubs:
- pthread_getaffinity_np
- pthread_setaffinity_np
- sched_getcpu
This resolves unresolved symbols from Verilator runtime threading code when targeting browser WASM.
6. Verilator/Emscripten portability flags
File: rv1_copy/wasm/Makefile
Key flags/workarounds:
- Verilator define to skip HDL file init at runtime load stage:
- +define+RV1_NO_MEM_INIT
- Larger model memories for browser workload:
- -GIMEM_SIZE=65536
- -GDMEM_SIZE=524288
- Emscripten compile flags:
- -DVL_IGNORE_UNKNOWN_ARCH
- -include stdint.h
- --no-entry
- -sSTANDALONE_WASM=1
- Include Verilator runtime source files explicitly during em++ link.
- Resolve VERILATOR_ROOT via environment value or executable-relative fallback.
Build Commands
From rv1_copy/wasm:
- Build full RTL-to-WASM pipeline and copy artifacts into content:
make rv1-installThis runs:
verilate
rv1-wasm
rv32-content
copy rv1_verilated.wasm into content
Build codeng site:
cd ../../
npm run buildRuntime Integration
Browser loader: content/rv1/rv1_verilated.js
Runtime behavior:
- Fetch rv1_verilated.wasm.
- Instantiate and call rv1_init + rv1_clear_memories.
- Fetch program.bin and data.bin.
- Copy bytes into IMEM/DMEM via exported pointers.
- Reset core, run fixed number of cycles.
- Read framebuffer bytes from DMEM and convert BGRA memory layout to RGBA for canvas.
Demo page: content/rv1/verilated.md
Expected Artifacts
After make rv1-install:
After npm run build:
- build/rv1/verilated.html
- build/rv1/rv1_verilated.js
- build/rv1/rv1_verilated.wasm
- build/rv1/program.bin
- build/rv1/data.bin
Known Failure Modes and Fixes
dyld: libisl missing when invoking riscv64-unknown-elf-gcc Fix: brew install isl
dyld: libmpc.3.dylib missing when invoking riscv64-unknown-elf-gcc Fix: brew install libmpc
Verilator runtime unresolved affinity symbols under wasm Fix: include rv1_copy/wasm/rv1_wasm_posix_shims.c in em++ link
Verilator arch portability errors in wasm toolchain Fix: compile with -DVL_IGNORE_UNKNOWN_ARCH and include stdint.h
Browser cannot rely on HDL $readmemh files Fix: compile RTL with RV1_NO_MEM_INIT and load binaries from JS into exported memory pointers