Self-Hosted Real-Time Collaborative Chip Design and Verification with WebAssembly and CRDTs
Quick Summary (TL;DR)
- What: A self-hosted platform where hardware engineers edit RTL together in real time and simulate it directly in the browser.
- Key tech: WebAssembly (Wasm) compiles Verilator for in-browser simulation; CRDTs (Yjs/Automerge) merge concurrent edits without locks.
- Why: Keeps proprietary IP on your own infrastructure, removes cloud lock-in, and eliminates “who has the latest file?” version conflicts.
- How: Compile Verilator to Wasm → wire a CRDT document model → run a WebSocket sync server → integrate simulation → harden and deploy.
- Effort: A small team can stand up a working prototype in a weekend and a production-grade deployment in 2–4 weeks.
Hardware design has a collaboration problem. RTL lives in version control, but the actual work — tweaking a state machine, debugging a testbench, fixing a timing path — happens in isolation. You commit, you push, you wait for CI, and you resolve merge conflicts in files that are structurally fragile. Meanwhile, the tools that could help run on expensive, license-gated desktops.
This guide shows how to build a self-hosted platform that fixes both problems: real-time multi-user RTL editing powered by CRDTs, and in-browser simulation powered by WebAssembly. Everything runs on infrastructure you control.
Why Self-Hosted Collaborative Chip Design?
Cloud EDA platforms exist, but they come with real trade-offs for silicon teams:
- IP confidentiality. Sending unreleased RTL to a third-party SaaS is often a non-starter for legal and export-control reasons.
- Cost. Per-seat cloud simulation licensing scales badly for large teams.
- Latency and control. You own the hardware, the network path, and the upgrade cadence.
- Integration freedom. You can wire the platform into your own CI, artifact stores, and internal auth.
A self-hosted approach gives you the collaboration benefits of a Google-Docs-style editor while keeping the crown jewels inside your perimeter.
Core Technologies: WebAssembly and CRDTs
Two technologies make this practical. They solve orthogonal problems: how code runs and how edits merge.
WebAssembly for In-Browser Simulation
WebAssembly is a compact, near-native binary instruction format that runs in every modern browser. By compiling Verilator — the fast open-source Verilog/SystemVerilog simulator — to Wasm, you get a full simulation engine executing client-side. The benefits:
- Zero server compute for simulation. The user’s machine does the work.
- Instant feedback. No round-trip to a build farm for a quick waveform check.
- Sandboxed execution. Wasm runs in the browser’s security sandbox, so untrusted RTL can’t escape.
The trade-off: Wasm is faster than JavaScript but slower than native code, typically landing at 50–80% of native performance for compute-heavy workloads.
CRDTs for Conflict-Free Real-Time Editing
CRDTs (Conflict-free Replicated Data Types) are data structures that can be replicated across peers and merged deterministically — no central lock, no manual conflict resolution. For text editing, sequence CRDTs (as used by Yjs and Automerge) guarantee that concurrent insertions and deletions converge to the same final state on every client.
This is the property that makes real-time RTL editing safe: two engineers editing different modules, or even the same file, will never corrupt each other’s work.
System Architecture Overview
The architecture splits cleanly into client and server responsibilities.
flowchart TD
subgraph Client["Browser Client"]
Editor["Monaco Editor<br/>(RTL text)"]
CRDT["CRDT Document<br/>(Yjs)"]
Wasm["Verilator.wasm<br/>+ Waveform Viewer"]
Editor <--> CRDT
CRDT --> Wasm
end
subgraph Server["Self-Hosted Server"]
WS["WebSocket Sync Server<br/>(y-websocket)"]
Auth["Auth / RBAC<br/>(OIDC)"]
DB[("Persistence<br/>PostgreSQL + WAL")]
Git["Git Snapshots"]
WS --> DB
WS --> Git
end
CRDT <-->|"CRDT updates<br/>(binary deltas)"| WS
Auth -.->|"token"| WS
Client-Side Components
- Monaco Editor (the engine behind VS Code) for syntax highlighting of Verilog/SystemVerilog.
- Yjs bound to Monaco via
y-monaco, providing the shared document. - Verilator.wasm for compilation and simulation, plus a waveform viewer (e.g., a Wasm build of GTKWave or a custom canvas renderer).
- Awareness layer showing cursors, selections, and presence of other users.
Server-Side Components
- y-websocket (or a custom WebSocket server) to relay and persist CRDT updates.
- PostgreSQL with JSONB columns to store document state and update logs.
- OIDC-based authentication (Keycloak, Authentik) for identity and role-based access.
- Git integration to snapshot the merged RTL at intervals.
Step-by-Step Implementation Guide
Step 1: Set Up the Development Environment
You need Emscripten (for Wasm compilation), Node.js, and a Postgres instance.
# Install Emscripten SDK
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk && ./emsdk install latest && ./emsdk activate latest
source ./emsdk_env.sh
# Scaffold the app
npm create vite@latest rtl-collab -- --template react-ts
cd rtl-collab
npm install yjs y-monaco y-websocket monaco-editor
Step 2: Compile Verilator to WebAssembly
Verilator is C++, so it compiles to Wasm with Emscripten. A minimal build:
git clone https://github.com/verilator/verilator.git
cd verilator
autoconf && ./configure
emmake make -j$(nproc)
You’ll typically wrap the Verilator API in a thin C++ shim that Emscripten exposes to JavaScript:
// shim.cpp
#include <emscripten/bind.h>
#include "verilated.h"
std::string simulate(const std::string& rtl) {
// Write RTL to MEMFS, invoke Verilator, capture VCD output
return run_verilator(rtl);
}
EMSCRIPTEN_BINDINGS(verilator_module) {
emscripten::function("simulate", &simulate);
}
Compile with emcc shim.cpp -o verilator.js -s MODULARIZE=1 -s EXPORT_ES6=1 --bind.
Step 3: Implement CRDT-Based Collaborative Editing
Bind a Yjs document to Monaco. Each file becomes a Y.Text keyed by path.
import * as Y from 'yjs';
import { MonacoBinding } from 'y-monaco';
import * as monaco from 'monaco-editor';
const ydoc = new Y.Doc();
const provider = new WebsocketProvider('wss://eda.internal/ws', 'soc-top', ydoc);
const ytext = ydoc.getText('rtl/top.sv');
const editor = monaco.editor.create(document.getElementById('editor')!, {
value: '',
language: 'systemverilog',
});
new MonacoBinding(ytext, editor.getModel()!, new Set([editor]), provider.awareness);
For structural RTL concepts (module boundaries, port lists), define a custom CRDT type — for example, a Y.Map per module with a Y.Array of ports — so that port reordering merges sensibly.
Step 4: Build the Real-Time Synchronization Server
Run a y-websocket server behind your reverse proxy:
HOST=0.0.0.0 PORT=1234 npx y-websocket
Add a persistence adapter so updates survive restarts. A minimal Postgres-backed store:
// persist.ts
import { Pool } from 'pg';
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
export async function appendUpdate(docName: string, update: Uint8Array) {
await pool.query(
'INSERT INTO crdt_updates (doc_name, update, created_at) VALUES ($1, $2, now())',
[docName, Buffer.from(update)]
);
}
The server relays binary CRDT deltas (typically a few hundred bytes each) and appends them to a write-ahead log for durability.
Step 5: Integrate Simulation and Verification
Wire the merged RTL into the Wasm simulator. Trigger a compile on demand or on a debounce, then render waveforms.
import VerilatorModule from './verilator.js';
const verilator = await VerilatorModule();
const vcd = verilator.simulate(ytext.toString());
renderWaveform(vcd); // feed to canvas viewer
For heavier designs, offload to a server-side native Verilator instance and stream the VCD back over WebSocket. This hybrid model keeps the browser responsive while supporting large SoCs.
Step 6: Deploy and Secure the Platform
Containerize both services and front them with a reverse proxy that terminates TLS:
# docker-compose.yml (excerpt)
services:
sync:
image: rtl-collab-sync
environment:
- DATABASE_URL=postgres://eda:secret@db:5432/eda
ports: ["1234:1234"]
web:
image: rtl-collab-web
ports: ["8080:80"]
db:
image: postgres:16
volumes: ["pgdata:/var/lib/postgresql/data"]
volumes: { pgdata: {} }
Enforce authentication at the proxy (OIDC) and validate the JWT inside the sync server before accepting a document subscription.
Comparison of CRDT Libraries for RTL Editing
| Feature | Yjs | Automerge | Loro |
|---|---|---|---|
| Text CRDT performance | Excellent (optimized YATA) | Good (slower for large docs) | Excellent |
| Binary encoding | Compact, efficient | Compact | Very compact |
| Monaco/editor bindings | Mature (y-monaco) | Limited | Emerging |
| Awareness/presence | Built-in | Manual | Built-in |
| Persistence ecosystem | Rich (LevelDB, Postgres) | Growing | Growing |
| License | MIT | MIT | MIT |
| Best for | Real-time RTL editing | Offline-first, history-heavy | Newer, high-perf projects |
For RTL specifically, Yjs is the pragmatic default: its Monaco integration, presence layer, and mature persistence adapters reduce the amount of custom code you write.
Performance and Scalability Considerations
- Document size. Keep individual files under ~2 MB. Split large SoCs into per-module documents so CRDT overhead stays linear.
- Update frequency. Batch keystrokes with a ~50 ms debounce to cut WebSocket traffic.
- Simulation offload. Use client-side Wasm for designs up to ~100k gates; route larger runs to a native server pool.
- Horizontal scaling. Run multiple sync servers with a shared Postgres backend; use sticky sessions or a pub/sub bus (Redis) to relay updates between nodes.
Security and Access Control
- Authentication: OIDC with short-lived JWTs; never trust client-asserted identity.
- Authorization: Map documents to roles (viewer, editor, admin) and enforce per-document ACLs in the sync server.
- Transport: TLS everywhere, including internal WebSocket traffic.
- Simulation sandbox: Wasm’s memory isolation protects the host, but validate RTL inputs to prevent resource-exhaustion attacks (infinite loops, huge allocations).
- Audit: Log every CRDT update with user ID and timestamp for provenance.
Conclusion
Self-hosting collaborative chip design is no longer a research project. WebAssembly makes in-browser Verilator simulation viable, and CRDTs make concurrent RTL editing safe without locks or merge conflicts. By combining them behind your own authentication and persistence layer, you get a platform that keeps IP in-house, cuts per-seat costs, and gives hardware teams the real-time workflow software engineers have enjoyed for years.
Start small: one project, one sync server, one Wasm build. Then scale the simulation pool and ACLs as adoption grows.
FAQ
What are the hardware requirements for self-hosting this platform? For small teams (up to 10 concurrent users), a server with 4 CPU cores, 16 GB RAM, and 100 GB SSD is sufficient. Since Wasm simulation runs client-side, server load is mainly synchronization and storage. For larger teams, scale to 8+ cores, 32 GB RAM, and add a load balancer.
Can I use this platform with existing EDA tools like Synopsys or Cadence? Partially. The platform focuses on open-source tools (Verilator, Yosys, Icarus Verilog) compiled to Wasm. You can export RTL and import it into proprietary flows, but real-time co-simulation with commercial tools isn’t supported out of the box.
How do CRDTs handle conflicts in RTL code editing? Sequence CRDTs (like Yjs) automatically merge concurrent edits without locks — insertions and deletions are commutative and idempotent, so all clients converge. If two users edit the same line, characters may interleave, but the final state is consistent. Structural conflicts (e.g., port changes) benefit from custom CRDT rules.
Is WebAssembly fast enough for gate-level simulation? Wasm is much faster than JavaScript but slower than native code. It handles RTL simulation for small to medium designs (up to ~100k gates) well. For gate-level or large SoC simulation, offload to a server-side native Verilator instance and stream results back.
How do I ensure data persistence and backup? Persist CRDT updates to a database (PostgreSQL with JSONB, or a Yjs LevelDB store), enable a write-ahead log for durability, and take regular backups. Complement this with periodic Git snapshots of the merged RTL for human-readable history.