Thanks to visit codestin.com
Credit goes to lib.rs

#zlib #simd #fst #signal #payload #waveform #bit-packing #mmap #ascii

wavefst

Modern Rust implementation of the Fast Signal Trace (FST) binary waveform format

1 unstable release

Uses new Rust 2024

0.1.0 Oct 25, 2025

#316 in Compression

Custom license

195KB
4.5K SLoC

wavefst

Crates.io Documentation License

Modern Rust reader and writer for the Fast Signal Trace (FST) waveform format.

wavefst streams FST data without copying, stays compatible with the original C libfst implementation, and layers ergonomic APIs on top of the low-level block layout. Readers, writers, benchmarks, and fuzz targets all live in the same crate so you can inspect existing traces, build new ones, or validate interoperability from a single dependency.


Highlights

  • Full block coverage – header, geometry, hierarchy, blackout, and every value-change variant (FST_BL_VCDATA*) are decoded and encoded symmetrically.
  • Zero-copy iteration – value changes stream straight from decoded buffers with alias handling, initial frames, and timestamps resolved for you.
  • Pluggable compression – raw, zlib, LZ4, and FastLZ logic is reusable between reader and writer pipelines, each gated behind a feature flag.
  • Async, SIMD, serde – optional helpers wrap the synchronous APIs for async I/O, fast ASCII→bit packing, and serialisable hierarchy/value-change snapshots.
  • Tooling ready – Criterion benches, libFuzzer harnesses, and integration tests are included to keep regressions in check.

Installation

cargo add wavefst

The default feature set enables gzip/zlib (gzip), LZ4 (lz4), memory mapping (mmap), and the SSE2 packed-bit fast path (simd). Disable them with --no-default-features and opt back into the ones you need.


Quick Start

Reading a trace

use wavefst::{ReaderBuilder, SignalValue};

fn dump_changes(path: &str) -> wavefst::Result<()> {
    let file = std::fs::File::open(path)?;
    let mut reader = ReaderBuilder::new(file).build()?;

    while let Some(mut block) = reader.next_value_changes()? {
        while let Some(event) = block.next() {
            let event = event?;
            println!("t={} handle={} value={:?}", event.timestamp, event.handle, event.value);
        }
    }
    Ok(())
}

Writing a trace

use std::borrow::Cow;
use wavefst::{
    ChainCompression, FstWriter, GeomEntry, Header, ScopeType, SignalValue, TimeCompression,
    VarDir, VarType,
};

fn build_example(path: &str) -> wavefst::Result<()> {
    let file = std::fs::File::create(path)?;
    let mut writer = FstWriter::builder(file)
        .chain_compression(ChainCompression::Lz4)
        .time_compression(TimeCompression::Zlib)
        .build()?;

    writer.begin_scope(ScopeType::VcdModule, "tb", None)?;
    let bit = writer.add_variable(VarType::VcdWire, VarDir::Implicit, "bit", GeomEntry::Fixed(1))?;
    let vec = writer.add_variable(VarType::VcdWire, VarDir::Implicit, "vec", GeomEntry::Fixed(8))?;
    writer.end_scope()?;

    let mut header = Header::default();
    header.version = "wavefst-demo".into();
    header.vc_section_count = 1;
    header.end_time = 20;
    writer.write_header(header)?;

    writer.emit_change(0, bit, SignalValue::Bit('0'))?;
    writer.emit_change(10, bit, SignalValue::Bit('1'))?;
    writer.emit_change(12, vec, SignalValue::Vector(Cow::Borrowed("10101010")))?;
    writer.finish()?;
    Ok(())
}

Feature Flags

Feature Default Description
gzip Enable zlib/deflate support (hierarchy and VC blocks, optional z-wrapper).
lz4 Support LZ4-compressed hierarchy blocks and value-change chains.
fastlz ⛔️ Add FastLZ decompression/compression for value-change chains.
parallel ⛔️ Use Rayon to decode chain payloads in parallel while keeping results sorted.
serde ⛔️ Provide serialisable hierarchy and value-change snapshots (serde_support).
mmap Expose the memory-mapped reader backend (io::MemoryMap).
async ⛔️ Include buffered async wrappers (async_support) built on tokio.
simd Use SSE2 to accelerate ASCII vector packing (falls back to scalar elsewhere).

Disable defaults with --no-default-features and enable the subset you need, for example:

cargo add wavefst --no-default-features --features "gzip parallel"

Performance

Criterion benches (release profile, Apple M-series, macOS 25.0.0)

Benchmark Raw Zlib LZ4
reader_next_value_changes 32.3 µs 41.7 µs 34.2 µs
writer_emit_change 115 µs 194 µs 125 µs

Run with:

cargo bench

Comparison with C libfst

Using the upstream fstReaderIterBlocks helper (compiled with clang -std=c99 -O2):

Scenario Rust (µs) C helper (µs) Speed-up
baseline, raw 50.9 335 551 ×6 590
baseline, wrapped 57.0 6 436 ×113
wide, raw 130.3 2 933 ×22.5
wide, wrapped 65.3 2 191 ×33.6

Debug builds show similar ratios (×4.6–×5.6). Event counters matched exactly between both implementations.


Async, SIMD, and serde helpers

  • wavefst::async_support::{AsyncReader, AsyncWriter} buffer async sources/sinks using tokio before delegating to the synchronous codecs. Useful when you cannot block the reactor thread.
  • wavefst::serde_support (behind serde) snapshots hierarchy trees and value changes as owned data structures that plug directly into serde_json, CBOR, etc.
  • simd enables an SSE2 fast path for ASCII vector packing; non-x86 targets automatically use the scalar implementation.

Tooling

  • Testscargo test (add --features "async gzip serde simd" to exercise optional paths).
  • Benchescargo bench compares reader/writer throughput across compression modes.
  • Docsdoc/fst_format.md describes the binary format; doc/rust_crate_design.md covers the crate layout and design notes (both ASCII safe).

Roadmap

  • Optional CLI (wavefst-tool) for inspecting traces and converting to other formats.
  • Additional fixtures that exercise mixed compression, alias chains, and multi-block timelines.
  • Configurable logging hooks (tracing) for long-running ingest jobs.

Contributions, bug reports, and ideas are very welcome. File an issue or open a pull request with a reproduction trace if you hit a corner case.


License

Licensed under the modified MIT License. See LICENSE for details.

Dependencies

~6–10MB
~104K SLoC