// SPDX-FileCopyrightText: 2025-2026 Yu-Sheng Lin // SPDX-FileCopyrightText: 2025-2026 Yoda Lee // SPDX-License-Identifier: MIT // Project: libfstwriter // Website: https://github.com/gtkwave/libfstwriter #pragma once // direct include #include "fstcpp/fstcpp.h" // C system headers // C++ standard library headers #include #include #include #include #include #include #if __cplusplus >= 201703L # include #endif // Other libraries' .h files. // Your project's .h files. #include "fstcpp/fstcpp_assertion.h" #include "fstcpp/fstcpp_variable_info.h" namespace fst { class Writer; namespace detail { // We define WriterWaveData here for better code inlining, no forward declaration struct BlackoutData { std::vector buffer; uint64_t previous_timestamp = 0; uint64_t count = 0; void emitDumpActive(uint64_t current_timestamp, bool enable); }; // We define ValueChangeData here for better code inlining, no forward declaration struct ValueChangeData { std::vector variable_infos; std::vector timestamps; ValueChangeData(); ~ValueChangeData(); void writeInitialBits(std::vector &os) const; std::vector> computeWaveData() const; static std::vector uniquifyWaveData(std::vector> &data); static uint64_t encodePositionsAndwriteUniqueWaveData( std::ostream &os, const std::vector> &unique_data, std::vector &positions, WriterPackType pack_type ); static void writeEncodedPositions( const std::vector &encoded_positions, std::ostream &os ); void writeTimestamps(std::vector &os) const; void keepOnlyTheLatestValue(); }; } // namespace detail class Writer { friend class WriterTest; public: Writer() {} Writer(const string_view_pair name) { if (name.second != 0) open(name); } ~Writer() { close(); } Writer(const Writer &) = delete; Writer(Writer &&) = delete; Writer &operator=(const Writer &) = delete; Writer &operator=(Writer &&) = delete; // File control void open(const string_view_pair name); void close(); ////////////////////////////// // Header manipulation API ////////////////////////////// const Header &getHeader() const; void setTimecale(int8_t timescale) { header_.timescale = timescale; } void setWriter(const string_view_pair Writer) { const auto len = std::min(Writer.second, sizeof(header_.writer)); std::copy_n(Writer.first, len, header_.writer); if (len != sizeof(header_.writer)) { header_.writer[len] = '\0'; } } void setDate(const string_view_pair date_str) { const auto len = date_str.second; FST_CHECK_EQ(len, sizeof(header_.date) - 1); std::copy_n(date_str.first, len, header_.date); header_.date[len] = '\0'; } void setTimezero(int64_t timezero) { header_.timezero = timezero; } ////////////////////////////// // Change scope API ////////////////////////////// void setScope( Hierarchy::ScopeType scopetype, const string_view_pair scopename, const string_view_pair scopecomp ); void upscope(); ////////////////////////////// // Attribute / Misc API ////////////////////////////// void setAttrBegin( Hierarchy::AttrType attrtype, Hierarchy::AttrSubType subtype, const string_view_pair attrname, uint64_t arg ); inline void setAttrEnd() { hierarchy_buffer_.push_back( static_cast(Hierarchy::ScopeControlType::GEN_ATTR_END) ); } EnumHandle createEnumTable( const string_view_pair name, uint32_t min_valbits, const std::vector> &literal_val_arr ); inline void emitEnumTableRef(EnumHandle handle) { setAttrBegin( Hierarchy::AttrType::MISC, Hierarchy::AttrSubType::MISC_ENUMTABLE, make_string_view_pair(nullptr, 0), handle ); } inline void setWriterPackType(WriterPackType pack_type) { FST_CHECK(pack_type != WriterPackType::ZLIB and pack_type != WriterPackType::FASTLZ); pack_type_ = pack_type; } ////////////////////////////// // Create variable API ////////////////////////////// Handle createVar( Hierarchy::VarType vartype, Hierarchy::VarDirection vardir, uint32_t bitwidth, const string_view_pair name, uint32_t alias_handle ); Handle createVar2( Hierarchy::VarType vartype, Hierarchy::VarDirection vardir, uint32_t bitwidth, const string_view_pair name, uint32_t alias_handle, const string_view_pair type, Hierarchy::SupplementalVarType svt, Hierarchy::SupplementalDataType sdt ); ////////////////////////////// // Waveform API ////////////////////////////// void emitTimeChange(uint64_t tim); void emitDumpActive(bool enable); void emitValueChange( Handle handle, const uint32_t *val, EncodingType encoding = EncodingType::BINARY ); void emitValueChange( Handle handle, const uint64_t *val, EncodingType encoding = EncodingType::BINARY ); // Pass by value for small integers void emitValueChange(Handle handle, uint64_t val); // Add support for C-string value changes (e.g. fst string values) // Note: This function is mainly for GtkWave compatibility. // It is very dirty and inefficient, users should avoid using it. // - For double handles, const char* is interpreted as a double* (8B) // - For normal integer handles, const char* is "01xz..." (1B per bit) // We only ensure that this function works where Verilator use it. void emitValueChange(Handle handle, const char *val); ////////////////////////////// // Alias version ////////////////////////////// // Constructor Writer(const char *name) : Writer(make_string_view_pair(name)) {} Writer(const std::string &name) : Writer(make_string_view_pair(name.c_str(), name.size())) {} // Open inline void open(const char *name) { open(make_string_view_pair(name)); } inline void open(const std::string &name) { open(make_string_view_pair(name.c_str(), name.size())); } // setWriter inline void setWriter(const char *Writer) { if (Writer) setWriter(make_string_view_pair(Writer)); } inline void setWriter(const std::string &Writer) { setWriter(make_string_view_pair(Writer.c_str(), Writer.size())); } // setDate inline void setDate(const char *date_str) { if (date_str) setDate(make_string_view_pair(date_str)); } inline void setDate(const std::string &date_str) { setDate(make_string_view_pair(date_str.c_str(), date_str.size())); } inline void setDate(const std::tm *d) { setDate(make_string_view_pair(std::asctime(d))); } inline void setDate() { // set date to now std::time_t t = std::time(nullptr); setDate(std::localtime(&t)); } // CreateVar(2) inline Handle createVar( Hierarchy::VarType vartype, Hierarchy::VarDirection vardir, uint32_t bitwidth, const char *name, uint32_t alias_handle ) { FST_CHECK_NE(name, static_cast(nullptr)); return createVar(vartype, vardir, bitwidth, make_string_view_pair(name), alias_handle); } inline Handle createVar( Hierarchy::VarType vartype, Hierarchy::VarDirection vardir, uint32_t bitwidth, const std::string &name, uint32_t alias_handle ) { return createVar( vartype, vardir, bitwidth, make_string_view_pair(name.c_str(), name.size()), alias_handle ); } // setScope inline void setScope( Hierarchy::ScopeType scopetype, const std::string &scopename, const std::string &scopecomp ) { setScope( scopetype, make_string_view_pair(scopename.c_str(), scopename.size()), make_string_view_pair(scopecomp.c_str(), scopecomp.size()) ); } inline void setScope( Hierarchy::ScopeType scopetype, const char *scopename, const char *scopecomp ) { setScope(scopetype, make_string_view_pair(scopename), make_string_view_pair(scopecomp)); } // setAttrBegin inline void setAttrBegin( Hierarchy::AttrType attrtype, Hierarchy::AttrSubType subtype, const char *attrname, uint64_t arg ) { setAttrBegin(attrtype, subtype, make_string_view_pair(attrname), arg); } // CreateEnumTable EnumHandle createEnumTable( const char *name, uint32_t min_valbits, const std::vector> &literal_val_arr ) { std::vector> arr; arr.reserve(literal_val_arr.size()); for (const auto &p : literal_val_arr) { arr.emplace_back(make_string_view_pair(p.first), make_string_view_pair(p.second)); } return createEnumTable(make_string_view_pair(name), min_valbits, arr); } // CreateVar2 inline Handle createVar2( Hierarchy::VarType vartype, Hierarchy::VarDirection vardir, uint32_t bitwidth, const char *name, uint32_t alias_handle, const char *type, Hierarchy::SupplementalVarType svt, Hierarchy::SupplementalDataType sdt ) { return createVar2( vartype, vardir, bitwidth, make_string_view_pair(name), alias_handle, make_string_view_pair(type), svt, sdt ); } // Flush value change data inline void flushValueChangeData() { flush_pending_ = true; } #if __cplusplus >= 201703L // All APIs with string_view_pair --> define a // string_view version and forward to the string_view_pair version inline Writer(std::string_view name) : Writer(make_string_view_pair(name.data(), name.size())) {} inline void open(std::string_view name) { open(make_string_view_pair(name.data(), name.size())); } inline void setWriter(std::string_view Writer) { setWriter(make_string_view_pair(Writer.data(), Writer.size())); } inline void setDate(std::string_view date_str) { setDate(make_string_view_pair(date_str.data(), date_str.size())); } inline void setScope( Hierarchy::ScopeType scopetype, std::string_view scopename, std::string_view scopecomp ) { setScope( scopetype, make_string_view_pair(scopename.data(), scopename.size()), make_string_view_pair(scopecomp.data(), scopecomp.size()) ); } inline void setAttrBegin( Hierarchy::AttrType attrtype, Hierarchy::AttrSubType subtype, std::string_view attrname, uint64_t arg ) { setAttrBegin( attrtype, subtype, make_string_view_pair(attrname.data(), attrname.size()), arg ); } EnumHandle createEnumTable( std::string_view name, uint32_t min_valbits, const std::vector> &literal_val_arr ) { std::vector> arr; arr.reserve(literal_val_arr.size()); for (const auto &p : literal_val_arr) { arr.emplace_back( make_string_view_pair(p.first.data(), p.first.size()), make_string_view_pair(p.second.data(), p.second.size()) ); } return createEnumTable(make_string_view_pair(name.data(), name.size()), min_valbits, arr); } inline Handle createVar( Hierarchy::VarType vartype, Hierarchy::VarDirection vardir, uint32_t bitwidth, std::string_view name, uint32_t alias_handle ) { return createVar( vartype, vardir, bitwidth, make_string_view_pair(name.data(), name.size()), alias_handle ); } inline Handle createVar2( Hierarchy::VarType vartype, Hierarchy::VarDirection vardir, uint32_t bitwidth, std::string_view name, uint32_t alias_handle, std::string_view type, Hierarchy::SupplementalVarType svt, Hierarchy::SupplementalDataType sdt ) { return createVar2( vartype, vardir, bitwidth, make_string_view_pair(name.data(), name.size()), alias_handle, make_string_view_pair(type.data(), type.size()), svt, sdt ); } #endif private: // File/memory buffers // 1. For hierarchy and geometry, we do not keep the data structure, instead we just // serialize them into buffers, and compress+write them at the end of file. // 2. For header, we keep the data structure in memory since it is quite small // 3. For wave data, we keep a complicated data structure in memory, // and flush them to file when necessary std::ofstream main_fst_file_; std::vector hierarchy_buffer_; std::vector geometry_buffer_; Header header_{}; detail::BlackoutData blackout_data_; detail::ValueChangeData value_change_data_; bool hierarchy_finalized_ = false; WriterPackType pack_type_ = WriterPackType::LZ4; uint64_t value_change_data_usage_ = 0; // Note: this value is just an estimation uint64_t value_change_data_flush_threshold_ = 128 << 20; // 128MB uint32_t enum_count_ = 0; bool flush_pending_ = false; // internal helpers static void writeHeader_(const Header &header, std::ostream &os); void appendGeometry_(std::ostream &os); void appendHierarchy_(std::ostream &os); void appendBlackout_(std::ostream &os); // This function is used to flush value change data to file, and keep only the latest value in // memory Just want to separate the const part from the non-const part for code clarity static void flushValueChangeDataConstPart_( const detail::ValueChangeData &vcd, std::ostream &os, WriterPackType pack_type ); inline void flushValueChangeData_(detail::ValueChangeData &vcd, std::ostream &os) { if (vcd.timestamps.empty()) { return; } flushValueChangeDataConstPart_(vcd, os, pack_type_); vcd.keepOnlyTheLatestValue(); ++header_.num_value_change_data_blocks; value_change_data_usage_ = 0; flush_pending_ = false; } void finalizeHierarchy_() { if (hierarchy_finalized_) return; hierarchy_finalized_ = true; // Original FST code comments: as a default, use 128MB and increment when // every 1M signals are defined. value_change_data_flush_threshold_ = (((header_.num_handles - 1) >> 20) + 1) << 27; } template void emitValueChangeHelper_(Handle handle, T &&...val); }; } // namespace fst