mirror of
https://github.com/verilator/verilator.git
synced 2026-09-08 02:28:43 +02:00
@@ -0,0 +1,22 @@
|
||||
---
|
||||
Language: Cpp
|
||||
BasedOnStyle: Google
|
||||
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: BlockIndent
|
||||
AlignEscapedNewlines: Left
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BreakBeforeBraces: Attach
|
||||
ColumnLimit: 100
|
||||
ContinuationIndentWidth: 4
|
||||
DerivePointerAlignment: false
|
||||
IncludeBlocks: Preserve
|
||||
IndentCaseLabels: false
|
||||
IndentPPDirectives: AfterHash
|
||||
IndentWidth: 4
|
||||
PointerAlignment: Right
|
||||
TabWidth: 4
|
||||
UseTab: ForContinuationAndIndentation
|
||||
@@ -0,0 +1,260 @@
|
||||
// SPDX-FileCopyrightText: 2025-2026 Yu-Sheng Lin <[email protected]>
|
||||
// SPDX-FileCopyrightText: 2025-2026 Yoda Lee <[email protected]>
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Project: libfstwriter
|
||||
// Website: https://github.com/gtkwave/libfstwriter
|
||||
#pragma once
|
||||
// direct include
|
||||
// C system headers
|
||||
// C++ standard library headers
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
// Other libraries' .h files.
|
||||
// Your project's .h files.
|
||||
#if defined(MSC_VER_) || defined(FORCE_MSC_VER_)
|
||||
# define USE_GCC_INTRINSIC 0
|
||||
// Note: we do not support MSVC intrinsic for now
|
||||
# define USE_MSVC_INTRINSIC 0
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
# define USE_GCC_INTRINSIC 1
|
||||
# define USE_MSVC_INTRINSIC 0
|
||||
#else
|
||||
# define USE_GCC_INTRINSIC 0
|
||||
# define USE_MSVC_INTRINSIC 0
|
||||
#endif
|
||||
|
||||
// Remove these when we upgrade to C++20
|
||||
#pragma GCC diagnostic ignored "-Wpragmas"
|
||||
#pragma GCC diagnostic ignored "-Wc++17-attribute-extensions"
|
||||
#pragma GCC diagnostic ignored "-Wc++20-attribute-extensions"
|
||||
|
||||
namespace fst {
|
||||
|
||||
typedef uint32_t Handle;
|
||||
typedef uint32_t EnumHandle;
|
||||
struct string_view_pair {
|
||||
const char *m_data = nullptr;
|
||||
size_t m_size = 0;
|
||||
|
||||
// implicit conversion from const char*, std::string, std::string_view
|
||||
string_view_pair(const char *data)
|
||||
: m_data{data}, m_size{data == nullptr ? 0 : std::strlen(data)} {}
|
||||
string_view_pair(const char *data, size_t size) : m_data{data}, m_size{size} {}
|
||||
string_view_pair(const std::string &s) : m_data{s.c_str()}, m_size{s.size()} {}
|
||||
#if __cplusplus >= 201703L
|
||||
string_view_pair(std::string_view s) : m_data{s.data()}, m_size{s.size()} {}
|
||||
#endif
|
||||
};
|
||||
|
||||
[[maybe_unused]]
|
||||
static inline string_view_pair make_string_view_pair(const char *data) {
|
||||
if (!data) {
|
||||
return {nullptr, 0};
|
||||
}
|
||||
return {data, std::strlen(data)};
|
||||
}
|
||||
|
||||
[[maybe_unused]]
|
||||
static inline string_view_pair make_string_view_pair(const char *data, std::size_t size) {
|
||||
return {data, size};
|
||||
}
|
||||
|
||||
enum class WriterPackType : uint8_t {
|
||||
ZLIB = 0, // not supported
|
||||
FASTLZ = 1, // not supported
|
||||
LZ4 = 2,
|
||||
// usually for testing, you should use eLz4
|
||||
// This will turn off compression for geometry/hierarchy/wave data
|
||||
NO_COMPRESSION = 3,
|
||||
};
|
||||
|
||||
enum class FileType : uint8_t {
|
||||
VERILOG = 0,
|
||||
VHDL,
|
||||
VERILOG_VHDL,
|
||||
};
|
||||
|
||||
enum class EncodingType : uint8_t {
|
||||
BINARY = 0, // 1 bit per bit to represent 0,1
|
||||
VERILOG = 1, // 2 bits per bit to represent X,Z
|
||||
VHDL = 2, // 4 bits per bit to represent H,U,W,L,-,?
|
||||
};
|
||||
|
||||
[[maybe_unused]]
|
||||
static inline constexpr unsigned bitPerEncodedBit(EncodingType type) {
|
||||
return 1 << static_cast<uint8_t>(type);
|
||||
}
|
||||
|
||||
[[maybe_unused]]
|
||||
static const char* kEncodedBitToCharTable = (
|
||||
"01" // Binary
|
||||
"xzhu" // Verilog
|
||||
"wl-? " // Vhdl (padded with ' ')
|
||||
);
|
||||
|
||||
struct Hierarchy {
|
||||
enum class ScopeType : uint8_t {
|
||||
VCD_MODULE = 0,
|
||||
VCD_TASK = 1,
|
||||
VCD_FUNCTION = 2,
|
||||
VCD_BEGIN = 3,
|
||||
VCD_FORK = 4,
|
||||
VCD_GENERATE = 5,
|
||||
VCD_STRUCT = 6,
|
||||
VCD_UNION = 7,
|
||||
VCD_CLASS = 8,
|
||||
VCD_INTERFACE = 9,
|
||||
VCD_PACKAGE = 10,
|
||||
VCD_PROGRAM = 11,
|
||||
VHDL_ARCHITECTURE = 12,
|
||||
VHDL_PROCEDURE = 13,
|
||||
VHDL_FUNCTION = 14,
|
||||
VHDL_RECORD = 15,
|
||||
VHDL_PROCESS = 16,
|
||||
VHDL_BLOCK = 17,
|
||||
VHDL_FORGENERATE = 18,
|
||||
VHDL_IFGENERATE = 19,
|
||||
VHDL_GENERATE = 20,
|
||||
VHDL_PACKAGE = 21,
|
||||
SV_ARRAY = 22,
|
||||
};
|
||||
|
||||
enum class ScopeControlType : uint8_t {
|
||||
GEN_ATTR_BEGIN = 252,
|
||||
GEN_ATTR_END = 253,
|
||||
VCD_SCOPE = 254,
|
||||
VCD_UPSCOPE = 255,
|
||||
};
|
||||
|
||||
enum class VarType : uint8_t {
|
||||
VCD_EVENT = 0,
|
||||
VCD_INTEGER = 1,
|
||||
VCD_PARAMETER = 2,
|
||||
VCD_REAL = 3,
|
||||
VCD_REAL_PARAMETER = 4,
|
||||
VCD_REG = 5,
|
||||
VCD_SUPPLY0 = 6,
|
||||
VCD_SUPPLY1 = 7,
|
||||
VCD_TIME = 8,
|
||||
VCD_TRI = 9,
|
||||
VCD_TRIAND = 10,
|
||||
VCD_TRIOR = 11,
|
||||
VCD_TRIREG = 12,
|
||||
VCD_TRI0 = 13,
|
||||
VCD_TRI1 = 14,
|
||||
VCD_WAND = 15,
|
||||
VCD_WIRE = 16,
|
||||
VCD_WOR = 17,
|
||||
VCD_PORT = 18,
|
||||
VCD_SPARRAY = 19,
|
||||
VCD_REALTIME = 20,
|
||||
GEN_STRING = 21,
|
||||
SV_BIT = 22,
|
||||
SV_LOGIC = 23,
|
||||
SV_INT = 24,
|
||||
SV_SHORTINT = 25,
|
||||
SV_LONGINT = 26,
|
||||
SV_BYTE = 27,
|
||||
SV_ENUM = 28,
|
||||
SV_SHORTREAL = 29,
|
||||
};
|
||||
|
||||
enum class VarDirection : uint8_t {
|
||||
MIN = 0,
|
||||
|
||||
IMPLICIT = 0,
|
||||
INPUT = 1,
|
||||
OUTPUT = 2,
|
||||
INOUT = 3,
|
||||
BUFFER = 4,
|
||||
LINKAGE = 5,
|
||||
|
||||
MAX = 5,
|
||||
};
|
||||
|
||||
enum class AttrType : uint8_t {
|
||||
MIN = 0,
|
||||
MISC = 0,
|
||||
ARRAY = 1,
|
||||
ENUM = 2,
|
||||
PACK = 3,
|
||||
MAX = 3,
|
||||
};
|
||||
|
||||
enum class AttrSubType : uint8_t {
|
||||
// For AttrType::eMisc
|
||||
MISC_MIN = 0,
|
||||
MISC_COMMENT = 0,
|
||||
MISC_ENVVAR = 1,
|
||||
MISC_SUPVAR = 2,
|
||||
MISC_PATHNAME = 3,
|
||||
MISC_SOURCESTEM = 4,
|
||||
MISC_SOURCEISTEM = 5,
|
||||
MISC_VALUELIST = 6,
|
||||
MISC_ENUMTABLE = 7,
|
||||
MISC_UNKNOWN = 8,
|
||||
MISC_MAX = 8,
|
||||
|
||||
// For AttrType::eArray
|
||||
ARRAY_MIN = 0,
|
||||
ARRAY_NONE = 0,
|
||||
ARRAY_UNPACKED = 1,
|
||||
ARRAY_PACKED = 2,
|
||||
ARRAY_SPARSE = 3,
|
||||
ARRAY_MAX = 3,
|
||||
|
||||
// For AttrType::eEnum
|
||||
ENUM_MIN = 0,
|
||||
ENUM_SV_INTEGER = 0,
|
||||
ENUM_SV_BIT = 1,
|
||||
ENUM_SV_LOGIC = 2,
|
||||
ENUM_SV_INT = 3,
|
||||
ENUM_SV_SHORTINT = 4,
|
||||
ENUM_SV_LONGINT = 5,
|
||||
ENUM_SV_BYTE = 6,
|
||||
ENUM_SV_UNSIGNED_INTEGER = 7,
|
||||
ENUM_SV_UNSIGNED_BIT = 8,
|
||||
ENUM_SV_UNSIGNED_LOGIC = 9,
|
||||
ENUM_SV_UNSIGNED_INT = 10,
|
||||
ENUM_SV_UNSIGNED_SHORTINT = 11,
|
||||
ENUM_SV_UNSIGNED_LONGINT = 12,
|
||||
ENUM_SV_UNSIGNED_BYTE = 13,
|
||||
ENUM_REG = 14,
|
||||
ENUM_TIME = 15,
|
||||
ENUM_MAX = 15,
|
||||
|
||||
// For AttrType::ePack
|
||||
PACK_MIN = 0,
|
||||
PACK_NONE = 0,
|
||||
PACK_UNPACKED = 1,
|
||||
PACK_PACKED = 2,
|
||||
PACK_SPARSE = 3,
|
||||
PACK_MAX = 3,
|
||||
};
|
||||
|
||||
enum class SupplementalVarType : uint8_t {};
|
||||
|
||||
enum class SupplementalDataType : uint8_t {};
|
||||
};
|
||||
|
||||
struct Header {
|
||||
uint64_t m_start_time{uint64_t(-1)};
|
||||
uint64_t m_end_time{0};
|
||||
int64_t m_timezero{0};
|
||||
// Match the original fstapi.c. Just for information, not used in FST.
|
||||
uint64_t m_writer_memory_use{1ull << 27};
|
||||
uint64_t m_num_scopes{0};
|
||||
uint64_t m_num_vars{0}; // #CreateVar calls, including aliases
|
||||
uint64_t m_num_handles{0}; // #unique handles, excluding aliases, shall be <= m_num_vars
|
||||
uint64_t m_num_value_change_data_blocks{0};
|
||||
char m_writer[128]{};
|
||||
char m_date[26]{};
|
||||
FileType m_filetype{FileType::VERILOG};
|
||||
int8_t m_timescale{-9};
|
||||
};
|
||||
|
||||
static constexpr uint64_t kInvalidTime = uint64_t(-1);
|
||||
|
||||
} // namespace fst
|
||||
@@ -0,0 +1,113 @@
|
||||
// SPDX-FileCopyrightText: 2025 Yu-Sheng Lin <[email protected]>
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Project: libfstwriter
|
||||
// Website: https://github.com/gtkwave/libfstwriter
|
||||
#pragma once
|
||||
// direct include
|
||||
// C system headers
|
||||
// C++ standard library headers
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
// Other libraries' .h files.
|
||||
// Your project's .h files.
|
||||
|
||||
#define FST_CHECK(a) \
|
||||
if (!(a)) [[unlikely]] { \
|
||||
std::ostringstream oss; \
|
||||
oss << "FST_CHECK failed: " #a; \
|
||||
const auto e = oss.str(); \
|
||||
std::cerr << e << std::endl; \
|
||||
std::abort(); \
|
||||
}
|
||||
|
||||
#define FST_CHECK_EQ(a, b) \
|
||||
if ((a) != (b)) [[unlikely]] { \
|
||||
std::ostringstream oss; \
|
||||
oss << "FST_CHECK_EQ failed: " #a " != " #b; \
|
||||
oss << " (" << (a) << " vs. " << (b) << ")"; \
|
||||
const auto e = oss.str(); \
|
||||
std::cerr << e << std::endl; \
|
||||
std::abort(); \
|
||||
}
|
||||
|
||||
#define FST_CHECK_NE(a, b) \
|
||||
if ((a) == (b)) [[unlikely]] { \
|
||||
std::ostringstream oss; \
|
||||
oss << "FST_CHECK_NE failed: " #a " == " #b; \
|
||||
oss << " (" << (a) << " vs. " << (b) << ")"; \
|
||||
const auto e = oss.str(); \
|
||||
std::cerr << e << std::endl; \
|
||||
std::abort(); \
|
||||
}
|
||||
|
||||
#define FST_CHECK_GT(a, b) \
|
||||
if ((a) <= (b)) [[unlikely]] { \
|
||||
std::ostringstream oss; \
|
||||
oss << "FST_CHECK_GT failed: " #a " <= " #b; \
|
||||
oss << " (" << (a) << " vs. " << (b) << ")"; \
|
||||
const auto e = oss.str(); \
|
||||
std::cerr << e << std::endl; \
|
||||
std::abort(); \
|
||||
}
|
||||
|
||||
#define FST_CHECK_GE(a, b) \
|
||||
if ((a) < (b)) [[unlikely]] { \
|
||||
std::ostringstream oss; \
|
||||
oss << "FST_CHECK_GE failed: " #a " < " #b; \
|
||||
oss << " (" << (a) << " vs. " << (b) << ")"; \
|
||||
const auto e = oss.str(); \
|
||||
std::cerr << e << std::endl; \
|
||||
std::abort(); \
|
||||
}
|
||||
|
||||
#define FST_CHECK_LT(a, b) \
|
||||
if ((a) >= (b)) [[unlikely]] { \
|
||||
std::ostringstream oss; \
|
||||
oss << "FST_CHECK_LT failed: " #a " >= " #b; \
|
||||
oss << " (" << (a) << " vs. " << (b) << ")"; \
|
||||
const auto e = oss.str(); \
|
||||
std::cerr << e << std::endl; \
|
||||
std::abort(); \
|
||||
}
|
||||
|
||||
#define FST_CHECK_LE(a, b) \
|
||||
if ((a) > (b)) [[unlikely]] { \
|
||||
std::ostringstream oss; \
|
||||
oss << "FST_CHECK_LE failed: " #a " > " #b; \
|
||||
oss << " (" << (a) << " vs. " << (b) << ")"; \
|
||||
const auto e = oss.str(); \
|
||||
std::cerr << e << std::endl; \
|
||||
std::abort(); \
|
||||
}
|
||||
|
||||
// We turn on all DCHECKs to CHECKs temporarily for better safety.
|
||||
#if 1
|
||||
# define FST_DCHECK(a) FST_CHECK(a)
|
||||
# define FST_DCHECK_EQ(a, b) FST_CHECK_EQ(a, b)
|
||||
# define FST_DCHECK_NE(a, b) FST_CHECK_NE(a, b)
|
||||
# define FST_DCHECK_GT(a, b) FST_CHECK_GT(a, b)
|
||||
# define FST_DCHECK_GE(a, b) FST_CHECK_GE(a, b)
|
||||
# define FST_DCHECK_LT(a, b) FST_CHECK_LT(a, b)
|
||||
# define FST_DCHECK_LE(a, b) FST_CHECK_LE(a, b)
|
||||
#else
|
||||
# define FST_DCHECK(a)
|
||||
# define FST_DCHECK_EQ(a, b)
|
||||
# define FST_DCHECK_NE(a, b)
|
||||
# define FST_DCHECK_GT(a, b)
|
||||
# define FST_DCHECK_GE(a, b)
|
||||
# define FST_DCHECK_LT(a, b)
|
||||
# define FST_DCHECK_LE(a, b)
|
||||
#endif
|
||||
|
||||
// Compatibility layer for unreachable code hint
|
||||
#if defined(__cplusplus) && __cplusplus >= 202302L
|
||||
# include <utility>
|
||||
# define FST_UNREACHABLE std::unreachable()
|
||||
#elif USE_GCC_INTRINSIC
|
||||
# define FST_UNREACHABLE __builtin_unreachable()
|
||||
// TODO: implement MSVC version
|
||||
// #elif USE_MSVC_INTRINSIC
|
||||
#else
|
||||
# define FST_UNREACHABLE std::abort()
|
||||
#endif
|
||||
@@ -0,0 +1,83 @@
|
||||
// SPDX-FileCopyrightText: 2025-2026 Yu-Sheng Lin <[email protected]>
|
||||
// SPDX-FileCopyrightText: 2025-2026 Yoda Lee <[email protected]>
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Project: libfstwriter
|
||||
// Website: https://github.com/gtkwave/libfstwriter
|
||||
#pragma once
|
||||
// direct include
|
||||
// C system headers
|
||||
// C++ standard library headers
|
||||
#include <cstdint>
|
||||
// Other libraries' .h files.
|
||||
// Your project's .h files.
|
||||
|
||||
namespace fst {
|
||||
|
||||
// Original block types from fstapi.h
|
||||
// FST_BL_HDR = 0,
|
||||
// FST_BL_VCDATA = 1,
|
||||
// FST_BL_BLACKOUT = 2,
|
||||
// FST_BL_GEOM = 3,
|
||||
// FST_BL_HIER = 4,
|
||||
// FST_BL_VCDATA_DYN_ALIAS = 5,
|
||||
// FST_BL_HIER_LZ4 = 6,
|
||||
// FST_BL_HIER_LZ4DUO = 7,
|
||||
// FST_BL_VCDATA_DYN_ALIAS2 = 8,
|
||||
// FST_BL_ZWRAPPER = 254,
|
||||
// FST_BL_SKIP = 255
|
||||
enum class BlockType : uint8_t {
|
||||
HEADER = 0,
|
||||
WAVE_DATA_VERSION1 = 1, // not implemented
|
||||
BLACKOUT = 2,
|
||||
GEOMETRY = 3,
|
||||
HIERARCHY_GZ_COMPRESSED = 4, // not implemented
|
||||
WAVE_DATA_VERSION2 = 5, // not implemented
|
||||
HIERARCHY_LZ4_COMPRESSED = 6,
|
||||
HIERARCHY_LZ4_COMPRESSED_TWICE = 7, // not implemented
|
||||
WAVE_DATA_VERSION3 = 8,
|
||||
|
||||
ZWRAPPER = 254, // not implemented
|
||||
SKIP = 255 // not implemented
|
||||
};
|
||||
|
||||
constexpr unsigned kSharedBlockHeaderSize = 1 /* BlockType */ + 8 /* size (u64) */;
|
||||
|
||||
struct HeaderInfo {
|
||||
struct Size {
|
||||
static constexpr unsigned start_time = 0;
|
||||
static constexpr unsigned end_time = 8;
|
||||
static constexpr unsigned real_endianness = 8;
|
||||
static constexpr unsigned writer_memory_use = 8;
|
||||
static constexpr unsigned num_scopes = 8;
|
||||
static constexpr unsigned num_vars = 8;
|
||||
static constexpr unsigned num_handles = 8;
|
||||
static constexpr unsigned num_wave_data_blocks = 8;
|
||||
static constexpr unsigned timescale = 1;
|
||||
static constexpr unsigned writer = 128;
|
||||
static constexpr unsigned date = 26;
|
||||
static constexpr unsigned reserved = 93;
|
||||
static constexpr unsigned filetype = 1;
|
||||
static constexpr unsigned timezero = 8;
|
||||
};
|
||||
struct Offset {
|
||||
static constexpr unsigned start_time = 0;
|
||||
static constexpr unsigned end_time = start_time + Size::end_time;
|
||||
static constexpr unsigned real_endianness = end_time + Size::real_endianness;
|
||||
static constexpr unsigned writer_memory_use = real_endianness + Size::writer_memory_use;
|
||||
static constexpr unsigned num_scopes = writer_memory_use + Size::num_scopes;
|
||||
static constexpr unsigned num_vars = num_scopes + Size::num_vars;
|
||||
static constexpr unsigned num_handles = num_vars + Size::num_vars;
|
||||
static constexpr unsigned num_wave_data_blocks = num_handles + Size::num_handles;
|
||||
static constexpr unsigned timescale = num_wave_data_blocks + Size::num_wave_data_blocks;
|
||||
static constexpr unsigned writer = timescale + Size::timescale;
|
||||
static constexpr unsigned date = writer + Size::writer;
|
||||
static constexpr unsigned reserved = date + Size::date;
|
||||
static constexpr unsigned filetype = reserved + Size::reserved;
|
||||
static constexpr unsigned timezero = filetype + Size::filetype;
|
||||
};
|
||||
static constexpr unsigned total_size = Offset::timezero + Size::timezero;
|
||||
static constexpr double kEndianessMagicIdentifier = 2.7182818284590452354;
|
||||
static_assert(total_size == 321, "Total size of HeaderInfo must be 321 bytes");
|
||||
};
|
||||
|
||||
} // namespace fst
|
||||
@@ -0,0 +1,388 @@
|
||||
// SPDX-FileCopyrightText: 2025-2026 Yu-Sheng Lin <[email protected]>
|
||||
// SPDX-FileCopyrightText: 2025-2026 Yoda Lee <[email protected]>
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Project: libfstwriter
|
||||
// Website: https://github.com/gtkwave/libfstwriter
|
||||
#pragma once
|
||||
// direct include
|
||||
// C system headers
|
||||
// C++ standard library headers
|
||||
#if defined(__cplusplus) && __cplusplus >= 202302L
|
||||
# include <bit>
|
||||
#endif
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
// Other libraries' .h files.
|
||||
// Your project's .h files.
|
||||
#include "fstcpp/fstcpp.h"
|
||||
#include "fstcpp/fstcpp_file.h"
|
||||
|
||||
namespace fst {
|
||||
|
||||
namespace platform {
|
||||
|
||||
// For C++14
|
||||
// Can remove once C++23 is required
|
||||
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
// clang-format off
|
||||
template <typename U> U to_big_endian(U u) { return u; }
|
||||
#else
|
||||
#if defined(__cplusplus) && __cplusplus >= 202302L
|
||||
template <typename U, size_t S>
|
||||
U to_big_endian(U u, std::integral_constant<size_t, S>) {
|
||||
return std::byteswap(u);
|
||||
}
|
||||
#elif USE_GCC_INTRINSIC
|
||||
template<typename U> U to_big_endian(U u, std::integral_constant<size_t, 1>) { return u; }
|
||||
template<typename U> U to_big_endian(U u, std::integral_constant<size_t, 2>) { return __builtin_bswap16(u); }
|
||||
template<typename U> U to_big_endian(U u, std::integral_constant<size_t, 4>) { return __builtin_bswap32(u); }
|
||||
template<typename U> U to_big_endian(U u, std::integral_constant<size_t, 8>) { return __builtin_bswap64(u); }
|
||||
// TODO: implement MSVC version
|
||||
// #elif USE_MSVC_INTRINSIC
|
||||
#else
|
||||
template <typename U, size_t S>
|
||||
U to_big_endian(U u, std::integral_constant<size_t, S>) {
|
||||
U ret{0};
|
||||
for (size_t i = 0; i < S; ++i) {
|
||||
ret = (ret << 8) | (u & 0xff);
|
||||
u >>= 8;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
// clang-format on
|
||||
template <typename U>
|
||||
U to_big_endian(U u) {
|
||||
return platform::to_big_endian(u, std::integral_constant<size_t, sizeof(U)>());
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace platform
|
||||
|
||||
struct StreamWriteHelper {
|
||||
std::ostream *m_os{nullptr};
|
||||
|
||||
StreamWriteHelper(std::ostream &os_) : m_os{&os_} {}
|
||||
StreamWriteHelper(std::ostream *os_) : m_os{os_} {}
|
||||
|
||||
// Write the entire uint, big-endian
|
||||
// We do not provide little-endian version since FST only uses big-endian
|
||||
template <typename U>
|
||||
StreamWriteHelper &writeUInt(U u) {
|
||||
u = platform::to_big_endian(u);
|
||||
m_os->write(reinterpret_cast<const char *>(&u), sizeof(u));
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Write the uint, big-endian, left-aligned but only (bitwidth+7)/8 bytes
|
||||
// This is a very special case for value changes
|
||||
// For example, if the value is 10-bits (e.g. logic [9:0] in Verilog),
|
||||
// then the first byte will be [9-:8], then {[1:0], 6'b0}.
|
||||
template <typename U>
|
||||
StreamWriteHelper &writeUIntPartialForValueChange(U u, size_t bitwidth) {
|
||||
// Shift left to align the MSB to the MSB of the uint
|
||||
u <<= sizeof(u) * 8 - bitwidth;
|
||||
// Write the first (bitwidth+7)/8 bytes
|
||||
u = platform::to_big_endian(u);
|
||||
m_os->write(reinterpret_cast<const char *>(&u), (bitwidth + 7) / 8);
|
||||
return *this;
|
||||
}
|
||||
|
||||
StreamWriteHelper &writeLEB128(uint64_t v) {
|
||||
// Just reuse the logic from fstapi.c, is there a better way?
|
||||
uint64_t nxt{0};
|
||||
unsigned char buf[10]{}; /* ceil(64/7) = 10 */
|
||||
unsigned char *pnt{buf};
|
||||
int len{0};
|
||||
while ((nxt = v >> 7)) {
|
||||
*(pnt++) = ((unsigned char)v) | 0x80;
|
||||
v = nxt;
|
||||
}
|
||||
*(pnt++) = (unsigned char)v;
|
||||
len = static_cast<int>(pnt - buf);
|
||||
m_os->write(reinterpret_cast<const char *>(buf), len);
|
||||
return *this;
|
||||
}
|
||||
|
||||
StreamWriteHelper &writeLEB128Signed(int64_t v) {
|
||||
// Just reuse the logic from fstapi.c, is there a better way?
|
||||
unsigned char buf[15]{}; /* ceil(64/7) = 10 + sign byte padded way up */
|
||||
unsigned char byt{0};
|
||||
unsigned char *pnt{buf};
|
||||
int more{1};
|
||||
int len{0};
|
||||
do {
|
||||
byt = static_cast<unsigned char>(v | 0x80);
|
||||
v >>= 7;
|
||||
|
||||
if (((!v) && (!(byt & 0x40))) || ((v == -1) && (byt & 0x40))) {
|
||||
more = 0;
|
||||
byt &= 0x7f;
|
||||
}
|
||||
|
||||
*(pnt++) = byt;
|
||||
} while (more);
|
||||
len = static_cast<int>(pnt - buf);
|
||||
m_os->write(reinterpret_cast<const char *>(buf), len);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
StreamWriteHelper &writeFloat(F f) {
|
||||
// Always write in native endianness
|
||||
m_os->write(reinterpret_cast<const char *>(&f), sizeof(f));
|
||||
return *this;
|
||||
}
|
||||
|
||||
StreamWriteHelper &writeBlockHeader(fst::BlockType block_type, uint64_t block_length) {
|
||||
return (
|
||||
this //
|
||||
->writeUInt(static_cast<uint8_t>(block_type))
|
||||
.writeUInt(
|
||||
block_length + 8
|
||||
) // The 8 is required by FST, which is the size of this uint64_t
|
||||
);
|
||||
}
|
||||
|
||||
// Write the string, non-null-terminated
|
||||
StreamWriteHelper &writeString(const fst::string_view_pair str) {
|
||||
m_os->write(str.m_data, str.m_size);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Write the string, null-terminated
|
||||
StreamWriteHelper &writeString0(const fst::string_view_pair str) {
|
||||
m_os->write(str.m_data, str.m_size).put('\0');
|
||||
return *this;
|
||||
}
|
||||
StreamWriteHelper &writeString(const std::string &str) {
|
||||
return writeString0(fst::make_string_view_pair(str.c_str(), str.size()));
|
||||
}
|
||||
StreamWriteHelper &writeString(const char *str) {
|
||||
return writeString0(fst::make_string_view_pair(str));
|
||||
}
|
||||
|
||||
StreamWriteHelper &write(const char *ptr, size_t size) {
|
||||
m_os->write(ptr, size);
|
||||
return *this;
|
||||
}
|
||||
|
||||
StreamWriteHelper &write(const uint8_t *ptr, size_t size) {
|
||||
m_os->write(reinterpret_cast<const char *>(ptr), size);
|
||||
return *this;
|
||||
}
|
||||
|
||||
StreamWriteHelper &seek(std::streamoff pos, std::ios_base::seekdir dir) {
|
||||
m_os->seekp(pos, dir);
|
||||
return *this;
|
||||
}
|
||||
|
||||
StreamWriteHelper &fill(char fill_char, size_t size) {
|
||||
if (size > 32) {
|
||||
// optimize large fills
|
||||
constexpr unsigned s_kChunkSize = 16;
|
||||
char buf[s_kChunkSize]{};
|
||||
std::memset(buf, fill_char, s_kChunkSize);
|
||||
for (size_t i{0}; i < size / s_kChunkSize; ++i) {
|
||||
m_os->write(buf, s_kChunkSize);
|
||||
}
|
||||
size %= s_kChunkSize;
|
||||
}
|
||||
for (size_t i{0}; i < size; ++i) {
|
||||
m_os->put(fill_char);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Handy functions for writing variable length data, you can
|
||||
// cascade multiple write() calls after RecordOffset(), then
|
||||
// call DiffOffset() to get the total number of bytes written.
|
||||
|
||||
// (1)
|
||||
// std::streamoff diff;
|
||||
// h
|
||||
// .beginOffset(diff)
|
||||
// .write(...)
|
||||
// ... do other stuff ...
|
||||
// .endOffset(&diff); <-- diff will be set to the number of bytes written
|
||||
// (2)
|
||||
// std::streamoff pos, diff;
|
||||
// h
|
||||
// .beginOffset(pos)
|
||||
// .write(...)
|
||||
// ... do other stuff ...
|
||||
// .endOffset(&diff, pos); <-- diff will be set to the number of bytes written
|
||||
|
||||
// The API uses pointer on purpose to prevent you pass (pos, diff) as arguments
|
||||
// to endOffset(), which is a common mistake.
|
||||
|
||||
StreamWriteHelper &beginOffset(std::streamoff &pos) {
|
||||
pos = m_os->tellp();
|
||||
return *this;
|
||||
}
|
||||
|
||||
StreamWriteHelper &endOffset(std::streamoff *diff) {
|
||||
// diff shall store previous position before calling this function
|
||||
*diff = m_os->tellp() - *diff;
|
||||
return *this;
|
||||
}
|
||||
|
||||
StreamWriteHelper &endOffset(std::streamoff *diff, std::streamoff pos) {
|
||||
*diff = m_os->tellp() - pos;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
struct StreamVectorWriteHelper {
|
||||
std::vector<uint8_t> &m_vec;
|
||||
|
||||
StreamVectorWriteHelper(std::vector<uint8_t> &vec_) : m_vec{vec_} {}
|
||||
|
||||
template <typename T>
|
||||
StreamVectorWriteHelper &write(T u) {
|
||||
const size_t s = sizeof(u);
|
||||
m_vec.resize(m_vec.size() + s);
|
||||
std::memcpy(m_vec.data() + m_vec.size() - s, &u, s);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
StreamVectorWriteHelper &fill(T u, size_t count) {
|
||||
const size_t s = sizeof(u) * count;
|
||||
m_vec.resize(m_vec.size() + s);
|
||||
for (size_t i{0}; i < count; ++i) {
|
||||
std::memcpy(m_vec.data() + m_vec.size() - s + i * sizeof(u), &u, sizeof(u));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
StreamVectorWriteHelper &write(T *u, size_t size) {
|
||||
const size_t s = sizeof(u) * size;
|
||||
m_vec.resize(m_vec.size() + s);
|
||||
std::memcpy(m_vec.data() + m_vec.size() - s, u, s);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
StreamVectorWriteHelper &writeU8Enum(E e) {
|
||||
m_vec.push_back(static_cast<uint8_t>(e));
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Write the entire uint, big-endian
|
||||
// We do not provide little-endian version since FST only uses big-endian
|
||||
template <typename U>
|
||||
StreamVectorWriteHelper &writeUIntBE(U u) {
|
||||
u = platform::to_big_endian(u);
|
||||
const size_t s = sizeof(u);
|
||||
m_vec.resize(m_vec.size() + s);
|
||||
std::memcpy(m_vec.data() + m_vec.size() - s, &u, s);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Write the uint, big-endian, left-aligned but only (bitwidth+7)/8 bytes
|
||||
// This is a very special case for value changes
|
||||
// For example, if the value is 10-bits (e.g. logic [9:0] in Verilog),
|
||||
// then the first byte will be [9-:8], then {[1:0], 6'b0}.
|
||||
template <typename U>
|
||||
StreamVectorWriteHelper &writeUIntPartialForValueChange(U u, size_t bitwidth) {
|
||||
// Shift left to align the MSB to the MSB of the uint
|
||||
u <<= sizeof(u) * 8 - bitwidth;
|
||||
// Write the first (bitwidth+7)/8 bytes
|
||||
u = platform::to_big_endian(u);
|
||||
const size_t s = (bitwidth + 7) / 8;
|
||||
m_vec.resize(m_vec.size() + s);
|
||||
std::memcpy(m_vec.data() + m_vec.size() - s, &u, s);
|
||||
return *this;
|
||||
}
|
||||
|
||||
StreamVectorWriteHelper &writeLEB128(uint64_t v) {
|
||||
// Just reuse the logic from fstapi.c, is there a better way?
|
||||
uint64_t nxt{0};
|
||||
unsigned char buf[10]{}; /* ceil(64/7) = 10 */
|
||||
unsigned char *pnt{buf};
|
||||
int len{0};
|
||||
while ((nxt = v >> 7)) {
|
||||
*(pnt++) = ((unsigned char)v) | 0x80;
|
||||
v = nxt;
|
||||
}
|
||||
*(pnt++) = (unsigned char)v;
|
||||
len = static_cast<int>(pnt - buf);
|
||||
|
||||
const size_t cur = m_vec.size();
|
||||
m_vec.resize(cur + len);
|
||||
std::memcpy(m_vec.data() + cur, buf, len);
|
||||
return *this;
|
||||
}
|
||||
|
||||
StreamVectorWriteHelper &writeLEB128Signed(int64_t v) {
|
||||
// Just reuse the logic from fstapi.c, is there a better way?
|
||||
unsigned char buf[15]{}; /* ceil(64/7) = 10 + sign byte padded way up */
|
||||
unsigned char byt{0};
|
||||
unsigned char *pnt{buf};
|
||||
int more{1};
|
||||
int len{0};
|
||||
do {
|
||||
byt = static_cast<unsigned char>(v | 0x80);
|
||||
v >>= 7;
|
||||
|
||||
if (((!v) && (!(byt & 0x40))) || ((v == -1) && (byt & 0x40))) {
|
||||
more = 0;
|
||||
byt &= 0x7f;
|
||||
}
|
||||
|
||||
*(pnt++) = byt;
|
||||
} while (more);
|
||||
len = static_cast<int>(pnt - buf);
|
||||
|
||||
const size_t cur = m_vec.size();
|
||||
m_vec.resize(cur + len);
|
||||
std::memcpy(m_vec.data() + cur, buf, len);
|
||||
return *this;
|
||||
}
|
||||
|
||||
StreamVectorWriteHelper &writeBlockHeader(fst::BlockType block_type, uint64_t block_length) {
|
||||
return (
|
||||
this //
|
||||
->writeUIntBE(static_cast<uint8_t>(block_type))
|
||||
.writeUIntBE(
|
||||
block_length + 8
|
||||
) // The 8 is required by FST, which is the size of this uint64_t
|
||||
);
|
||||
}
|
||||
|
||||
// Write the string, non-null-terminated
|
||||
StreamVectorWriteHelper &writeString(const fst::string_view_pair str) {
|
||||
if (str.m_size != 0) {
|
||||
const size_t len = str.m_size;
|
||||
const size_t cur = m_vec.size();
|
||||
m_vec.resize(cur + len);
|
||||
std::memcpy(m_vec.data() + cur, str.m_data, len);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Write the string, null-terminated
|
||||
StreamVectorWriteHelper &writeString0(const fst::string_view_pair str) {
|
||||
if (str.m_size != 0) {
|
||||
const size_t len = str.m_size;
|
||||
const size_t cur = m_vec.size();
|
||||
m_vec.resize(cur + len + 1);
|
||||
std::memcpy(m_vec.data() + cur, str.m_data, len);
|
||||
m_vec[cur + len] = '\0';
|
||||
} else {
|
||||
m_vec.push_back('\0');
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
StreamVectorWriteHelper &writeString(const std::string &str) {
|
||||
return writeString0(fst::make_string_view_pair(str.c_str(), str.size()));
|
||||
}
|
||||
StreamVectorWriteHelper &writeString(const char *str) {
|
||||
return writeString0(fst::make_string_view_pair(str));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace fst
|
||||
@@ -0,0 +1,39 @@
|
||||
// SPDX-FileCopyrightText: 2026 Yu-Sheng Lin <[email protected]>
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Project: libfstwriter
|
||||
// Website: https://github.com/gtkwave/libfstwriter
|
||||
// direct include
|
||||
#include "fstcpp/fstcpp_variable_info.h"
|
||||
// C system headers
|
||||
// C++ standard library headers
|
||||
#include <algorithm>
|
||||
// Other libraries' .h files.
|
||||
// Your project's .h files.
|
||||
|
||||
namespace fst {
|
||||
|
||||
// I don't know why I need to define them here, but StackOverflow says it
|
||||
constexpr uint64_t VariableInfo::kCapacityBaseShift;
|
||||
constexpr uint64_t VariableInfo::kCapacityBase;
|
||||
|
||||
void VariableInfo::reallocate(uint64_t new_size) {
|
||||
// Allocate new memory
|
||||
const uint32_t new_capacity_log2{
|
||||
std::max(
|
||||
static_cast<uint32_t>(platform::clog2(new_size)),
|
||||
static_cast<uint32_t>(kCapacityBaseShift)
|
||||
) -
|
||||
static_cast<uint32_t>(kCapacityBaseShift)
|
||||
};
|
||||
uint8_t *new_data{new uint8_t[kCapacityBase << new_capacity_log2]};
|
||||
// Copy old data to new memory
|
||||
if (m_data != nullptr) {
|
||||
const uint64_t old_size{size()};
|
||||
std::copy_n(m_data, old_size, new_data);
|
||||
delete[] m_data;
|
||||
}
|
||||
m_data = new_data;
|
||||
capacity_log2(new_capacity_log2);
|
||||
}
|
||||
|
||||
} // namespace fst
|
||||
@@ -0,0 +1,830 @@
|
||||
// SPDX-FileCopyrightText: 2025-2026 Yu-Sheng Lin <[email protected]>
|
||||
// SPDX-FileCopyrightText: 2025-2026 Yoda Lee <[email protected]>
|
||||
// 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
|
||||
#if defined(__cplusplus) && __cplusplus >= 202002L
|
||||
# include <bit>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
// Other libraries' .h files.
|
||||
// Your project's .h files.
|
||||
#include "fstcpp/fstcpp_assertion.h"
|
||||
#include "fstcpp/fstcpp_stream_write_helper.h"
|
||||
|
||||
namespace fst {
|
||||
|
||||
namespace platform {
|
||||
|
||||
// Can be replaced with std::bit_width when C++20 is available
|
||||
inline uint64_t clog2(uint64_t x) {
|
||||
if (x <= 1) return 0;
|
||||
#if defined(__cplusplus) && __cplusplus >= 202002L
|
||||
return std::bit_width(x - 1);
|
||||
#elif USE_GCC_INTRINSIC
|
||||
return 64 - __builtin_clzll(x - 1);
|
||||
// TODO: implement MSVC version
|
||||
// #elif USE_MSVC_INTRINSIC
|
||||
#else
|
||||
uint64_t r = 0;
|
||||
x -= 1;
|
||||
auto CheckAndShift = [&](uint64_t shift) {
|
||||
if (x >> shift) {
|
||||
r += shift;
|
||||
x >>= shift;
|
||||
}
|
||||
};
|
||||
CheckAndShift(32);
|
||||
CheckAndShift(16);
|
||||
CheckAndShift(8);
|
||||
CheckAndShift(4);
|
||||
CheckAndShift(2);
|
||||
CheckAndShift(1);
|
||||
r += x;
|
||||
return r;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline constexpr uint32_t gen_mask_safe(unsigned width) {
|
||||
// works even when width == 32
|
||||
return ((uint32_t(1) << (width - 1)) << 1) - 1;
|
||||
}
|
||||
|
||||
inline uint32_t read_field(const uint32_t src, unsigned width, unsigned offset) {
|
||||
const uint32_t mask = gen_mask_safe(width);
|
||||
return (src >> offset) & mask;
|
||||
}
|
||||
|
||||
inline void write_field(uint32_t &dst, const uint32_t src, unsigned width, unsigned offset) {
|
||||
const uint32_t mask = gen_mask_safe(width) << offset;
|
||||
dst = (dst & ~mask) | ((src << offset) & mask);
|
||||
}
|
||||
|
||||
} // namespace platform
|
||||
|
||||
class VariableInfo final {
|
||||
public:
|
||||
static constexpr uint32_t kMaxSupportedBitwidth = 0x7fffff;
|
||||
|
||||
private:
|
||||
static constexpr uint64_t kCapacityBaseShift = 5;
|
||||
static constexpr uint64_t kCapacityBase = 1 << kCapacityBaseShift;
|
||||
|
||||
// To maximize cache efficiency, we compact the data members into 16 bytes.
|
||||
// We make use of bitfields to store multiple pieces of information in a single integer.
|
||||
// But standard does not guarantee the layout of bitfields (the `int x : N;` syntax),
|
||||
// so we use helper functions to access bitfields.
|
||||
|
||||
// begin of data members
|
||||
// 1. 8B pointer (assume 64-bit architecture), its size can be:
|
||||
// - 0 if m_data is nullptr
|
||||
// - `kCapacityBase * pow(2, m_capacity_log2)` if m_data is not nullptr
|
||||
// - If we want more bits, we can use the `kCapacityBaseShift` LSB for other purposes.
|
||||
uint8_t *m_data{nullptr};
|
||||
// 2. 4B size. The same as vector.size(), but we only need 32b.
|
||||
uint32_t m_size{0};
|
||||
// 3. 4B misc. Highly compacted information for max cache efficiency.
|
||||
// - 6b capacity_log2
|
||||
// - 2b last_encoding_type
|
||||
// - 23b bitwidth
|
||||
// - 1b is_real
|
||||
uint32_t m_misc{0};
|
||||
// end of data members
|
||||
|
||||
// Note: optimization possibility (not implemented)
|
||||
// - real is always 64-bit double, so we can use 24 bits to encode
|
||||
// is_real and bitwidth together, and bitwidth = (1<<24-1) is a special
|
||||
// value to indicate that the variable is a real.
|
||||
// - Currently bitwidth is whatever you pass to Writer::createVar.
|
||||
// - Not implemented since nobody needs 16M-bit over 8M-bit bitwidth IMO.
|
||||
static constexpr uint32_t kIsRealWidth = 1;
|
||||
static constexpr uint32_t kBitwidthWidth = 23;
|
||||
static constexpr uint32_t kLastEncodingTypeWidth = 2;
|
||||
static constexpr uint32_t kCapacityLog2Width = 6;
|
||||
|
||||
static constexpr uint32_t kIsRealOffset = 0;
|
||||
static constexpr uint32_t kBitwidthOffset = kIsRealOffset + kIsRealWidth;
|
||||
static constexpr uint32_t kLastEncodingTypeOffset = kBitwidthOffset + kBitwidthWidth;
|
||||
static constexpr uint32_t kCapacityLog2Offset =
|
||||
kLastEncodingTypeOffset + kLastEncodingTypeWidth;
|
||||
|
||||
void capacity_log2(uint32_t capacity_log2_) {
|
||||
platform::write_field(m_misc, capacity_log2_, kCapacityLog2Width, kCapacityLog2Offset);
|
||||
}
|
||||
uint32_t capacity() const {
|
||||
if (m_data == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return kCapacityBase << platform::read_field(
|
||||
m_misc, kCapacityLog2Width, kCapacityLog2Offset
|
||||
);
|
||||
}
|
||||
|
||||
bool need_reallocate(uint64_t new_size) const { return capacity() < new_size; }
|
||||
// This function is cold, so we don't inline it
|
||||
void reallocate(uint64_t new_size);
|
||||
|
||||
void size(uint64_t s) { m_size = static_cast<uint32_t>(s); }
|
||||
|
||||
public:
|
||||
uint64_t size() const { return m_size; }
|
||||
uint32_t bitwidth() const {
|
||||
return platform::read_field(m_misc, kBitwidthWidth, kBitwidthOffset);
|
||||
}
|
||||
bool is_real() const { return bool(platform::read_field(m_misc, kIsRealWidth, kIsRealOffset)); }
|
||||
void last_written_encode_type(EncodingType encoding_) {
|
||||
platform::write_field(
|
||||
m_misc,
|
||||
static_cast<uint32_t>(encoding_),
|
||||
kLastEncodingTypeWidth,
|
||||
kLastEncodingTypeOffset
|
||||
);
|
||||
}
|
||||
EncodingType last_written_encode_type() const {
|
||||
return static_cast<EncodingType>(
|
||||
platform::read_field(m_misc, kLastEncodingTypeWidth, kLastEncodingTypeOffset)
|
||||
);
|
||||
}
|
||||
uint64_t last_written_bytes() const;
|
||||
|
||||
template <typename Callable, typename... Args>
|
||||
auto dispatchHelper(Callable &&callable, Args &&...args) const;
|
||||
|
||||
VariableInfo(uint32_t bitwidth_, bool is_real_ = false);
|
||||
~VariableInfo() {
|
||||
if (data_ptr() != nullptr) {
|
||||
// don't delete data directly for better abstraction
|
||||
// we might use the LSB of data in the future as LSB is
|
||||
// always aligned to kCapacityBase
|
||||
delete[] data_ptr();
|
||||
}
|
||||
}
|
||||
VariableInfo(VariableInfo &&rhs) {
|
||||
m_data = rhs.m_data;
|
||||
rhs.m_data = nullptr;
|
||||
m_misc = rhs.m_misc;
|
||||
m_size = rhs.m_size;
|
||||
}
|
||||
|
||||
uint32_t emitValueChange(uint64_t current_time_index, const uint64_t val);
|
||||
uint32_t emitValueChange(
|
||||
uint64_t current_time_index, const uint32_t *val, EncodingType encoding
|
||||
);
|
||||
uint32_t emitValueChange(
|
||||
uint64_t current_time_index, const uint64_t *val, EncodingType encoding
|
||||
);
|
||||
|
||||
void keepOnlyTheLatestValue() {
|
||||
const uint64_t last_written_bytes_ = last_written_bytes();
|
||||
uint8_t *data_ptr_ = data_ptr();
|
||||
std::copy_n(data_ptr_ + size() - last_written_bytes_, last_written_bytes_, data_ptr_);
|
||||
size(last_written_bytes_);
|
||||
}
|
||||
void dumpInitialBits(std::vector<uint8_t> &buf) const;
|
||||
void dumpValueChanges(std::vector<uint8_t> &buf) const;
|
||||
|
||||
// We only need to make this class compatible with vector
|
||||
// delete copy constructor and assignment operator
|
||||
VariableInfo(const VariableInfo &) = delete;
|
||||
VariableInfo &operator=(const VariableInfo &) = delete;
|
||||
VariableInfo &operator=(VariableInfo &&) = delete;
|
||||
|
||||
void resize(size_t new_size) {
|
||||
if (need_reallocate(new_size)) {
|
||||
reallocate(new_size);
|
||||
}
|
||||
size(new_size);
|
||||
}
|
||||
void add_size(size_t added_size) { resize(size() + added_size); }
|
||||
uint8_t *data_ptr() { return m_data; }
|
||||
};
|
||||
static_assert(
|
||||
sizeof(VariableInfo) != 12,
|
||||
"We don't support 32-bit architecture, comment out the assertions and take the risk"
|
||||
);
|
||||
static_assert(sizeof(VariableInfo) == 16, "VariableInfo should be small");
|
||||
|
||||
namespace detail {
|
||||
|
||||
constexpr size_t kEmitTimeIndexAndEncodingSize = sizeof(uint64_t) + sizeof(fst::EncodingType);
|
||||
|
||||
// EmitReaderHelper and EmitWriterHelper are very optimized for emit functions
|
||||
// User must ensure the pointer points to the valid memory region
|
||||
struct EmitReaderHelper {
|
||||
const uint8_t *ptr;
|
||||
EmitReaderHelper(const uint8_t *ptr_) : ptr(ptr_) {}
|
||||
|
||||
std::pair<uint64_t, fst::EncodingType> readTimeIndexAndEncoding() {
|
||||
const auto time_index = read<uint64_t>();
|
||||
const auto encoding = read<fst::EncodingType>();
|
||||
return std::make_pair(time_index, encoding);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T read() {
|
||||
const size_t s = sizeof(T);
|
||||
T u;
|
||||
std::memcpy(&u, ptr, s);
|
||||
ptr += s;
|
||||
return u;
|
||||
}
|
||||
|
||||
void skip(size_t count) { ptr += count; }
|
||||
|
||||
template <typename T>
|
||||
T peek(size_t i = 0) {
|
||||
const size_t s = sizeof(T);
|
||||
T u;
|
||||
std::memcpy(&u, ptr + i * s, s);
|
||||
return u;
|
||||
}
|
||||
};
|
||||
|
||||
struct EmitWriterHelper {
|
||||
uint8_t *ptr;
|
||||
|
||||
EmitWriterHelper(uint8_t *ptr_) : ptr(ptr_) {}
|
||||
|
||||
EmitWriterHelper &writeTimeIndexAndEncoding(uint64_t time_index, fst::EncodingType encoding) {
|
||||
write(time_index);
|
||||
write(encoding);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
EmitWriterHelper &write(T u) {
|
||||
const size_t s = sizeof(u);
|
||||
std::memcpy(ptr, &u, s);
|
||||
ptr += s;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
EmitWriterHelper &fill(T u, size_t count) {
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
std::memcpy(ptr, &u, sizeof(u));
|
||||
ptr += sizeof(u);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
EmitWriterHelper &write(T *u, size_t size) {
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
std::memcpy(ptr, u + i, sizeof(T));
|
||||
ptr += sizeof(T);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
class VariableInfoDouble {
|
||||
VariableInfo &info;
|
||||
|
||||
public:
|
||||
VariableInfoDouble(VariableInfo &info_) : info(info_) {}
|
||||
|
||||
public:
|
||||
inline size_t computeBytesNeeded(EncodingType encoding) const {
|
||||
(void)encoding;
|
||||
return kEmitTimeIndexAndEncodingSize + sizeof(double);
|
||||
}
|
||||
|
||||
inline EmitWriterHelper emitValueChangeCommonPart(
|
||||
uint64_t current_time_index, EncodingType encoding
|
||||
) {
|
||||
if (current_time_index + 1 == 0) {
|
||||
info.resize(0);
|
||||
}
|
||||
// For Double, value is always 8 bytes (sizeof(double) or uint64_t)
|
||||
const size_t added_size = computeBytesNeeded(encoding);
|
||||
const size_t old_size = info.size();
|
||||
info.add_size(added_size);
|
||||
|
||||
EmitWriterHelper wh(info.data_ptr() + old_size);
|
||||
wh.writeTimeIndexAndEncoding(current_time_index, encoding);
|
||||
return wh;
|
||||
}
|
||||
|
||||
public:
|
||||
void construct() {
|
||||
const size_t needed = computeBytesNeeded(EncodingType::BINARY);
|
||||
info.resize(needed);
|
||||
EmitWriterHelper wh(info.data_ptr());
|
||||
const double nan_val = std::numeric_limits<double>::quiet_NaN();
|
||||
uint64_t nan_val_u64;
|
||||
std::memcpy(&nan_val_u64, &nan_val, sizeof(nan_val_u64));
|
||||
wh.writeTimeIndexAndEncoding(0, EncodingType::BINARY).write<uint64_t>(nan_val_u64);
|
||||
}
|
||||
|
||||
void emitValueChange(uint64_t current_time_index, const uint64_t val) {
|
||||
auto wh = emitValueChangeCommonPart(current_time_index, EncodingType::BINARY);
|
||||
// Note, do not use write<double> here since the uint64_t is
|
||||
// already bit_cast'ed from double
|
||||
wh.write<uint64_t>(val);
|
||||
}
|
||||
|
||||
// Double variables should not use these array-based emitValueChange overloads.
|
||||
// We implement them to satisfy the VairableInfo::dispatchHelper template instantiation.
|
||||
void emitValueChange(uint64_t, const uint32_t *, EncodingType) {
|
||||
throw std::runtime_error("emitValueChange(uint32_t*) not supported for Double");
|
||||
}
|
||||
void emitValueChange(uint64_t, const uint64_t *, EncodingType) {
|
||||
throw std::runtime_error("emitValueChange(uint64_t*) not supported for Double");
|
||||
}
|
||||
|
||||
void dumpInitialBits(std::vector<uint8_t> &buf) const {
|
||||
FST_DCHECK_GT(info.size(), kEmitTimeIndexAndEncodingSize);
|
||||
EmitReaderHelper rh(info.data_ptr());
|
||||
StreamVectorWriteHelper wh(buf);
|
||||
(void)rh.readTimeIndexAndEncoding();
|
||||
auto v = rh.read<double>();
|
||||
wh.write<double>(v);
|
||||
}
|
||||
|
||||
void dumpValueChanges(std::vector<uint8_t> &buf) const {
|
||||
StreamVectorWriteHelper wh(buf);
|
||||
EmitReaderHelper rh(info.data_ptr());
|
||||
const uint8_t *tail = info.data_ptr() + info.size();
|
||||
|
||||
bool first = true;
|
||||
uint64_t prev_time_index = 0;
|
||||
|
||||
while (true) {
|
||||
if (rh.ptr == tail) break;
|
||||
FST_CHECK_GT(tail, rh.ptr);
|
||||
const auto time_index = rh.read<uint64_t>();
|
||||
const auto enc = rh.read<EncodingType>();
|
||||
const auto num_byte = sizeof(double);
|
||||
if (first) {
|
||||
// Note: [0] is initial value, which is already dumped in dumpInitialBits()
|
||||
first = false;
|
||||
} else {
|
||||
FST_CHECK(enc == EncodingType::BINARY);
|
||||
const uint64_t delta_time_index = time_index - prev_time_index;
|
||||
prev_time_index = time_index;
|
||||
// Double shall be treated as non-binary
|
||||
const bool has_non_binary = true;
|
||||
wh //
|
||||
.writeLEB128((delta_time_index << 1) | has_non_binary)
|
||||
.write<double>(rh.peek<double>());
|
||||
}
|
||||
rh.skip(num_byte);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class VariableInfoScalarInt {
|
||||
VariableInfo &info;
|
||||
|
||||
public:
|
||||
VariableInfoScalarInt(VariableInfo &info_) : info(info_) {}
|
||||
|
||||
public:
|
||||
size_t computeBytesNeeded(EncodingType encoding) const {
|
||||
return kEmitTimeIndexAndEncodingSize + sizeof(T) * bitPerEncodedBit(encoding);
|
||||
}
|
||||
|
||||
// The returning address points to the first byte of the value
|
||||
EmitWriterHelper emitValueChangeCommonPart(uint64_t current_time_index, EncodingType encoding) {
|
||||
if (current_time_index + 1 == 0) {
|
||||
// This is the first value change, we need to remove everything
|
||||
// and then add the new value
|
||||
info.resize(0);
|
||||
}
|
||||
const size_t added_size = computeBytesNeeded(encoding);
|
||||
const size_t old_size = info.size();
|
||||
info.add_size(added_size);
|
||||
EmitWriterHelper wh(info.data_ptr() + old_size);
|
||||
wh.writeTimeIndexAndEncoding(current_time_index, encoding);
|
||||
return wh;
|
||||
}
|
||||
|
||||
public:
|
||||
void construct() {
|
||||
info.resize(computeBytesNeeded(EncodingType::VERILOG));
|
||||
EmitWriterHelper wh(info.data_ptr());
|
||||
wh.writeTimeIndexAndEncoding(0, EncodingType::VERILOG).write(T(0)).write(T(-1));
|
||||
}
|
||||
|
||||
void emitValueChange(uint64_t current_time_index, const uint64_t val) {
|
||||
auto wh = emitValueChangeCommonPart(current_time_index, EncodingType::BINARY);
|
||||
wh.template write<T>(val);
|
||||
}
|
||||
|
||||
void emitValueChange(uint64_t current_time_index, const uint32_t *val, EncodingType encoding) {
|
||||
auto wh = emitValueChangeCommonPart(current_time_index, encoding);
|
||||
for (unsigned i = 0; i < bitPerEncodedBit(encoding); ++i) {
|
||||
// C++17: replace this with if constexpr
|
||||
if (sizeof(T) == 8) {
|
||||
uint64_t v = val[1]; // high bits
|
||||
v <<= 32;
|
||||
v |= val[0]; // low bits
|
||||
wh.template write<uint64_t>(v);
|
||||
val += 2;
|
||||
} else {
|
||||
wh.template write<T>(val[0]);
|
||||
val += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void emitValueChange(uint64_t current_time_index, const uint64_t *val, EncodingType encoding) {
|
||||
auto wh = emitValueChangeCommonPart(current_time_index, encoding);
|
||||
for (unsigned i = 0; i < bitPerEncodedBit(encoding); ++i) {
|
||||
wh.template write<T>(val[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void dumpInitialBits(std::vector<uint8_t> &buf) const {
|
||||
// FST requires initial bits present
|
||||
FST_DCHECK_GT(info.size(), kEmitTimeIndexAndEncodingSize);
|
||||
EmitReaderHelper rh(info.data_ptr());
|
||||
const auto time_index_enc = rh.readTimeIndexAndEncoding();
|
||||
const auto enc = time_index_enc.second;
|
||||
const auto bitwidth = info.bitwidth();
|
||||
|
||||
switch (enc) {
|
||||
case EncodingType::BINARY: {
|
||||
auto v0 = rh.read<T>();
|
||||
for (unsigned i = bitwidth; i-- > 0;) {
|
||||
const char c = ((v0 >> i) & T(1)) ? '1' : '0';
|
||||
buf.push_back(c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case EncodingType::VERILOG: {
|
||||
auto v0 = rh.read<T>();
|
||||
auto v1 = rh.read<T>();
|
||||
for (unsigned i = bitwidth; i-- > 0;) {
|
||||
const T b1 = ((v1 >> i) & T(1));
|
||||
const T b0 = ((v0 >> i) & T(1));
|
||||
const char c = kEncodedBitToCharTable[(b1 << 1) | b0];
|
||||
buf.push_back(c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Not supporting VHDL now
|
||||
// LCOV_EXCL_START
|
||||
default:
|
||||
case EncodingType::VHDL: {
|
||||
auto v0 = rh.read<T>();
|
||||
auto v1 = rh.read<T>();
|
||||
auto v2 = rh.read<T>();
|
||||
for (unsigned i = bitwidth; i-- > 0;) {
|
||||
const T b2 = ((v2 >> i) & T(1));
|
||||
const T b1 = ((v1 >> i) & T(1));
|
||||
const T b0 = ((v0 >> i) & T(1));
|
||||
const char c = kEncodedBitToCharTable[(b2 << 2) | (b1 << 1) | b0];
|
||||
buf.push_back(c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
void dumpValueChanges(std::vector<uint8_t> &buf) const {
|
||||
StreamVectorWriteHelper h(buf);
|
||||
EmitReaderHelper rh(info.data_ptr());
|
||||
const uint8_t *tail = info.data_ptr() + info.size();
|
||||
const auto bitwidth = info.bitwidth();
|
||||
bool first = true;
|
||||
uint64_t prev_time_index = 0;
|
||||
if (bitwidth == 1) {
|
||||
while (true) {
|
||||
if (rh.ptr == tail) {
|
||||
break;
|
||||
}
|
||||
FST_DCHECK_GT(tail, rh.ptr);
|
||||
const auto time_index = rh.read<uint64_t>();
|
||||
const auto enc = rh.read<EncodingType>();
|
||||
const auto num_element = bitPerEncodedBit(enc);
|
||||
const auto num_byte = num_element * sizeof(T);
|
||||
if (first) {
|
||||
// Note: [0] is initial value, which is already dumped in dumpInitialBits()
|
||||
first = false;
|
||||
} else {
|
||||
unsigned val = 0;
|
||||
for (unsigned i = 0; i < num_element; ++i) {
|
||||
val |= rh.peek<T>(i);
|
||||
}
|
||||
uint64_t delta_time_index = time_index - prev_time_index;
|
||||
prev_time_index = time_index;
|
||||
switch (val) {
|
||||
// clang-format off
|
||||
case 0: delta_time_index = (delta_time_index<<2) | (0<<1) | 0; break; // '0'
|
||||
case 1: delta_time_index = (delta_time_index<<2) | (1<<1) | 0; break; // '1'
|
||||
case 2: delta_time_index = (delta_time_index<<4) | (0<<1) | 1; break; // 'X'
|
||||
case 3: delta_time_index = (delta_time_index<<4) | (1<<1) | 1; break; // 'Z'
|
||||
// Not supporting VHDL now
|
||||
// LCOV_EXCL_START
|
||||
case 4: delta_time_index = (delta_time_index<<4) | (2<<1) | 1; break; // 'H'
|
||||
case 5: delta_time_index = (delta_time_index<<4) | (3<<1) | 1; break; // 'U'
|
||||
case 6: delta_time_index = (delta_time_index<<4) | (4<<1) | 1; break; // 'W'
|
||||
case 7: delta_time_index = (delta_time_index<<4) | (5<<1) | 1; break; // 'L'
|
||||
case 8: delta_time_index = (delta_time_index<<4) | (6<<1) | 1; break; // '-'
|
||||
case 9: delta_time_index = (delta_time_index<<4) | (7<<1) | 1; break; // '?'
|
||||
default: break;
|
||||
// LCOV_EXCL_STOP
|
||||
// clang-format on
|
||||
}
|
||||
h.writeLEB128(delta_time_index);
|
||||
}
|
||||
rh.skip(num_byte);
|
||||
}
|
||||
} else {
|
||||
while (true) {
|
||||
if (rh.ptr == tail) {
|
||||
break;
|
||||
}
|
||||
FST_CHECK_GT(tail, rh.ptr);
|
||||
const auto time_index = rh.read<uint64_t>();
|
||||
const auto enc = rh.read<EncodingType>();
|
||||
const auto num_element = bitPerEncodedBit(enc);
|
||||
const auto num_byte = num_element * sizeof(T);
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
FST_CHECK(enc == EncodingType::BINARY); // TODO
|
||||
const bool has_non_binary = enc != EncodingType::BINARY;
|
||||
const uint64_t delta_time_index = time_index - prev_time_index;
|
||||
prev_time_index = time_index;
|
||||
h //
|
||||
.writeLEB128((delta_time_index << 1) | has_non_binary)
|
||||
.writeUIntPartialForValueChange(rh.peek<T>(), bitwidth);
|
||||
}
|
||||
rh.skip(num_byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class VariableInfoLongInt {
|
||||
VariableInfo &info;
|
||||
unsigned num_words() const { return (info.bitwidth() + 63) / 64; }
|
||||
|
||||
public:
|
||||
VariableInfoLongInt(VariableInfo &info_) : info(info_) {}
|
||||
|
||||
public:
|
||||
size_t computeBytesNeeded(EncodingType encoding) const {
|
||||
return (
|
||||
kEmitTimeIndexAndEncodingSize +
|
||||
num_words() * sizeof(uint64_t) * bitPerEncodedBit(encoding)
|
||||
);
|
||||
}
|
||||
|
||||
EmitWriterHelper emitValueChangeCommonPart(uint64_t current_time_index, EncodingType encoding) {
|
||||
if (current_time_index + 1 == 0) {
|
||||
info.resize(0);
|
||||
}
|
||||
const size_t added_size = computeBytesNeeded(encoding);
|
||||
const size_t old_size = info.size();
|
||||
info.add_size(added_size);
|
||||
|
||||
EmitWriterHelper wh(info.data_ptr() + old_size);
|
||||
wh.writeTimeIndexAndEncoding(current_time_index, encoding);
|
||||
return wh;
|
||||
}
|
||||
|
||||
public:
|
||||
void construct() {
|
||||
const size_t nw = num_words();
|
||||
info.resize(computeBytesNeeded(EncodingType::VERILOG));
|
||||
EmitWriterHelper wh(info.data_ptr());
|
||||
wh //
|
||||
.writeTimeIndexAndEncoding(0, EncodingType::VERILOG)
|
||||
.fill(uint64_t(0), nw)
|
||||
.fill(uint64_t(-1), nw);
|
||||
}
|
||||
|
||||
void emitValueChange(uint64_t current_time_index, const uint64_t val) {
|
||||
const unsigned nw = num_words();
|
||||
auto wh = emitValueChangeCommonPart(current_time_index, EncodingType::BINARY);
|
||||
wh.write(val).fill(uint64_t(0), nw - 1);
|
||||
}
|
||||
|
||||
void emitValueChange(uint64_t current_time_index, const uint32_t *val, EncodingType encoding) {
|
||||
const unsigned nw32 = (info.bitwidth() + 31) / 32;
|
||||
const unsigned bpb = bitPerEncodedBit(encoding);
|
||||
|
||||
auto wh = emitValueChangeCommonPart(current_time_index, encoding);
|
||||
|
||||
for (unsigned i = 0; i < bpb; ++i) {
|
||||
for (unsigned j = 0; j < nw32 / 2; ++j) {
|
||||
uint64_t v = val[1]; // high bits
|
||||
v <<= 32;
|
||||
v |= val[0]; // low bits
|
||||
wh.write(v);
|
||||
val += 2;
|
||||
}
|
||||
if (nw32 % 2 != 0) {
|
||||
uint64_t v = val[0];
|
||||
wh.write(v);
|
||||
val += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void emitValueChange(uint64_t current_time_index, const uint64_t *val, EncodingType encoding) {
|
||||
const unsigned nw_encoded = num_words() * bitPerEncodedBit(encoding);
|
||||
auto wh = emitValueChangeCommonPart(current_time_index, encoding);
|
||||
wh.write(val, nw_encoded);
|
||||
}
|
||||
|
||||
void dumpInitialBits(std::vector<uint8_t> &buf) const {
|
||||
FST_DCHECK_GT(info.size(), kEmitTimeIndexAndEncodingSize);
|
||||
EmitReaderHelper rh(info.data_ptr());
|
||||
const auto time_index_enc = rh.readTimeIndexAndEncoding();
|
||||
const auto enc = time_index_enc.second;
|
||||
const unsigned nw = num_words();
|
||||
switch (enc) {
|
||||
case EncodingType::BINARY: {
|
||||
for (unsigned word_index = nw; word_index-- > 0;) {
|
||||
const uint64_t v0 = rh.peek<uint64_t>(word_index);
|
||||
const unsigned num_bit =
|
||||
(word_index * 64 + 64 > info.bitwidth()) ? (info.bitwidth() % 64) : 64;
|
||||
for (unsigned bit_index = num_bit; bit_index-- > 0;) {
|
||||
const char c = ((v0 >> bit_index) & uint64_t(1)) ? '1' : '0';
|
||||
buf.push_back(c);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EncodingType::VERILOG: {
|
||||
for (unsigned word_index = nw; word_index-- > 0;) {
|
||||
const uint64_t v0 = rh.peek<uint64_t>(nw * 0 + word_index);
|
||||
const uint64_t v1 = rh.peek<uint64_t>(nw * 1 + word_index);
|
||||
const unsigned num_bit =
|
||||
(word_index * 64 + 64 > info.bitwidth()) ? (info.bitwidth() % 64) : 64;
|
||||
for (unsigned bit_index = num_bit; bit_index-- > 0;) {
|
||||
const bool b0 = ((v0 >> bit_index) & uint64_t(1));
|
||||
const bool b1 = ((v1 >> bit_index) & uint64_t(1));
|
||||
const char c = kEncodedBitToCharTable[(b1 << 1) | b0];
|
||||
buf.push_back(c);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
case EncodingType::VHDL: {
|
||||
// Not supporting VHDL now
|
||||
// LCOV_EXCL_START
|
||||
for (unsigned word_index = nw; word_index-- > 0;) {
|
||||
const uint64_t v0 = rh.peek<uint64_t>(nw * 0 + word_index);
|
||||
const uint64_t v1 = rh.peek<uint64_t>(nw * 1 + word_index);
|
||||
const uint64_t v2 = rh.peek<uint64_t>(nw * 2 + word_index);
|
||||
const unsigned num_bit =
|
||||
(word_index * 64 + 64 > info.bitwidth()) ? (info.bitwidth() % 64) : 64;
|
||||
for (unsigned bit_index = num_bit; bit_index-- > 0;) {
|
||||
const bool b0 = ((v0 >> bit_index) & uint64_t(1));
|
||||
const bool b1 = ((v1 >> bit_index) & uint64_t(1));
|
||||
const bool b2 = ((v2 >> bit_index) & uint64_t(1));
|
||||
const char c = kEncodedBitToCharTable[(b2 << 2) | (b1 << 1) | b0];
|
||||
buf.push_back(c);
|
||||
}
|
||||
}
|
||||
break;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
rh.skip(sizeof(uint64_t) * nw * bitPerEncodedBit(enc));
|
||||
}
|
||||
}
|
||||
|
||||
void dumpValueChanges(std::vector<uint8_t> &buf) const {
|
||||
StreamVectorWriteHelper h(buf);
|
||||
EmitReaderHelper rh(info.data_ptr());
|
||||
const uint8_t *tail = info.data_ptr() + info.size();
|
||||
const unsigned nw = num_words();
|
||||
const unsigned bitwidth = info.bitwidth(); // Local copy for lambda capture/usage if needed
|
||||
|
||||
bool first = true;
|
||||
uint64_t prev_time_index = 0;
|
||||
|
||||
while (true) {
|
||||
if (rh.ptr == tail) break;
|
||||
FST_DCHECK_GT(tail, rh.ptr);
|
||||
const auto time_index = rh.read<uint64_t>();
|
||||
const auto enc = rh.read<EncodingType>();
|
||||
const auto num_element = bitPerEncodedBit(enc);
|
||||
const auto num_byte = num_element * nw * sizeof(uint64_t);
|
||||
if (first) {
|
||||
// Note: [0] is initial value, which is already dumped in dumpInitialBits()
|
||||
first = false;
|
||||
} else {
|
||||
FST_CHECK(enc == EncodingType::BINARY); // TODO
|
||||
const bool has_non_binary = enc != EncodingType::BINARY;
|
||||
const uint64_t delta_time_index = time_index - prev_time_index;
|
||||
prev_time_index = time_index;
|
||||
h.writeLEB128((delta_time_index << 1) | has_non_binary);
|
||||
if (bitwidth % 64 != 0) {
|
||||
const unsigned remaining = bitwidth % 64;
|
||||
uint64_t hi64 = rh.peek<uint64_t>(nw - 1);
|
||||
// write from nw-1 to 1
|
||||
for (unsigned j = nw - 1; j > 0; --j) {
|
||||
uint64_t lo64 = rh.peek<uint64_t>(j - 1);
|
||||
h.writeUIntBE((hi64 << (64 - remaining)) | (lo64 >> remaining));
|
||||
hi64 = lo64;
|
||||
}
|
||||
// write 0
|
||||
h.writeUIntPartialForValueChange(hi64, remaining);
|
||||
} else {
|
||||
// write from nw-1 to 0
|
||||
for (unsigned j = nw; j-- > 0;) {
|
||||
h.writeUIntBE(rh.peek<uint64_t>(j));
|
||||
}
|
||||
}
|
||||
}
|
||||
rh.skip(num_byte);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename Callable, typename... Args>
|
||||
auto VariableInfo::dispatchHelper(Callable &&callable, Args &&...args) const {
|
||||
const uint32_t bitwidth = this->bitwidth();
|
||||
const bool is_real = this->is_real();
|
||||
if (!is_real) {
|
||||
// Decision: the branch miss is too expensive for large design, so we only use 3 types of
|
||||
// int
|
||||
if (bitwidth <= 8) {
|
||||
return callable(
|
||||
detail::VariableInfoScalarInt<uint8_t>(const_cast<VariableInfo &>(*this)),
|
||||
std::forward<Args>(args)...
|
||||
);
|
||||
} else if (bitwidth <= 64) {
|
||||
return callable(
|
||||
detail::VariableInfoScalarInt<uint64_t>(const_cast<VariableInfo &>(*this)),
|
||||
std::forward<Args>(args)...
|
||||
);
|
||||
} else {
|
||||
return callable(
|
||||
detail::VariableInfoLongInt(const_cast<VariableInfo &>(*this)),
|
||||
std::forward<Args>(args)...
|
||||
);
|
||||
}
|
||||
}
|
||||
return callable(
|
||||
detail::VariableInfoDouble(const_cast<VariableInfo &>(*this)), std::forward<Args>(args)...
|
||||
);
|
||||
}
|
||||
|
||||
inline VariableInfo::VariableInfo(uint32_t bitwidth_, bool is_real_) {
|
||||
platform::write_field(m_misc, bitwidth_, kBitwidthWidth, kBitwidthOffset);
|
||||
platform::write_field(m_misc, is_real_, kIsRealWidth, kIsRealOffset);
|
||||
dispatchHelper([](auto obj) { obj.construct(); });
|
||||
last_written_encode_type(EncodingType::BINARY);
|
||||
}
|
||||
|
||||
inline uint32_t VariableInfo::emitValueChange(uint64_t current_time_index, const uint64_t val) {
|
||||
const uint64_t old_size = size();
|
||||
dispatchHelper([=](auto obj) { obj.emitValueChange(current_time_index, val); });
|
||||
last_written_encode_type(EncodingType::BINARY);
|
||||
return static_cast<uint32_t>(size() - old_size);
|
||||
}
|
||||
|
||||
inline uint32_t VariableInfo::emitValueChange(
|
||||
uint64_t current_time_index, const uint32_t *val, EncodingType encoding
|
||||
) {
|
||||
const uint64_t old_size = size();
|
||||
dispatchHelper([=](auto obj) { obj.emitValueChange(current_time_index, val, encoding); });
|
||||
last_written_encode_type(encoding);
|
||||
return static_cast<uint32_t>(size() - old_size);
|
||||
}
|
||||
|
||||
inline uint32_t VariableInfo::emitValueChange(
|
||||
uint64_t current_time_index, const uint64_t *val, EncodingType encoding
|
||||
) {
|
||||
const uint64_t old_size = size();
|
||||
dispatchHelper([=](auto obj) { obj.emitValueChange(current_time_index, val, encoding); });
|
||||
last_written_encode_type(encoding);
|
||||
return static_cast<uint32_t>(size() - old_size);
|
||||
}
|
||||
|
||||
inline void VariableInfo::dumpInitialBits(std::vector<uint8_t> &buf) const {
|
||||
dispatchHelper([&](auto obj) { obj.dumpInitialBits(buf); });
|
||||
}
|
||||
|
||||
inline void VariableInfo::dumpValueChanges(std::vector<uint8_t> &buf) const {
|
||||
dispatchHelper([&](auto obj) { obj.dumpValueChanges(buf); });
|
||||
}
|
||||
|
||||
inline uint64_t VariableInfo::last_written_bytes() const {
|
||||
const EncodingType encoding = last_written_encode_type();
|
||||
return dispatchHelper([encoding](auto obj) { return obj.computeBytesNeeded(encoding); });
|
||||
}
|
||||
|
||||
} // namespace fst
|
||||
@@ -0,0 +1,891 @@
|
||||
// SPDX-FileCopyrightText: 2025-2026 Yu-Sheng Lin <[email protected]>
|
||||
// SPDX-FileCopyrightText: 2025-2026 Yoda Lee <[email protected]>
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Project: libfstwriter
|
||||
// Website: https://github.com/gtkwave/libfstwriter
|
||||
// direct include
|
||||
#include "fstcpp/fstcpp_writer.h"
|
||||
// C system headers
|
||||
// C++ standard library headers
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
// Other libraries' .h files.
|
||||
#include <lz4.h>
|
||||
#include <zlib.h>
|
||||
// Your project's .h files.
|
||||
#include "fstcpp/fstcpp.h"
|
||||
#include "fstcpp/fstcpp_assertion.h"
|
||||
#include "fstcpp/fstcpp_stream_write_helper.h"
|
||||
#include "fstcpp/fstcpp_variable_info.h"
|
||||
|
||||
// AT(vec, x) is used to access vector at index x, and it will throw exception if out of bound
|
||||
// in debug mode, but in release mode, it will not throw exception
|
||||
// Usually you should only need AT(vec, x) only at very hot code path.
|
||||
#ifndef NDEBUG
|
||||
# define AT(vec, x) (vec.at(x))
|
||||
#else
|
||||
# define AT(vec, x) (vec[x])
|
||||
#endif
|
||||
|
||||
namespace fst {
|
||||
|
||||
namespace detail {
|
||||
|
||||
void BlackoutData::emitDumpActive(uint64_t current_timestamp, bool enable) {
|
||||
StreamVectorWriteHelper h(m_buffer);
|
||||
h.writeUIntBE<uint8_t>(enable).writeLEB128(current_timestamp - m_previous_timestamp);
|
||||
++m_count;
|
||||
}
|
||||
|
||||
ValueChangeData::ValueChangeData() {
|
||||
m_variable_infos.reserve(1024);
|
||||
}
|
||||
|
||||
ValueChangeData::~ValueChangeData() = default;
|
||||
|
||||
void ValueChangeData::keepOnlyTheLatestValue() {
|
||||
for (VariableInfo &v : m_variable_infos) {
|
||||
v.keepOnlyTheLatestValue();
|
||||
}
|
||||
FST_CHECK(!m_timestamps.empty());
|
||||
m_timestamps.front() = m_timestamps.back();
|
||||
m_timestamps.resize(1);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
void Writer::open(const string_view_pair name) {
|
||||
FST_CHECK(!m_main_fst_file_.is_open());
|
||||
m_main_fst_file_.open(std::string(name.m_data, name.m_size), std::ios::binary);
|
||||
// reserve space for header, we will write it at Close(), append geometry and hierarchy at the
|
||||
// end wave data will be flushed in between
|
||||
m_main_fst_file_.seekp(kSharedBlockHeaderSize + HeaderInfo::total_size, std::ios_base::beg);
|
||||
}
|
||||
|
||||
void Writer::close() {
|
||||
if (!m_main_fst_file_.is_open()) return;
|
||||
// Finalize header fields
|
||||
if (m_header_.m_date[0] == '\0') {
|
||||
// date is not set yet, set to the current date
|
||||
setDate();
|
||||
}
|
||||
if (m_header_.m_start_time == kInvalidTime) {
|
||||
m_header_.m_start_time = 0;
|
||||
}
|
||||
flushValueChangeData_(m_value_change_data_, m_main_fst_file_);
|
||||
appendGeometry_(m_main_fst_file_);
|
||||
appendHierarchy_(m_main_fst_file_);
|
||||
appendBlackout_(m_main_fst_file_);
|
||||
// Note: write header seek to 0, so we need to do
|
||||
// this after all append operations
|
||||
writeHeader_(m_header_, m_main_fst_file_);
|
||||
m_main_fst_file_.close();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////
|
||||
// Hierarchy / variable API
|
||||
/////////////////////////////////////////
|
||||
void Writer::setScope(
|
||||
Hierarchy::ScopeType scopetype,
|
||||
const string_view_pair scopename,
|
||||
const string_view_pair scopecomp
|
||||
) {
|
||||
FST_CHECK(!m_hierarchy_finalized_);
|
||||
StreamVectorWriteHelper h(m_hierarchy_buffer_);
|
||||
h //
|
||||
.writeU8Enum(Hierarchy::ScopeControlType::VCD_SCOPE)
|
||||
.writeU8Enum(scopetype)
|
||||
.writeString0(scopename)
|
||||
.writeString0(scopecomp);
|
||||
++m_header_.m_num_scopes;
|
||||
}
|
||||
|
||||
void Writer::upscope() {
|
||||
FST_CHECK(!m_hierarchy_finalized_);
|
||||
// TODO: shall we inline it?
|
||||
StreamVectorWriteHelper h(m_hierarchy_buffer_);
|
||||
h.writeU8Enum(Hierarchy::ScopeControlType::VCD_UPSCOPE);
|
||||
}
|
||||
|
||||
Handle Writer::createVar(
|
||||
Hierarchy::VarType vartype,
|
||||
Hierarchy::VarDirection vardir,
|
||||
uint32_t bitwidth,
|
||||
const string_view_pair name,
|
||||
Handle alias_handle
|
||||
) {
|
||||
FST_CHECK(!m_hierarchy_finalized_);
|
||||
FST_CHECK_LE(bitwidth, VariableInfo::kMaxSupportedBitwidth);
|
||||
// write hierarchy entry: type, direction, name, length, alias
|
||||
StreamVectorWriteHelper h(m_hierarchy_buffer_);
|
||||
|
||||
// determine real/string handling like original C implementation
|
||||
bool is_real{false};
|
||||
switch (vartype) {
|
||||
case Hierarchy::VarType::VCD_REAL:
|
||||
case Hierarchy::VarType::VCD_REAL_PARAMETER:
|
||||
case Hierarchy::VarType::VCD_REALTIME:
|
||||
case Hierarchy::VarType::SV_SHORTREAL:
|
||||
is_real = true;
|
||||
bitwidth = 8; // recast to double size
|
||||
break;
|
||||
case Hierarchy::VarType::GEN_STRING:
|
||||
bitwidth = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (alias_handle > m_header_.m_num_handles) {
|
||||
// sanitize
|
||||
alias_handle = 0;
|
||||
}
|
||||
const bool is_alias{alias_handle != 0};
|
||||
// This counter is incremented whether alias || non-alias
|
||||
++m_header_.m_num_vars;
|
||||
if (!is_alias) {
|
||||
// This counter is incremented only for non-alias variables
|
||||
++m_header_.m_num_handles;
|
||||
alias_handle = static_cast<uint32_t>(m_header_.m_num_handles);
|
||||
}
|
||||
|
||||
h //
|
||||
.writeU8Enum(vartype)
|
||||
.writeU8Enum(vardir)
|
||||
.writeString0(name)
|
||||
.writeLEB128(bitwidth)
|
||||
.writeLEB128(is_alias ? alias_handle : 0);
|
||||
|
||||
// If alias_handle == 0, we must allocate geom/valpos/curval entries and create a new handle
|
||||
if (!is_alias) {
|
||||
StreamVectorWriteHelper g(m_geometry_buffer_);
|
||||
// I don't know why the original C implementation encode bitwidth again
|
||||
const uint32_t geom_len{(bitwidth == 0 ? uint32_t(-1) : is_real ? uint32_t(0) : bitwidth)};
|
||||
g.writeLEB128(geom_len);
|
||||
m_value_change_data_.m_variable_infos.emplace_back(bitwidth, is_real);
|
||||
}
|
||||
|
||||
return alias_handle;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// LCOV_EXCL_START
|
||||
// Handle Writer::createVar2(
|
||||
// Hierarchy::VarType vartype,
|
||||
// Hierarchy::VarDirection vardir,
|
||||
// uint32_t bitwidth,
|
||||
// const string_view_pair name,
|
||||
// Handle alias_handle,
|
||||
// const string_view_pair type,
|
||||
// Hierarchy::SupplementalVarType svt,
|
||||
// Hierarchy::SupplementalDataType sdt
|
||||
// ) {
|
||||
// FST_CHECK(!m_hierarchy_finalized_);
|
||||
// (void)vartype;
|
||||
// (void)vardir;
|
||||
// (void)bitwidth;
|
||||
// (void)name;
|
||||
// (void)alias_handle;
|
||||
// (void)type;
|
||||
// (void)svt;
|
||||
// (void)sdt;
|
||||
// throw std::runtime_error("TODO");
|
||||
// return 0;
|
||||
// }
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
/////////////////////////////////////////
|
||||
// Waveform API
|
||||
/////////////////////////////////////////
|
||||
void Writer::emitTimeChange(uint64_t tim) {
|
||||
finalizeHierarchy_();
|
||||
|
||||
if (m_value_change_data_usage_ > m_value_change_data_flush_threshold_ || m_flush_pending_) {
|
||||
flushValueChangeData_(m_value_change_data_, m_main_fst_file_);
|
||||
}
|
||||
|
||||
// Update header
|
||||
m_header_.m_start_time = std::min(m_header_.m_start_time, tim);
|
||||
m_header_.m_end_time = tim;
|
||||
|
||||
if (m_value_change_data_.m_timestamps.empty() ||
|
||||
m_value_change_data_.m_timestamps.back() != tim) {
|
||||
m_value_change_data_.m_timestamps.push_back(tim);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
// void Writer::emitDumpActive(bool enable) {
|
||||
// // TODO: this API is not fully understood, need to check
|
||||
// FST_CHECK(!m_value_change_data_.m_timestamps.empty());
|
||||
// m_blackout_data_.emitDumpActive(m_value_change_data_.m_timestamps.back(), enable);
|
||||
// }
|
||||
|
||||
template <typename... T>
|
||||
void Writer::emitValueChangeHelper_(Handle handle, T &&...val) {
|
||||
// Let data prefetch go first
|
||||
VariableInfo &var_info = AT(m_value_change_data_.m_variable_infos, handle - 1);
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__builtin_prefetch(var_info.data_ptr() + var_info.size() - 1, 1, 0);
|
||||
#endif
|
||||
|
||||
finalizeHierarchy_();
|
||||
|
||||
// Original implementation: virtual, but vtable is too costly, we switch to if-else static
|
||||
// dispatch
|
||||
m_value_change_data_usage_ += var_info.emitValueChange(
|
||||
m_value_change_data_.m_timestamps.size() - 1, std::forward<T>(val)...
|
||||
);
|
||||
}
|
||||
|
||||
void Writer::emitValueChange(Handle handle, const uint32_t *val, EncodingType encoding) {
|
||||
emitValueChangeHelper_(handle, val, encoding);
|
||||
}
|
||||
|
||||
void Writer::emitValueChange(Handle handle, const uint64_t *val, EncodingType encoding) {
|
||||
emitValueChangeHelper_(handle, val, encoding);
|
||||
}
|
||||
|
||||
void Writer::emitValueChange(Handle handle, uint64_t val) {
|
||||
emitValueChangeHelper_(handle, val);
|
||||
}
|
||||
|
||||
void Writer::emitValueChange(Handle handle, const char *val) {
|
||||
finalizeHierarchy_();
|
||||
VariableInfo &var_info = AT(m_value_change_data_.m_variable_infos, handle - 1);
|
||||
|
||||
// For double handles, const char* is interpreted as a double* (8B)
|
||||
// This double shall be written out as raw IEEE 754 double
|
||||
// So we just reinterpret_cast it to uint64_t and emit it
|
||||
if (var_info.is_real()) {
|
||||
emitValueChange(handle, *reinterpret_cast<const uint64_t *>(val));
|
||||
return;
|
||||
}
|
||||
|
||||
// For normal integer handles, const char* is "01xz..." (1B per bit)
|
||||
const uint32_t bitwidth{var_info.bitwidth()};
|
||||
FST_DCHECK_NE(bitwidth, 0);
|
||||
|
||||
val += bitwidth;
|
||||
const unsigned num_words{(bitwidth + 63) / 64};
|
||||
m_packed_value_buffer_.assign(num_words, 0);
|
||||
for (unsigned i = 0; i < num_words; ++i) {
|
||||
const char *start{val - std::min((i + 1) * 64, bitwidth)};
|
||||
const char *end{val - 64 * i};
|
||||
m_packed_value_buffer_[i] = 0;
|
||||
for (const char *p = start; p < end; ++p) {
|
||||
// No checking for invalid characters, follow original C implementation
|
||||
m_packed_value_buffer_[i] <<= 1;
|
||||
m_packed_value_buffer_[i] |= static_cast<uint64_t>(*p - '0');
|
||||
}
|
||||
}
|
||||
|
||||
if (bitwidth <= 64) {
|
||||
emitValueChange(handle, m_packed_value_buffer_.front());
|
||||
} else {
|
||||
emitValueChange(handle, m_packed_value_buffer_.data(), EncodingType::BINARY);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////
|
||||
// File flushing functions
|
||||
/////////////////////////////////////////
|
||||
void Writer::writeHeader_(const Header &header, std::ostream &os) {
|
||||
StreamWriteHelper h(os);
|
||||
static char kDefaultWriterName[sizeof(header.m_writer)] = "fstcppWriter";
|
||||
const char *writer_name = header.m_writer[0] == '\0' ? kDefaultWriterName : header.m_writer;
|
||||
|
||||
// Actual write
|
||||
h //
|
||||
.seek(std::streamoff(0), std::ios_base::beg)
|
||||
.writeBlockHeader(BlockType::HEADER, HeaderInfo::total_size)
|
||||
.writeUInt(header.m_start_time)
|
||||
.writeUInt(header.m_end_time)
|
||||
.writeFloat(HeaderInfo::kEndianessMagicIdentifier)
|
||||
.writeUInt(header.m_writer_memory_use)
|
||||
.writeUInt(header.m_num_scopes)
|
||||
.writeUInt(header.m_num_vars)
|
||||
.writeUInt(header.m_num_handles)
|
||||
.writeUInt(header.m_num_value_change_data_blocks)
|
||||
.writeUInt(header.m_timescale)
|
||||
.write(writer_name, sizeof(header.m_writer))
|
||||
.write(header.m_date, sizeof(header.m_date))
|
||||
.fill('\0', HeaderInfo::Size::reserved)
|
||||
.writeUInt(static_cast<uint8_t>(header.m_filetype))
|
||||
.writeUInt(header.m_timezero);
|
||||
|
||||
FST_DCHECK_EQ(os.tellp(), HeaderInfo::total_size + kSharedBlockHeaderSize);
|
||||
}
|
||||
|
||||
namespace { // compression helpers
|
||||
|
||||
// These API pass compressed_data to avoid frequent reallocations
|
||||
void compressUsingLz4(
|
||||
const std::vector<uint8_t> &uncompressed_data, std::vector<uint8_t> &compressed_data
|
||||
) {
|
||||
const int uncompressed_size = uncompressed_data.size();
|
||||
const int compressed_bound = LZ4_compressBound(uncompressed_size);
|
||||
compressed_data.resize(compressed_bound);
|
||||
const int compressed_size = LZ4_compress_default(
|
||||
reinterpret_cast<const char *>(uncompressed_data.data()),
|
||||
reinterpret_cast<char *>(compressed_data.data()),
|
||||
uncompressed_size,
|
||||
compressed_bound
|
||||
);
|
||||
compressed_data.resize(compressed_size);
|
||||
}
|
||||
|
||||
void compressUsingZlib(
|
||||
const std::vector<uint8_t> &uncompressed_data, std::vector<uint8_t> &compressed_data, int level
|
||||
) {
|
||||
// compress using zlib
|
||||
const uLong uncompressed_size = uncompressed_data.size();
|
||||
uLongf compressed_bound = compressBound(uncompressed_size);
|
||||
compressed_data.resize(compressed_bound);
|
||||
const auto z_status = compress2(
|
||||
reinterpret_cast<Bytef *>(compressed_data.data()),
|
||||
&compressed_bound,
|
||||
reinterpret_cast<const Bytef *>(uncompressed_data.data()),
|
||||
uncompressed_size,
|
||||
level
|
||||
);
|
||||
if (z_status != Z_OK) {
|
||||
throw std::runtime_error(
|
||||
"Failed to compress data with zlib, error code: " + std::to_string(z_status)
|
||||
);
|
||||
}
|
||||
compressed_data.resize(compressed_bound);
|
||||
}
|
||||
|
||||
std::pair<const uint8_t *, size_t> selectSmaller(
|
||||
const std::vector<uint8_t> &compressed_data, const std::vector<uint8_t> &uncompressed_data
|
||||
) {
|
||||
std::pair<const uint8_t *, size_t> ret;
|
||||
if (compressed_data.size() < uncompressed_data.size()) {
|
||||
ret.first = compressed_data.data();
|
||||
ret.second = compressed_data.size();
|
||||
} else {
|
||||
ret.first = uncompressed_data.data();
|
||||
ret.second = uncompressed_data.size();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// AppendHierarchy_ and AppendGeometry_ shares a very similar structure
|
||||
// But they are slightly different in the original C implementation...
|
||||
void Writer::appendGeometry_(std::ostream &os) {
|
||||
if (m_geometry_buffer_.empty()) {
|
||||
// skip the geometry block if there is no data
|
||||
return;
|
||||
}
|
||||
std::vector<uint8_t> geometry_buffer_compressed_{};
|
||||
compressUsingZlib(m_geometry_buffer_, geometry_buffer_compressed_, 9);
|
||||
// TODO: Replace with structured binding in C++17
|
||||
const std::pair<const uint8_t *, size_t> selected_pair =
|
||||
selectSmaller(geometry_buffer_compressed_, m_geometry_buffer_);
|
||||
const uint8_t *selected_data = selected_pair.first;
|
||||
const size_t selected_size = selected_pair.second;
|
||||
|
||||
StreamWriteHelper h(os);
|
||||
h //
|
||||
.seek(0, std::ios_base::end)
|
||||
// 16 is for the uncompressed_size and header_.num_handles
|
||||
.writeBlockHeader(BlockType::GEOMETRY, selected_size + 16)
|
||||
.writeUInt<uint64_t>(m_geometry_buffer_.size())
|
||||
// I don't know why the original C implementation write num_handles again here
|
||||
// but we have to follow it
|
||||
.writeUInt(m_header_.m_num_handles)
|
||||
.write(selected_data, selected_size);
|
||||
}
|
||||
|
||||
void Writer::appendHierarchy_(std::ostream &os) {
|
||||
if (m_hierarchy_buffer_.empty()) {
|
||||
// skip the hierarchy block if there is no data
|
||||
return;
|
||||
}
|
||||
|
||||
// compress hierarchy_buffer_ using LZ4.
|
||||
const int compressed_bound{LZ4_compressBound(m_hierarchy_buffer_.size())};
|
||||
std::vector<uint8_t> hierarchy_buffer_compressed_(compressed_bound);
|
||||
const int compressed_size{LZ4_compress_default(
|
||||
reinterpret_cast<const char *>(m_hierarchy_buffer_.data()),
|
||||
reinterpret_cast<char *>(hierarchy_buffer_compressed_.data()),
|
||||
m_hierarchy_buffer_.size(),
|
||||
compressed_bound
|
||||
)};
|
||||
|
||||
StreamWriteHelper h(os);
|
||||
h //
|
||||
.seek(0, std::ios_base::end)
|
||||
// +16 is for the uncompressed_size
|
||||
.writeBlockHeader(BlockType::HIERARCHY_LZ4_COMPRESSED, compressed_size + 8)
|
||||
.writeUInt<uint64_t>(m_hierarchy_buffer_.size())
|
||||
.write(hierarchy_buffer_compressed_.data(), compressed_size);
|
||||
}
|
||||
|
||||
void Writer::appendBlackout_(std::ostream &os) {
|
||||
if (m_blackout_data_.m_count == 0) {
|
||||
// skip the blackout block if there is no data
|
||||
return;
|
||||
}
|
||||
const std::vector<uint8_t> &blackout_data = m_blackout_data_.m_buffer;
|
||||
const std::streampos begin_of_blackout_block = os.tellp();
|
||||
StreamWriteHelper h(os);
|
||||
h //
|
||||
// skip the block header
|
||||
.seek(kSharedBlockHeaderSize, std::ios_base::cur)
|
||||
// Note: we cannot know the size beforehand since this length is LEB128 encoded
|
||||
.writeLEB128(blackout_data.size())
|
||||
.write(blackout_data.data(), blackout_data.size());
|
||||
|
||||
const std::streamoff size_of_blackout_block = os.tellp() - begin_of_blackout_block;
|
||||
h //
|
||||
// go back to the beginning of the block
|
||||
.seek(begin_of_blackout_block, std::ios_base::beg)
|
||||
// and write the block header
|
||||
.writeBlockHeader(
|
||||
BlockType::BLACKOUT,
|
||||
static_cast<uint64_t>(size_of_blackout_block - kSharedBlockHeaderSize)
|
||||
);
|
||||
}
|
||||
|
||||
void detail::ValueChangeData::writeInitialBits(std::vector<uint8_t> &os) const {
|
||||
// Build vc_bits_data by concatenating each variable's initial bits as documented.
|
||||
// We will not compress for now; just generate the raw bytes and print summary to stdout.
|
||||
for (size_t i{0}; i < m_variable_infos.size(); ++i) {
|
||||
const VariableInfo &vref = m_variable_infos[i];
|
||||
vref.dumpInitialBits(os);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::vector<uint8_t>> detail::ValueChangeData::computeWaveData() const {
|
||||
const size_t N{m_variable_infos.size()};
|
||||
std::vector<std::vector<uint8_t>> data(N);
|
||||
for (size_t i{0}; i < N; ++i) {
|
||||
m_variable_infos[i].dumpValueChanges(data[i]);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
std::vector<int64_t> detail::ValueChangeData::uniquifyWaveData(
|
||||
std::vector<std::vector<uint8_t>> &data
|
||||
) {
|
||||
// After this function, positions[i] is:
|
||||
// - = 0: If data[i] is unique (first occurrence)
|
||||
// - < 0: If data[i] is a duplicate, encoded as -(original_index + 1)
|
||||
std::vector<int64_t> positions(data.size(), 0);
|
||||
struct MyHash {
|
||||
size_t operator()(const std::vector<uint8_t> *vec) const {
|
||||
size_t seed = 0;
|
||||
for (auto v : *vec) {
|
||||
seed ^= v + 0x9e3779b9 + (seed << 6) + (seed >> 2);
|
||||
}
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
struct MyEqual {
|
||||
bool operator()(const std::vector<uint8_t> *a, const std::vector<uint8_t> *b) const {
|
||||
return *a == *b;
|
||||
}
|
||||
};
|
||||
std::unordered_map<const std::vector<uint8_t> *, int64_t, MyHash, MyEqual> data_map;
|
||||
for (size_t i = 0; i < data.size(); ++i) {
|
||||
if (data[i].empty()) {
|
||||
continue;
|
||||
}
|
||||
// insert vec->i to data_map if not exists
|
||||
auto p = data_map.emplace(&data[i], static_cast<int64_t>(i));
|
||||
auto it = p.first;
|
||||
const bool inserted{p.second};
|
||||
|
||||
if (!inserted) {
|
||||
// duplicated wave data found
|
||||
positions[i] = -(it->second + 1);
|
||||
// clear data to save memory
|
||||
data[i].clear();
|
||||
}
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
|
||||
uint64_t detail::ValueChangeData::encodePositionsAndwriteUniqueWaveData(
|
||||
std::ostream &os,
|
||||
const std::vector<std::vector<uint8_t>> &data,
|
||||
std::vector<int64_t> &positions,
|
||||
WriterPackType pack_type
|
||||
) {
|
||||
// After this function, positions[i] is:
|
||||
// - = 0: If variable i has no wave data
|
||||
// - < 0: The negative value from flushValueChangeData_ValueChanges_UniquifyWaveData_,
|
||||
// unchanged
|
||||
// - > 0: The size (in bytes) of the wave data block for *previous* variable,
|
||||
// the previous block size of the first block is 1 (required by FST spec).
|
||||
StreamWriteHelper h(os);
|
||||
int64_t previous_size = 1;
|
||||
uint64_t written_count = 0;
|
||||
std::vector<uint8_t> compressed_data;
|
||||
for (size_t i = 0; i < positions.size(); ++i) {
|
||||
if (positions[i] < 0) {
|
||||
// duplicate (negative index), do nothing
|
||||
} else if (data[i].empty()) {
|
||||
// no change (empty data), positions[i] remains 0
|
||||
} else {
|
||||
// try to compress
|
||||
const uint8_t *selected_data;
|
||||
size_t selected_size;
|
||||
if (pack_type == WriterPackType::NO_COMPRESSION || data[i].size() <= 32) {
|
||||
selected_data = data[i].data();
|
||||
selected_size = data[i].size();
|
||||
} else {
|
||||
compressUsingLz4(data[i], compressed_data);
|
||||
const std::pair<const uint8_t *, size_t> selected_pair =
|
||||
selectSmaller(compressed_data, data[i]);
|
||||
selected_data = selected_pair.first;
|
||||
selected_size = selected_pair.second;
|
||||
}
|
||||
const bool is_compressed = selected_data != data[i].data();
|
||||
|
||||
// non-empty unique data, write it
|
||||
written_count++;
|
||||
std::streamoff bytes_written;
|
||||
h //
|
||||
.beginOffset(bytes_written)
|
||||
// FST spec: 0 means no compression, >0 for the size of the original data
|
||||
.writeLEB128(is_compressed ? data[i].size() : 0)
|
||||
.write(selected_data, selected_size)
|
||||
.endOffset(&bytes_written);
|
||||
positions[i] = previous_size;
|
||||
previous_size = bytes_written;
|
||||
}
|
||||
}
|
||||
return written_count;
|
||||
}
|
||||
|
||||
void detail::ValueChangeData::writeEncodedPositions(
|
||||
const std::vector<int64_t> &encoded_positions, std::ostream &os
|
||||
) {
|
||||
// Encode positions with the specified run/varint rules into a varint buffer.
|
||||
StreamWriteHelper h(os);
|
||||
|
||||
size_t i = 0;
|
||||
const size_t n = encoded_positions.size();
|
||||
|
||||
// arbitrary positive value for prev_negative
|
||||
// so that first negative is always != prev_negative
|
||||
int64_t prev_negative = 1;
|
||||
|
||||
// Please refer to the comments in
|
||||
// flushValueChangeData_ValueChanges_EncodePositionsAndwriteWaveData_() for the encoding rules
|
||||
// of positions.
|
||||
while (i < n) {
|
||||
if (encoded_positions[i] == 0) {
|
||||
// zero: handle zero run-length
|
||||
size_t run = 0;
|
||||
while (i < n && encoded_positions[i] == 0) {
|
||||
++run;
|
||||
++i;
|
||||
}
|
||||
// encode as signed (run << 1) | 0 and write as signed LEB128
|
||||
h.writeLEB128(run << 1);
|
||||
} else {
|
||||
// non-zero
|
||||
int64_t value_to_encode = 0;
|
||||
int64_t cur = encoded_positions[i];
|
||||
if (cur < 0) {
|
||||
if (cur == prev_negative) {
|
||||
value_to_encode = 0;
|
||||
} else {
|
||||
value_to_encode = cur;
|
||||
prev_negative = cur;
|
||||
}
|
||||
} else {
|
||||
value_to_encode = cur;
|
||||
}
|
||||
|
||||
// encode as signed (value << 1) | 1 and write as signed LEB128
|
||||
h.writeLEB128Signed((value_to_encode << 1) | 1);
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void detail::ValueChangeData::writeTimestamps(std::vector<uint8_t> &os) const {
|
||||
// Build LEB128-encoded delta stream (first delta is timestamp[0] - 0)
|
||||
StreamVectorWriteHelper h(os);
|
||||
uint64_t prev{0};
|
||||
for (size_t i{0}; i < m_timestamps.size(); ++i) {
|
||||
const uint64_t cur{m_timestamps[i]};
|
||||
const uint64_t delta{cur - prev};
|
||||
h.writeLEB128(delta);
|
||||
prev = cur;
|
||||
}
|
||||
}
|
||||
|
||||
void Writer::flushValueChangeDataConstPart_(
|
||||
const detail::ValueChangeData &vcd, std::ostream &os, WriterPackType pack_type
|
||||
) {
|
||||
// 0. setup
|
||||
StreamWriteHelper h(os);
|
||||
|
||||
// 1. write Block Header & Global Fields (start/end/mem_req placeholder)
|
||||
// FST_BL_VCDATA_DYN_ALIAS2 (8) maps to WaveDataVersion3 in fst_file.h
|
||||
// The positions we cannot fill in yet
|
||||
const auto p_tmp1 = [&]() {
|
||||
std::streamoff start_pos, memory_usage_pos;
|
||||
h //
|
||||
.beginOffset(start_pos) // record start position
|
||||
.writeBlockHeader(BlockType::WAVE_DATA_VERSION3, 0 /* Length placeholder 0 */)
|
||||
.writeUInt(vcd.m_timestamps.front())
|
||||
.writeUInt(vcd.m_timestamps.back())
|
||||
.beginOffset(memory_usage_pos) // record memory usage position
|
||||
.writeUInt<uint64_t>(0); // placeholder for memory usage
|
||||
return std::make_pair(start_pos, memory_usage_pos);
|
||||
}();
|
||||
const std::streamoff start_pos{p_tmp1.first};
|
||||
const std::streamoff memory_usage_pos{p_tmp1.second};
|
||||
|
||||
// 2. Bits Section
|
||||
{
|
||||
std::vector<uint8_t> bits_data;
|
||||
vcd.writeInitialBits(bits_data);
|
||||
std::vector<uint8_t> bits_data_compressed;
|
||||
const uint8_t *selected_data;
|
||||
size_t selected_size;
|
||||
if (pack_type == WriterPackType::NO_COMPRESSION || bits_data.size() < 32) {
|
||||
selected_data = bits_data.data();
|
||||
selected_size = bits_data.size();
|
||||
} else {
|
||||
compressUsingZlib(bits_data, bits_data_compressed, 4);
|
||||
const std::pair<const uint8_t *, size_t> selected_pair =
|
||||
selectSmaller(bits_data_compressed, bits_data);
|
||||
selected_data = selected_pair.first;
|
||||
selected_size = selected_pair.second;
|
||||
}
|
||||
|
||||
h //
|
||||
.writeLEB128(bits_data.size()) // uncompressed length
|
||||
.writeLEB128(selected_size) // compressed length
|
||||
.writeLEB128(vcd.m_variable_infos.size()) // bits count
|
||||
.write(selected_data, selected_size);
|
||||
}
|
||||
|
||||
// 3. Waves Section
|
||||
// Note: We need positions for the next section
|
||||
const auto p_tmp2 = [&, pack_type]() {
|
||||
std::vector<std::vector<uint8_t>> wave_data{vcd.computeWaveData()};
|
||||
const size_t memory_usage{std::accumulate(
|
||||
wave_data.begin(),
|
||||
wave_data.end(),
|
||||
size_t(0),
|
||||
[](size_t a, const std::vector<uint8_t> &b) { return a + b.size(); }
|
||||
)};
|
||||
std::vector<int64_t> positions{vcd.uniquifyWaveData(wave_data)};
|
||||
h
|
||||
// Note: this is not a typo, I expect we shall write count here.
|
||||
// but the spec indeed write vcd.variable_infos.size(),
|
||||
// which is repeated 1 times in header block, 2 times in valuechange block
|
||||
.writeLEB128(vcd.m_variable_infos.size())
|
||||
.writeUInt(uint8_t('4'));
|
||||
const uint64_t count{detail::ValueChangeData::encodePositionsAndwriteUniqueWaveData(
|
||||
os, wave_data, positions, pack_type
|
||||
)};
|
||||
(void)count;
|
||||
return std::make_pair(positions, memory_usage);
|
||||
}();
|
||||
const std::vector<int64_t> positions{p_tmp2.first};
|
||||
const size_t memory_usage{p_tmp2.second};
|
||||
|
||||
// 4. Position Section
|
||||
{
|
||||
const std::streampos pos_begin{os.tellp()};
|
||||
vcd.writeEncodedPositions(positions, os);
|
||||
const uint64_t pos_size{static_cast<uint64_t>(os.tellp() - pos_begin)};
|
||||
h.writeUInt(pos_size); // Length comes AFTER data for positions
|
||||
}
|
||||
|
||||
// 5. Time Section
|
||||
{
|
||||
std::vector<uint8_t> time_data;
|
||||
vcd.writeTimestamps(time_data);
|
||||
std::vector<uint8_t> time_data_compressed;
|
||||
const uint8_t *selected_data;
|
||||
size_t selected_size;
|
||||
if (pack_type == WriterPackType::NO_COMPRESSION) {
|
||||
selected_data = time_data.data();
|
||||
selected_size = time_data.size();
|
||||
} else {
|
||||
compressUsingZlib(time_data, time_data_compressed, 9);
|
||||
const std::pair<const uint8_t *, size_t> selected_pair =
|
||||
selectSmaller(time_data_compressed, time_data);
|
||||
selected_data = selected_pair.first;
|
||||
selected_size = selected_pair.second;
|
||||
}
|
||||
h //
|
||||
.write(selected_data, selected_size) // time data
|
||||
.writeUInt(time_data.size()) // uncompressed len
|
||||
.writeUInt(selected_size) // compressed len
|
||||
.writeUInt(uint64_t(vcd.m_timestamps.size())); // count
|
||||
}
|
||||
|
||||
// 6. Patch Block Length and Memory Required
|
||||
std::streamoff end_pos{0};
|
||||
h //
|
||||
.beginOffset(end_pos)
|
||||
// Patch Block Length (after 1 byte Type)
|
||||
.seek(start_pos + std::streamoff(1), std::ios_base::beg)
|
||||
.writeUInt<uint64_t>(static_cast<uint64_t>(end_pos - start_pos - 1))
|
||||
// Patch Memory Required
|
||||
.seek(memory_usage_pos, std::ios_base::beg)
|
||||
.writeUInt<uint64_t>(static_cast<uint64_t>(memory_usage))
|
||||
// Restore position to end
|
||||
.seek(end_pos, std::ios_base::beg);
|
||||
}
|
||||
|
||||
namespace { // Helper functions for createEnumTable
|
||||
|
||||
void appendEscToString(const string_view_pair in, std::string &out) {
|
||||
for (size_t i{0}; i < in.m_size; ++i) {
|
||||
const char c{in.m_data[i]};
|
||||
switch (c) {
|
||||
// clang-format off
|
||||
case '\a': { out += "\\a"; break; }
|
||||
case '\b': { out += "\\b"; break; }
|
||||
case '\f': { out += "\\f"; break; }
|
||||
case '\n': { out += "\\n"; break; }
|
||||
case '\r': { out += "\\r"; break; }
|
||||
case '\t': { out += "\\t"; break; }
|
||||
case '\v': { out += "\\v"; break; }
|
||||
case '\'': { out += "\\'"; break; }
|
||||
case '\"': { out += "\\\""; break; }
|
||||
case '\\': { out += "\\\\"; break; }
|
||||
case '?': { out += "\\?"; break; }
|
||||
// clang-format on
|
||||
default: {
|
||||
if (c > ' ' && c <= '~') {
|
||||
out += c;
|
||||
} else {
|
||||
unsigned char val = static_cast<unsigned char>(c);
|
||||
out += '\\';
|
||||
out += (val / 64) + '0';
|
||||
val &= 63;
|
||||
out += (val / 8) + '0';
|
||||
val &= 7;
|
||||
out += val + '0';
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Writer::setAttrBegin(
|
||||
Hierarchy::AttrType attrtype,
|
||||
Hierarchy::AttrSubType subtype,
|
||||
const string_view_pair attrname,
|
||||
uint64_t arg
|
||||
) {
|
||||
FST_CHECK(!m_hierarchy_finalized_);
|
||||
|
||||
StreamVectorWriteHelper h(m_hierarchy_buffer_);
|
||||
|
||||
if (attrtype > Hierarchy::AttrType::MAX) {
|
||||
attrtype = Hierarchy::AttrType::MISC;
|
||||
subtype = Hierarchy::AttrSubType::MISC_UNKNOWN;
|
||||
}
|
||||
|
||||
switch (attrtype) {
|
||||
// clang-format off
|
||||
case Hierarchy::AttrType::ARRAY:
|
||||
if (
|
||||
subtype < Hierarchy::AttrSubType::ARRAY_NONE ||
|
||||
subtype > Hierarchy::AttrSubType::ARRAY_SPARSE
|
||||
) {
|
||||
subtype = Hierarchy::AttrSubType::ARRAY_NONE;
|
||||
}
|
||||
break;
|
||||
case Hierarchy::AttrType::ENUM:
|
||||
if (
|
||||
subtype < Hierarchy::AttrSubType::ENUM_SV_INTEGER ||
|
||||
subtype > Hierarchy::AttrSubType::ENUM_TIME
|
||||
) {
|
||||
subtype = Hierarchy::AttrSubType::ENUM_SV_INTEGER;
|
||||
}
|
||||
break;
|
||||
case Hierarchy::AttrType::PACK:
|
||||
if (
|
||||
subtype < Hierarchy::AttrSubType::PACK_NONE ||
|
||||
subtype > Hierarchy::AttrSubType::PACK_SPARSE
|
||||
) {
|
||||
subtype = Hierarchy::AttrSubType::PACK_NONE;
|
||||
}
|
||||
break;
|
||||
// clang-format on
|
||||
case Hierarchy::AttrType::MISC:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
h //
|
||||
.writeU8Enum(Hierarchy::ScopeControlType::GEN_ATTR_BEGIN)
|
||||
.writeU8Enum(attrtype)
|
||||
.writeU8Enum(subtype)
|
||||
.writeString0(attrname)
|
||||
.writeLEB128(arg);
|
||||
}
|
||||
|
||||
EnumHandle Writer::createEnumTable(
|
||||
const string_view_pair name,
|
||||
uint32_t min_valbits,
|
||||
const std::vector<std::pair<string_view_pair, string_view_pair>> &literal_val_arr
|
||||
) {
|
||||
EnumHandle handle{0};
|
||||
|
||||
if (name.m_size == 0 || literal_val_arr.empty()) {
|
||||
return handle;
|
||||
}
|
||||
|
||||
std::string attr_str;
|
||||
attr_str.reserve(256);
|
||||
attr_str.append(name.m_data, name.m_size);
|
||||
attr_str += ' ';
|
||||
attr_str += std::to_string(literal_val_arr.size());
|
||||
attr_str += ' ';
|
||||
|
||||
for (const auto &p : literal_val_arr) {
|
||||
const string_view_pair literal{p.first};
|
||||
// literal
|
||||
appendEscToString(literal, attr_str);
|
||||
attr_str += ' ';
|
||||
}
|
||||
for (const auto &p : literal_val_arr) {
|
||||
const string_view_pair val{p.second};
|
||||
// val (with padding)
|
||||
if (min_valbits > 0 && val.m_size < min_valbits) {
|
||||
attr_str.insert(attr_str.end(), min_valbits - val.m_size, '0');
|
||||
}
|
||||
appendEscToString(val, attr_str);
|
||||
attr_str += ' ';
|
||||
}
|
||||
attr_str.pop_back(); // remove last space
|
||||
|
||||
handle = ++m_enum_count_;
|
||||
setAttrBegin(
|
||||
Hierarchy::AttrType::MISC,
|
||||
Hierarchy::AttrSubType::MISC_ENUMTABLE,
|
||||
make_string_view_pair(attr_str.c_str(), attr_str.size()),
|
||||
handle
|
||||
);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
} // namespace fst
|
||||
@@ -0,0 +1,269 @@
|
||||
// SPDX-FileCopyrightText: 2025-2026 Yu-Sheng Lin <[email protected]>
|
||||
// SPDX-FileCopyrightText: 2025-2026 Yoda Lee <[email protected]>
|
||||
// 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 <algorithm>
|
||||
#include <cstdint>
|
||||
#include <ctime>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#if __cplusplus >= 201703L
|
||||
# include <string_view>
|
||||
#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 BlackoutData here for better code inlining, no forward declaration
|
||||
// Blackout is not implemented yet
|
||||
struct BlackoutData {
|
||||
std::vector<uint8_t> m_buffer{};
|
||||
uint64_t m_previous_timestamp{0};
|
||||
uint64_t m_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<VariableInfo> m_variable_infos{};
|
||||
std::vector<uint64_t> m_timestamps{};
|
||||
|
||||
ValueChangeData();
|
||||
~ValueChangeData();
|
||||
|
||||
void writeInitialBits(std::vector<uint8_t> &os) const;
|
||||
std::vector<std::vector<uint8_t>> computeWaveData() const;
|
||||
static std::vector<int64_t> uniquifyWaveData(std::vector<std::vector<uint8_t>> &data);
|
||||
static uint64_t encodePositionsAndwriteUniqueWaveData(
|
||||
std::ostream &os,
|
||||
const std::vector<std::vector<uint8_t>> &unique_data,
|
||||
std::vector<int64_t> &positions,
|
||||
WriterPackType pack_type
|
||||
);
|
||||
static void writeEncodedPositions(
|
||||
const std::vector<int64_t> &encoded_positions, std::ostream &os
|
||||
);
|
||||
void writeTimestamps(std::vector<uint8_t> &os) const;
|
||||
void keepOnlyTheLatestValue();
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
class Writer {
|
||||
friend class WriterTest;
|
||||
|
||||
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
|
||||
// 4. For blackout data, it is not implemented yet
|
||||
std::ofstream m_main_fst_file_{};
|
||||
std::vector<uint8_t> m_hierarchy_buffer_{};
|
||||
std::vector<uint8_t> m_geometry_buffer_{};
|
||||
// Temporary buffer for packing bit strings into words
|
||||
// Only used in emitValueChange(Handle, const char*)
|
||||
std::vector<uint64_t> m_packed_value_buffer_{};
|
||||
Header m_header_{};
|
||||
detail::BlackoutData m_blackout_data_{}; // Not implemented yet
|
||||
detail::ValueChangeData m_value_change_data_{};
|
||||
bool m_hierarchy_finalized_{false};
|
||||
WriterPackType m_pack_type_{WriterPackType::LZ4};
|
||||
uint64_t m_value_change_data_usage_{0}; // Note: this value is just an estimation
|
||||
uint64_t m_value_change_data_flush_threshold_{128 << 20}; // 128MB
|
||||
uint32_t m_enum_count_{0};
|
||||
bool m_flush_pending_{false};
|
||||
|
||||
public:
|
||||
Writer() {}
|
||||
Writer(const string_view_pair name) {
|
||||
if (name.m_size != 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 { return m_header_; }
|
||||
void setTimecale(int8_t timescale) { m_header_.m_timescale = timescale; }
|
||||
void setWriter(const string_view_pair writer) {
|
||||
const size_t len = std::min(writer.m_size, sizeof(m_header_.m_writer));
|
||||
std::copy_n(writer.m_data, len, m_header_.m_writer);
|
||||
if (len != sizeof(m_header_.m_writer)) {
|
||||
m_header_.m_writer[len] = '\0';
|
||||
}
|
||||
}
|
||||
void setDate(const string_view_pair date_str) {
|
||||
const size_t len = date_str.m_size;
|
||||
FST_CHECK_EQ(len, sizeof(m_header_.m_date) - 1);
|
||||
std::copy_n(date_str.m_data, len, m_header_.m_date);
|
||||
m_header_.m_date[len] = '\0';
|
||||
}
|
||||
void setDate(const std::tm *d) { setDate(make_string_view_pair(std::asctime(d))); }
|
||||
void setDate() {
|
||||
// set date to now
|
||||
std::time_t t{std::time(nullptr)};
|
||||
setDate(std::localtime(&t));
|
||||
}
|
||||
void setTimezero(int64_t timezero) { m_header_.m_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
|
||||
);
|
||||
void setAttrEnd() {
|
||||
m_hierarchy_buffer_.push_back(
|
||||
static_cast<uint8_t>(Hierarchy::ScopeControlType::GEN_ATTR_END)
|
||||
);
|
||||
}
|
||||
EnumHandle createEnumTable(
|
||||
const string_view_pair name,
|
||||
uint32_t min_valbits,
|
||||
const std::vector<std::pair<string_view_pair, string_view_pair>> &literal_val_arr
|
||||
);
|
||||
template <typename T1, typename T2>
|
||||
EnumHandle createEnumTable(
|
||||
const char *name,
|
||||
uint32_t min_valbits,
|
||||
const std::vector<std::pair<T1, T2>> &literal_val_arr
|
||||
) {
|
||||
std::vector<std::pair<string_view_pair, string_view_pair>> 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);
|
||||
}
|
||||
void emitEnumTableRef(EnumHandle handle) {
|
||||
setAttrBegin(
|
||||
Hierarchy::AttrType::MISC,
|
||||
Hierarchy::AttrSubType::MISC_ENUMTABLE,
|
||||
make_string_view_pair(nullptr, 0),
|
||||
handle
|
||||
);
|
||||
}
|
||||
void setWriterPackType(WriterPackType pack_type) {
|
||||
FST_CHECK(pack_type != WriterPackType::ZLIB && pack_type != WriterPackType::FASTLZ);
|
||||
m_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
|
||||
);
|
||||
// TODO
|
||||
// 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);
|
||||
// TODO
|
||||
// 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);
|
||||
|
||||
// Flush value change data
|
||||
void flushValueChangeData() { m_flush_pending_ = true; }
|
||||
|
||||
private:
|
||||
// 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); // Not implemented yet
|
||||
// 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
|
||||
);
|
||||
void flushValueChangeData_(detail::ValueChangeData &vcd, std::ostream &os) {
|
||||
if (vcd.m_timestamps.empty()) {
|
||||
return;
|
||||
}
|
||||
flushValueChangeDataConstPart_(vcd, os, m_pack_type_);
|
||||
vcd.keepOnlyTheLatestValue();
|
||||
++m_header_.m_num_value_change_data_blocks;
|
||||
m_value_change_data_usage_ = 0;
|
||||
m_flush_pending_ = false;
|
||||
}
|
||||
void finalizeHierarchy_() {
|
||||
if (m_hierarchy_finalized_) return;
|
||||
m_hierarchy_finalized_ = true;
|
||||
// Original FST code comments: as a default, use 128MB and increment when
|
||||
// every 1M signals are defined.
|
||||
m_value_change_data_flush_threshold_ = (((m_header_.m_num_handles - 1) >> 20) + 1) << 27;
|
||||
}
|
||||
template <typename... T>
|
||||
void emitValueChangeHelper_(Handle handle, T &&...val);
|
||||
};
|
||||
|
||||
} // namespace fst
|
||||
Reference in New Issue
Block a user