English | 中文
Waveform Viewer is a high-performance web-based waveform viewer for VCD and FST files. The core parser is written in C++ and runs in the browser via WebAssembly.
You can access the online version hosted on GitHub Pages directly without installing any software:
👉 Access Waveform Viewer Online
If you wish to run it independently or use the desktop version (powered by Tauri), you can obtain pre-built binaries from the GitHub Releases page.
- Visit the Releases Page.
- Download the package or installer for your operating system (Windows, macOS, Linux).
- Extract and run it.
If you want to do secondary development or build it yourself, please follow these steps:
- Node.js (v20+ recommended)
- Emscripten (for compiling C++ to WASM)
- Make (build tool)
- Rust/Tauri (optional, required only for building the desktop version)
-
Fork and Clone the Repository:
git clone https://github.com/nanjo712/Waveform-Viewer.git cd Waveform-Viewer -
Compile WASM Core:
make wasm
-
Build Web Package:
make web
After the build is complete, the static web package will be stored in the
./distdirectory. -
(Optional) Build other targets:
- Native CLI:
make native - Desktop App:
make tauri - VSCode Extension:
make vscode(ormake vsixfor.vsixpackage)
- Native CLI:
-
Local Development Preview:
make dev
Then open
http://localhost:3000in your browser.
Note
Can I write plugins in WASM or native C++?
Currently, the Waveform Viewer core parser is written in C++ and compiled to WebAssembly (WASM) for high performance. However, all formatting plugins must be written in JavaScript or TypeScript. The plugin system relies on the browser's JavaScript engine to execute the format functions and dynamically inject them into the React frontend. If you have computationally heavy formatting logic, you could technically compile it to a separate WASM module and call it from your JavaScript plugin wrapper, but the plugin interface itself must remain in JS/TS.
This document explains how to write and register custom signal formatting plugins for the Waveform Viewer.
The Waveform Viewer supports custom formatters to display signal values in different ways (e.g., Hexadecimal, Float, ASCII, etc.). A plugin can define multiple "views" (formatters), each supporting specific signal widths or any width.
Plugins are built around two main TypeScript interfaces defined in frontend/packages/core/src/types/plugin.ts:
export interface FormatView {
id: string; // Unique identifier for the view
name: string; // Display name in the UI
supportedWidths: number[] | 'any'; // Supported signal bit-widths (e.g., [16, 32] or 'any')
format: (val: string, width: number) => { // The formatting function
display: string; // The formatted string to display
isX: boolean; // Whether the value contains undefined 'x' or 'X' bits
isZ: boolean; // Whether the value contains high-impedance 'z' or 'Z' bits
};
}
export interface FormatPlugin {
id: string; // Unique identifier for the plugin
name: string; // Name of the plugin bundle (e.g., 'IEEE 754 Float Formatter')
views: FormatView[]; // Array of format views provided by this plugin
}The core of a plugin is the format function. It receives:
val: Astringcontaining the raw binary representation of the signal (e.g.,"1010","x","z"). It may contain leading base indicators likeb, though it's recommended to strip them or handle them carefully.width: Anumberindicating the bit-width of the signal.
It must return an object with:
display: The final string to render on the waveform and signal list.isX: True if the value is undefined (X).isZ: True if the value is high-impedance (Z).
Here is a simple example of a plugin that formats 1-bit signals as True or False.
TypeScript:
import type { FormatPlugin } from './types/plugin';
export const myBooleanPlugin: FormatPlugin = {
id: 'my_boolean',
name: 'Boolean Formatter',
views: [
{
id: 'Bool',
name: 'True/False',
supportedWidths: [1], // Only supports 1-bit signals
format: (val: string, width: number) => {
// Handle X and Z states
const isX = val.toLowerCase().includes('x');
const isZ = val.toLowerCase().includes('z');
if (isX) return { display: 'X', isX, isZ };
if (isZ) return { display: 'Z', isX, isZ };
// Parse binary value
const cleanVal = val.replace(/^[bB]/, ''); // remove 'b' prefix if any
const boolValue = parseInt(cleanVal, 2) === 1;
return {
display: boolValue ? 'True' : 'False',
isX: false,
isZ: false
};
}
}
]
};JavaScript:
const myBooleanPlugin = {
id: 'my_boolean',
name: 'Boolean Formatter',
views: [
{
id: 'Bool',
name: 'True/False',
supportedWidths: [1],
format: (val, width) => {
const isX = val.toLowerCase().includes('x');
const isZ = val.toLowerCase().includes('z');
if (isX) return { display: 'X', isX, isZ };
if (isZ) return { display: 'Z', isX, isZ };
const cleanVal = val.replace(/^[bB]/, '');
const boolValue = parseInt(cleanVal, 2) === 1;
return {
display: boolValue ? 'True' : 'False',
isX: false,
isZ: false
};
}
}
]
};Plugins can be registered in two ways:
If you are adding a built-in plugin directly to the repository:
- Place your plugin file in
frontend/packages/core/src/plugins/. - Import it in
frontend/packages/core/src/state/reducer.ts. - Add it to the
formatPluginsarray in theinitialStateofreducer.ts.
For external plugins loaded at runtime, the application exposes a global registration method on the window object:
window.WaveformViewer.registerPlugin(myBooleanPlugin);When this is called, the plugin is dispatched to the application state and becomes immediately available in the UI for users to select for matching signals.
Signals passed to the format function might need parsing. A common utility pattern used in the core plugins is to strip the binary prefix and pad the string to the correct width:
function parseBase(val: string, width: number) {
let raw = val;
if (raw.startsWith('b') || raw.startsWith('B')) raw = raw.slice(1);
const isX = raw.includes('x') || raw.includes('X');
const isZ = raw.includes('z') || raw.includes('Z');
const paddedBin = raw.padStart(width, '0');
return { isX, isZ, paddedBin };
}Using this pattern ensures uniform handling for 'X'/'Z' states and proper alignment for arithmetic conversions.