Skip to main content

bitloom_sim/
fst.rs

1//! Optional FST via documented `vcd2fst` (AD-24 / FR31). Default dump remains VCD.
2
3use std::path::{Path, PathBuf};
4use std::process::Command;
5
6/// How to obtain FST: never a homegrown writer (AD-24).
7pub fn resolve_vcd2fst() -> Result<PathBuf, FstError> {
8    resolve_vcd2fst_from(std::env::var_os("RHDL_VCD2FST"), std::env::var_os("PATH"))
9}
10
11pub(crate) fn resolve_vcd2fst_from(
12    override_bin: Option<std::ffi::OsString>,
13    path: Option<std::ffi::OsString>,
14) -> Result<PathBuf, FstError> {
15    if let Some(p) = override_bin {
16        let pb = PathBuf::from(&p);
17        if pb.is_file() {
18            return Ok(pb);
19        }
20        return Err(FstError::Message(format!(
21            "RHDL_VCD2FST={p:?} is not a file; install gtkwave's vcd2fst or point this env at it"
22        )));
23    }
24    which_in("vcd2fst", path).ok_or_else(|| {
25        FstError::Message(
26            "FST requested but vcd2fst not found; install gtkwave (vcd2fst) or set RHDL_VCD2FST \
27             (AD-24; Verilator --trace-fst is for Verilated C++ models, not native tick)"
28                .into(),
29        )
30    })
31}
32
33fn which_in(name: &str, path: Option<std::ffi::OsString>) -> Option<PathBuf> {
34    let path = path?;
35    for dir in std::env::split_paths(&path) {
36        let cand = dir.join(name);
37        if cand.is_file() {
38            return Some(cand);
39        }
40    }
41    None
42}
43
44#[derive(Debug)]
45pub enum FstError {
46    Message(String),
47    Io(std::io::Error),
48}
49
50impl std::fmt::Display for FstError {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        match self {
53            FstError::Message(m) => write!(f, "{m}"),
54            FstError::Io(e) => write!(f, "{e}"),
55        }
56    }
57}
58
59impl std::error::Error for FstError {}
60
61impl From<std::io::Error> for FstError {
62    fn from(e: std::io::Error) -> Self {
63        FstError::Io(e)
64    }
65}
66
67pub(crate) fn convert_vcd_to_fst(converter: &Path, vcd: &Path, fst: &Path) -> Result<(), FstError> {
68    let status = Command::new(converter)
69        .arg(vcd)
70        .arg(fst)
71        .status()
72        .map_err(|e| FstError::Message(format!("spawn vcd2fst: {e}")))?;
73    if !status.success() {
74        return Err(FstError::Message(format!(
75            "vcd2fst failed converting {vcd:?} -> {fst:?}"
76        )));
77    }
78    if !fst.is_file() {
79        return Err(FstError::Message(format!("vcd2fst did not write {fst:?}")));
80    }
81    Ok(())
82}