821 lines
26 KiB
C++
821 lines
26 KiB
C++
// SPDX-FileCopyrightText: 2025-2026 Yu-Sheng Lin <johnjohnlys@gmail.com>
|
|
// SPDX-FileCopyrightText: 2025-2026 Yoda Lee <lc85301@gmail.com>
|
|
// SPDX-License-Identifier: MIT
|
|
// Project: libfstwriter
|
|
// Website: https://github.com/gtkwave/libfstwriter
|
|
#pragma once
|
|
// direct include
|
|
#include "fstcpp/fstcpp.h"
|
|
// C system headers
|
|
#ifdef _MSC_VER
|
|
# include <intrin.h>
|
|
#endif
|
|
// C++ standard library headers
|
|
#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 defined(__GNUC__) || defined(__clang__)
|
|
return 64 - __builtin_clzll(x - 1);
|
|
#elif defined(_MSC_VER) // MSVC
|
|
if (x <= 1) return 0;
|
|
unsigned long index;
|
|
_BitScanReverse64(&index, x - 1);
|
|
return static_cast<uint64_t>(index + 1);
|
|
#else
|
|
uint64_t r = 0;
|
|
while (x > 1) {
|
|
x >>= 1;
|
|
r++;
|
|
}
|
|
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
|