From b9acc83d48c5c99479f1268ebb90857427d5767c Mon Sep 17 00:00:00 2001 From: johnjohnlin Date: Mon, 14 Sep 2026 01:19:26 +0800 Subject: [PATCH 1/2] Support long xz format --- .github/workflows/regression.yml | 11 +- README.md | 16 +- fstcpp/fstcpp.h | 2 +- fstcpp/fstcpp_variable_info.h | 90 ++++------- fstcpp/fstcpp_variable_info.test.cpp | 90 ++++++++--- fstcpp/fstcpp_writer.cpp | 95 +++++++----- integration_test/tests/xz_test/Makefile | 1 + .../tests/xz_test/xz_test.test.cpp | 143 ++++++++++++++++++ 8 files changed, 317 insertions(+), 131 deletions(-) create mode 100644 integration_test/tests/xz_test/Makefile create mode 100644 integration_test/tests/xz_test/xz_test.test.cpp diff --git a/.github/workflows/regression.yml b/.github/workflows/regression.yml index 4629634..1d59762 100644 --- a/.github/workflows/regression.yml +++ b/.github/workflows/regression.yml @@ -12,6 +12,8 @@ jobs: regression: name: Build and Run Regression runs-on: ubuntu-latest + container: + image: verilator/verilator:v5.050 steps: - name: Checkout repository @@ -19,13 +21,8 @@ jobs: - name: Install dependencies run: | - sudo apt-get update - sudo apt-get install -y libgtest-dev lcov liblz4-dev - - - name: Setup Verilator - uses: veryl-lang/setup-verilator@v1 - with: - version: '5.050' + apt-get update + apt-get install -y cmake ninja-build libgtest-dev lcov liblz4-dev patch - name: Run tests run: ./run_regression.sh --coverage diff --git a/README.md b/README.md index f4918d0..1062a3d 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,8 @@ To view it: - `fstapi.c`: REFERENCE - the original GtkWave implementation. - `fstapi.h`: The original GtkWave header file. - `*.patch`: Patches for Verilator runtime. - - other files: Copied from Verilator 5.044 runtime (5.042 should also work) by `setup_runtime.sh`. - - other folders: individual integration tests. + - other files: Copied from Verilator 5.050 runtime by `setup_runtime.sh`. + - other folders: individual integration tests. Each test builds two binaries—one against the original `fstapi.c` and one against `fstcpp`—to compare and ensure bit-true outputs. --- @@ -45,7 +45,7 @@ To view it: * C++20 compatible compiler (GCC/Clang) * CMake (3.10 or higher) * Ninja (optional, recommended for speed) -* Verilator (5.044 is tested, but a bit older version might work too) +* Verilator (5.050 is tested; official Docker image `verilator/verilator:v5.050` is used in CI) ### Setup for Development @@ -75,7 +75,13 @@ ctest -j$(nproc) ### Testing and Verification -To run the full regression suite (build, test, and coverage) just like the CI server. The regression script automatically calls `setup_runtime.sh`: +Integration tests compile each test suite into two executables: +- One linked against the reference GtkWave C implementation (`fstapi.c`). +- One linked against `fstcpp` via the `fstapi.cpp` wrapper. + +CTest runs both binaries to produce separate FST waveform dumps and verifies with `compare_files` that both files are bit-identical (bit-true). + +To run the full regression suite (build, test, and coverage) just like the CI server (which uses the official `verilator/verilator:v5.050` Docker image): ```bash ./run_regression.sh --coverage @@ -109,7 +115,7 @@ The root directory of this project contains a mix of original code and redistrib * **`integration_test/`**: Without explicitly mentioned, all SystemVerilog files and the testbench CPP files are part of this project. * **`verilator_share/gtkwave/`**: Redistributed from `libfst`, which is MIT license. * **`verilator_share/gtkwave/fstapi.cpp`**: A compatibility layer wrapping this project into `libfst`'s original C-interface. - * **`*/verilated/`**: Generated by Verilator 5.044 (5.042 also supported). They are derivative works and inherit the original MIT license of the SystemVerilog source. + * **`*/verilated/`**: Generated by Verilator 5.050 (5.042-5.044 also supported). They are derivative works and inherit the original MIT license of the SystemVerilog source. * **LGPL v3** * **`integration_test/verilator_share/`**: Copied from Verilator runtime by `setup_runtime.sh` (patched for bit-true). * Projects that we have cited: diff --git a/fstcpp/fstcpp.h b/fstcpp/fstcpp.h index d8d5a9b..e17b337 100644 --- a/fstcpp/fstcpp.h +++ b/fstcpp/fstcpp.h @@ -90,7 +90,7 @@ static inline constexpr unsigned bitPerEncodedBit(EncodingType type) { [[maybe_unused]] static const char* kEncodedBitToCharTable = ( "01" // Binary - "zxhu" // Verilog + "xzhu" // Verilog "wl-? " // Vhdl (padded with ' ') ); diff --git a/fstcpp/fstcpp_variable_info.h b/fstcpp/fstcpp_variable_info.h index c5b4a4f..53167f4 100644 --- a/fstcpp/fstcpp_variable_info.h +++ b/fstcpp/fstcpp_variable_info.h @@ -533,8 +533,8 @@ class VariableInfoScalarInt { // 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) | (1<<1) | 1; break; // 'Z' - case 3: delta_time_index = (delta_time_index<<4) | (0<<1) | 1; break; // 'X' + 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' @@ -603,20 +603,11 @@ class VariableInfoLongInt { VariableInfoLongInt(VariableInfo &info_) : info(info_) {} public: - size_t computeBytesNeededNoHeader(EncodingType encoding) const { - switch (encoding) { - case EncodingType::BINARY: - return num_words() * sizeof(uint64_t); - case EncodingType::VERILOG: - return num_words32() * sizeof(uint32_t) * 2; - [[unlikely]] case EncodingType::VHDL: - FST_FAIL_STRING("VHDL format is unsupported with wide values"); - } - FST_UNREACHABLE; - } - size_t computeBytesNeeded(EncodingType encoding) const { - return kEmitTimeIndexAndEncodingSize + computeBytesNeededNoHeader(encoding); + return ( + kEmitTimeIndexAndEncodingSize + + num_words() * sizeof(uint64_t) * bitPerEncodedBit(encoding) + ); } EmitWriterHelper emitValueChangeCommonPart(uint64_t current_time_index, EncodingType encoding) { @@ -634,12 +625,13 @@ class VariableInfoLongInt { public: void construct() { - const size_t nw = num_words32(); + const size_t nw = num_words(); info.resize(computeBytesNeeded(EncodingType::VERILOG)); EmitWriterHelper wh(info.data_ptr()); wh // .writeTimeIndexAndEncoding(0, EncodingType::VERILOG) - .fill(static_cast(std::numeric_limits::max()) << 32, nw); + .fill(uint64_t(0), nw) + .fill(uint64_t(-1), nw); } void emitValueChange(uint64_t current_time_index, const uint64_t val) { @@ -650,11 +642,11 @@ class VariableInfoLongInt { void emitValueChange(uint64_t current_time_index, const uint32_t *val, EncodingType encoding) { const unsigned nw32 = num_words32(); + const unsigned bpb = bitPerEncodedBit(encoding); auto wh = emitValueChangeCommonPart(current_time_index, encoding); - switch (encoding) { - case EncodingType::BINARY: { + for (unsigned i = 0; i < bpb; ++i) { for (unsigned j = 0; j < nw32 / 2; ++j) { uint64_t v = val[1]; // high bits v <<= 32; @@ -667,25 +659,13 @@ class VariableInfoLongInt { wh.write(v); val += 1; } - } break; - case EncodingType::VERILOG: { - for (unsigned j = 0; j < nw32; ++j) { - uint64_t v = val[1]; // high bits - v <<= 32; - v |= val[0]; // low bits - wh.write(v); - val += 2; - } - } break; - [[unlikely]] case EncodingType::VHDL: - FST_FAIL_STRING("VHDL format is unsupported with wide values"); } } 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); - FST_CHECK(encoding == EncodingType::BINARY); - wh.write(val, num_words()); + wh.write(val, nw_encoded); } void dumpInitialBits(std::vector &buf) const { @@ -708,16 +688,15 @@ class VariableInfoLongInt { break; } case EncodingType::VERILOG: { - for (unsigned word_index = num_words32(); word_index-- > 0;) { - const uint64_t val = rh.peek(word_index); - const uint32_t aval = static_cast(val); - const uint32_t bval = static_cast(val >> 32); + for (unsigned word_index = nw; word_index-- > 0;) { + const uint64_t v0 = rh.peek(nw * 0 + word_index); + const uint64_t v1 = rh.peek(nw * 1 + word_index); const unsigned num_bit = - (word_index * 32 + 32 > info.bitwidth()) ? (info.bitwidth() % 32) : 32; + (word_index * 64 + 64 > info.bitwidth()) ? (info.bitwidth() % 64) : 64; for (unsigned bit_index = num_bit; bit_index-- > 0;) { - const bool a = ((aval >> bit_index) & 1); - const bool b = ((bval >> bit_index) & 1); - const char c = kEncodedBitToCharTable[(b << 1) | a]; + 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); } } @@ -763,7 +742,8 @@ class VariableInfoLongInt { FST_DCHECK_GT(tail, rh.ptr); const auto time_index = rh.read(); const auto enc = rh.read(); - const auto num_byte = computeBytesNeededNoHeader(enc); + 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; @@ -793,22 +773,16 @@ class VariableInfoLongInt { } break; case EncodingType::VERILOG: { h.writeLEB128((delta_time_index << 1) | 1); - const int fullWords = (bitwidth / 32); - if (int j = bitwidth % 32) { - const uint64_t val = rh.peek(fullWords); - while (j > 0) { - --j; - const uint64_t v = val >> j; - h.writeUIntBE(kEncodedBitToCharTable[((v >> 31) & 2) | (v & 1)]); - } - } - for (size_t i = fullWords; i > 0;) { - --i; - const uint64_t val = rh.peek(i); - for (int j = 32; j > 0;) { - --j; - const uint64_t v = val >> j; - h.writeUIntBE(kEncodedBitToCharTable[((v >> 31) & 2) | (v & 1)]); + for (unsigned word_index = nw; word_index-- > 0;) { + const uint64_t v0 = rh.peek(nw * 0 + word_index); + const uint64_t v1 = rh.peek(nw * 1 + word_index); + const unsigned num_bit = + (word_index * 64 + 64 > bitwidth) ? (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]; + h.write(c); } } } break; diff --git a/fstcpp/fstcpp_variable_info.test.cpp b/fstcpp/fstcpp_variable_info.test.cpp index 3fdab29..6c60665 100644 --- a/fstcpp/fstcpp_variable_info.test.cpp +++ b/fstcpp/fstcpp_variable_info.test.cpp @@ -63,7 +63,7 @@ TEST(VariableInfoTest, writeInitialVerilog_ScalarInt) { vi.keepOnlyTheLatestValue(); vector buf{}; vi.dumpInitialBits(buf); - EXPECT_EQ(V2S(buf), "10xz"); + EXPECT_EQ(V2S(buf), "10zx"); } TEST(VariableInfoTest, writeInitialVerilog_ScalarInt64) { @@ -73,17 +73,17 @@ TEST(VariableInfoTest, writeInitialVerilog_ScalarInt64) { vi.keepOnlyTheLatestValue(); vector buf{}; vi.dumpInitialBits(buf); - EXPECT_EQ(V2S(buf), std::string(60, '0') + "10xz"); + EXPECT_EQ(V2S(buf), std::string(60, '0') + "10zx"); } TEST(VariableInfoTest, writeInitialVerilog_LongInt) { VariableInfo vi{70}; - const uint32_t val[] = {0, 0, 0b1010, 0b0011, 0, 0}; + const uint32_t val[] = {0, 0b1010, 0, 0, 0b0011, 0}; vi.emitValueChange(0, val, fst::EncodingType::VERILOG); vi.keepOnlyTheLatestValue(); vector buf{}; vi.dumpInitialBits(buf); - const string expected{string(34, '0') + "10xz" + string(32, '0')}; + const string expected{string(34, '0') + "10zx" + string(32, '0')}; EXPECT_EQ(V2S(buf), expected); } ///////////////////////////// @@ -167,10 +167,10 @@ TEST(VariableInfoTest, dumpValueChange_ScalarInt_1bit_Verilog) { // Encoding time_index_delta << 2 | (bit << 1) | 0 in binary mode // (1-0) << 2 | 0b00 // (2-1) << 2 | 0b10 - // (3-2) << 4 | 0b01 - // (4-3) << 4 | 0b11 + // (3-2) << 4 | 0b11 + // (4-3) << 4 | 0b01 // (5-4) << 2 | 0b00 - EXPECT_EQ(V2S(buf), "\x04\x06\x11\x13\x04"s); + EXPECT_EQ(V2S(buf), "\x04\x06\x13\x11\x04"s); } TEST(VariableInfoTest, dumpValueChange_ScalarInt_2bit_Verilog) { @@ -213,11 +213,11 @@ TEST(VariableInfoTest, dumpValueChange_ScalarInt_2bit_Verilog) { vi.dumpValueChanges(buf); // 1. Varint encoding of (Time_index_delta << 1) | 1 // 2. Data encoding as chars - EXPECT_EQ(buf, (std::vector{0x03, '0', '0', 0x05, '0', 'z', 0x05, 'z', 'z', - 0x05, 'z', '0', 0x07, 'z', '1', 0x03, 'z', 'x', - 0x05, '0', 'x', 0x05, '0', '1', 0x05, '1', '1', - 0x07, '1', 'x', 0x03, 'x', 'x', 0x05, 'x', '1', - 0x05, 'x', '0', 0x05, 'x', 'z', 0x07, '1', 'z', + EXPECT_EQ(buf, (std::vector{0x03, '0', '0', 0x05, '0', 'x', 0x05, 'x', 'x', + 0x05, 'x', '0', 0x07, 'x', '1', 0x03, 'x', 'z', + 0x05, '0', 'z', 0x05, '0', '1', 0x05, '1', '1', + 0x07, '1', 'z', 0x03, 'z', 'z', 0x05, 'z', '1', + 0x05, 'z', '0', 0x05, 'z', 'x', 0x07, '1', 'x', 0x03, '1', '0', 0x05, '0', '0'})); } @@ -241,8 +241,8 @@ TEST(VariableInfoTest, dumpValueChange_ScalarInt_10bit_Verilog) { // 2. data encoded as raw bits,aligned with MSB and packed into a whole number of bytes EXPECT_EQ(buf, (std::vector{0x03, '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', 0x05, '0', '0', '0', '0', '0', '0', '0', '0', '0', '1', - 0x05, '0', '0', '0', '0', '0', '0', '0', 'z', '0', 'x', - 0x05, '0', '0', '0', '0', '1', '0', '0', 'x', '0', 'x', + 0x05, '0', '0', '0', '0', '0', '0', '0', 'x', '0', 'z', + 0x05, '0', '0', '0', '0', '1', '0', '0', 'z', '0', 'z', 0x03, '0', '0', '0', '0', '1', '0', '0', '1', '0', '1', 0x05, '0', '0', '0', '0', '0', '0', '0', '0', '0', '0'})); } @@ -255,7 +255,7 @@ TEST(VariableInfoTest, dumpValueChange_ScalarInt_64bit_Verilog) { vi.dumpValueChanges(buf); // 1. Varint encoding of Time_index_delta << 1 | 1 since it contains only 0 and 1 // 2. data encoded as raw bits,aligned with MSB and packed into a whole number of bytes - EXPECT_EQ(V2S(buf), "\x03"s + std::string(61, '0') + "1zx"); + EXPECT_EQ(V2S(buf), "\x03"s + std::string(61, '0') + "1xz"); } TEST(VariableInfoTest, dumpValueChange_ScalarInt_64bit_Verilog2) { @@ -266,27 +266,77 @@ TEST(VariableInfoTest, dumpValueChange_ScalarInt_64bit_Verilog2) { vi.dumpValueChanges(buf); // 1. Varint encoding of Time_index_delta << 1 | 1 since it contains only 0 and 1 // 2. data encoded as raw bits,aligned with MSB and packed into a whole number of bytes - EXPECT_EQ(V2S(buf), "\x03"s + std::string(61, '0') + "1zx"); + EXPECT_EQ(V2S(buf), "\x03"s + std::string(61, '0') + "1xz"); } TEST(VariableInfoTest, dumpValueChange_LongInt_Verilog) { VariableInfo vi{68}; uint32_t val[] = {0, 0, 0, 0, 0, 0}; vi.emitValueChange(2, val, fst::EncodingType::VERILOG); - val[2] = 0b0101; - val[3] = 0b0011; + val[1] = 0b0101; + val[4] = 0b0011; vi.emitValueChange(5, val, fst::EncodingType::VERILOG); vector buf{}; vi.dumpValueChanges(buf); std::vector result(138, '0'); result[0] = 0x05; result[69] = 0x07; - result[105] = 'x'; - result[104] = 'z'; + result[105] = 'z'; + result[104] = 'x'; result[103] = '1'; EXPECT_EQ(buf, result); } +TEST(VariableInfoTest, writeInitialBits_LongInt_Verilog) { + VariableInfo vi{70}; + // clang-format off + uint64_t val70_xz[4] = { + 0x0000000000000005ULL, 0x00ULL, // b0 + 0x0000000000000003ULL, 0x30ULL // b1 + }; + // clang-format on + vi.emitValueChange(0, val70_xz, fst::EncodingType::VERILOG); + vi.keepOnlyTheLatestValue(); + vector buf{}; + vi.dumpInitialBits(buf); + const string expected = "xx" + string(65, '0') + "1xz"; + EXPECT_EQ(V2S(buf), expected); +} + +TEST(VariableInfoTest, dumpValueChange_LongInt_Verilog_uint64_array) { + VariableInfo vi{70}; + // clang-format off + uint64_t val70_xz[4] = { + 0x0000000000000005ULL, 0x00ULL, // b0 + 0x0000000000000003ULL, 0x30ULL // b1 + }; + // clang-format on + vi.emitValueChange(1, val70_xz, fst::EncodingType::VERILOG); + + vector buf{}; + vi.dumpValueChanges(buf); + + const string expected = "\x03"s + "xx" + string(65, '0') + "1xz"; + EXPECT_EQ(V2S(buf), expected); +} + +TEST(VariableInfoTest, dumpValueChange_LongInt_Verilog_uint32_array) { + VariableInfo vi{70}; + // clang-format off + uint32_t val70_u32[6] = { + 0x00000005, 0x00000000, 0x00000000, // b0 + 0x00000003, 0x00000000, 0x00000030 // b1 + }; + // clang-format on + vi.emitValueChange(2, val70_u32, fst::EncodingType::VERILOG); + + vector buf{}; + vi.dumpValueChanges(buf); + + const string expected = "\x05"s + "xx" + string(65, '0') + "1xz"; + EXPECT_EQ(V2S(buf), expected); +} + // LCOV_EXCL_START TEST(VariableInfoTest, DISABLED_dumpValueChange_Double_Binary) { VariableInfo vi(kDontCareBitWidth, true); diff --git a/fstcpp/fstcpp_writer.cpp b/fstcpp/fstcpp_writer.cpp index 4a31c3a..b48fa93 100644 --- a/fstcpp/fstcpp_writer.cpp +++ b/fstcpp/fstcpp_writer.cpp @@ -266,57 +266,72 @@ void Writer::emitValueChange(Handle handle, const char *val) { return; } - // For normal integer handles, const char* is "01xz..." (1B per bit) const uint32_t bitwidth{var_info.bitwidth()}; - const bool hasXZ = // Detects A-Z and a-z but not 0-9 and NOT `-` `?` - (std::accumulate(val, val + bitwidth, 0, [](int a, char b) { return a | b; }) & (1 << 6)) != - 0; FST_DCHECK_NE(bitwidth, 0); + bool has_non_binary = false; + for (uint32_t i = 0; i < bitwidth; ++i) { + char c = val[i]; + if (c == 'x' || c == 'X' || c == 'z' || c == 'Z' || c == 'u' || c == 'h' || c == 'w' || + c == 'l') { + has_non_binary = true; + break; + } + } + val += bitwidth; const unsigned num_words{(bitwidth + 63) / 64}; - m_packed_value_buffer_.assign(num_words << (hasXZ ? 1 : 0), 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 - if (hasXZ) { - const size_t j = i << 1; - m_packed_value_buffer_[j] <<= 1; - m_packed_value_buffer_[j | 1] <<= 1; - switch (*p) { - case '0': - break; - case '1': { - m_packed_value_buffer_[i] |= 1; - } break; - case 'X': - case 'x': { - m_packed_value_buffer_[i] |= 1; - } // FALLTHROUGH - case 'Z': - case 'z': { - m_packed_value_buffer_[j | 1] |= 1; - } break; - [[unlikely]] default: { FST_FAIL_STRING("Unexpected char"); } break; - } - } else { + + if (!has_non_binary) { + 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) { m_packed_value_buffer_[i] <<= 1; m_packed_value_buffer_[i] |= static_cast(*p - '0'); } } - } - if (bitwidth <= 64 && !hasXZ) { - emitValueChange(handle, m_packed_value_buffer_.front()); + if (bitwidth <= 64) { + emitValueChange(handle, m_packed_value_buffer_.front()); + } else { + emitValueChange(handle, m_packed_value_buffer_.data(), EncodingType::BINARY); + } } else { - emitValueChange( - handle, - m_packed_value_buffer_.data(), - hasXZ ? EncodingType::VERILOG : EncodingType::BINARY - ); + m_packed_value_buffer_.assign(num_words * 2, 0); + uint64_t *b0_ptr = m_packed_value_buffer_.data(); + uint64_t *b1_ptr = m_packed_value_buffer_.data() + num_words; + + for (unsigned i = 0; i < num_words; ++i) { + const char *start{val - std::min((i + 1) * 64, bitwidth)}; + const char *end{val - 64 * i}; + uint64_t b0 = 0; + uint64_t b1 = 0; + for (const char *p = start; p < end; ++p) { + b0 <<= 1; + b1 <<= 1; + char c = *p; + if (c == '1') { + b0 |= 1; + } else if (c == 'x' || c == 'X') { + b1 |= 1; + } else if (c == 'z' || c == 'Z') { + b0 |= 1; + b1 |= 1; + } else if (c == 'h' || c == 'H') { + b1 |= 1; + } else if (c == 'u' || c == 'U') { + b0 |= 1; + b1 |= 1; + } + } + b0_ptr[i] = b0; + b1_ptr[i] = b1; + } + + emitValueChange(handle, m_packed_value_buffer_.data(), EncodingType::VERILOG); } } diff --git a/integration_test/tests/xz_test/Makefile b/integration_test/tests/xz_test/Makefile new file mode 100644 index 0000000..1263948 --- /dev/null +++ b/integration_test/tests/xz_test/Makefile @@ -0,0 +1 @@ +all: diff --git a/integration_test/tests/xz_test/xz_test.test.cpp b/integration_test/tests/xz_test/xz_test.test.cpp new file mode 100644 index 0000000..a5edad3 --- /dev/null +++ b/integration_test/tests/xz_test/xz_test.test.cpp @@ -0,0 +1,143 @@ +// SPDX-FileCopyrightText: 2025-2026 Yu-Sheng Lin +// SPDX-License-Identifier: MIT +#include +#include +#include +#include +#include "gtkwave/fstapi.h" + +using namespace std; + +int main(int argc, char **argv) { + if (argc != 2) { + cerr << "Usage: " << argv[0] << " " << endl; + return 1; + } + + fstWriterContext *ctx = fstWriterCreate(argv[1], 1); + assert(ctx != nullptr); + + fstWriterSetPackType(ctx, FST_WR_PT_LZ4); + fstWriterSetDate(ctx, "Sat Jan 01 01:23:45 2000\x0a"); + fstWriterSetVersion(ctx, "Verilator"); + fstWriterSetTimescale(ctx, -9); // 1ns + + // Set Top Scope + fstWriterSetScope(ctx, FST_ST_VCD_MODULE, "top", "top"); + + // 1. Single-bit variables + fstHandle wire_1bit_a = + fstWriterCreateVar(ctx, FST_VT_VCD_WIRE, FST_VD_INPUT, 1, "wire_1bit_a", 0); + fstHandle wire_1bit_b = + fstWriterCreateVar(ctx, FST_VT_VCD_WIRE, FST_VD_OUTPUT, 1, "wire_1bit_b", 0); + + // 2. Multi-bit variables (<= 32 bits) + fstHandle bus_8bit = + fstWriterCreateVar(ctx, FST_VT_VCD_WIRE, FST_VD_IMPLICIT, 8, "bus_8bit", 0); + fstHandle bus_10bit = + fstWriterCreateVar(ctx, FST_VT_VCD_WIRE, FST_VD_IMPLICIT, 10, "bus_10bit", 0); + fstHandle bus_32bit = + fstWriterCreateVar(ctx, FST_VT_VCD_WIRE, FST_VD_IMPLICIT, 32, "bus_32bit", 0); + + // Submodule Scope + fstWriterSetScope(ctx, FST_ST_VCD_MODULE, "sub_module", "sub_module"); + + // 3. 64-bit variable + fstHandle bus_64bit = + fstWriterCreateVar(ctx, FST_VT_VCD_WIRE, FST_VD_IMPLICIT, 64, "bus_64bit", 0); + + // 4. Wide variable (> 64 bits, e.g., 128 bits) + fstHandle bus_128bit = + fstWriterCreateVar(ctx, FST_VT_VCD_WIRE, FST_VD_IMPLICIT, 128, "bus_128bit", 0); + + fstWriterSetUpscope(ctx); // Back to top + fstWriterSetUpscope(ctx); // Close top scope + + // ==================== TIME 0: Initial states ==================== + fstWriterEmitTimeChange(ctx, 0); + + // 1-bit: initial X / Z using string emit + fstWriterEmitValueChange(ctx, wire_1bit_a, "x"); + fstWriterEmitValueChange(ctx, wire_1bit_b, "z"); + + // 8-bit: initial state + fstWriterEmitValueChange(ctx, bus_8bit, "01xz01xz"); + + // 10-bit: all Z + fstWriterEmitValueChange(ctx, bus_10bit, "zzzzzzzzzz"); + + // 32-bit: mixed 0, 1, X, Z pattern + fstWriterEmitValueChange(ctx, bus_32bit, "zzzzzzzzxxxxxxxx1111111100000000"); + + // 64-bit: mixed X and Z + fstWriterEmitValueChange( + ctx, bus_64bit, "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + ); + + // 128-bit: mixed string pattern + fstWriterEmitValueChange( + ctx, + bus_128bit, + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx11111111111111111111111111111111" + "00000000000000000000000000000000zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + ); + + // ==================== TIME 10: State transitions ==================== + fstWriterEmitTimeChange(ctx, 10); + + // 1-bit: transition X -> 1, Z -> 0 + fstWriterEmitValueChange64(ctx, wire_1bit_a, 1, 1); + fstWriterEmitValueChange64(ctx, wire_1bit_b, 1, 0); + + // 8-bit: transition to pattern 01xz01xz + fstWriterEmitValueChange(ctx, bus_8bit, "01xz01xz"); + + // 10-bit: transition Z -> binary 0x2A5 using 64-bit emit + fstWriterEmitValueChange64(ctx, bus_10bit, 10, 0x2A5ULL); + + // 32-bit: transition with all X + fstWriterEmitValueChange(ctx, bus_32bit, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + + // 64-bit: transition using 64-bit emit + fstWriterEmitValueChange64(ctx, bus_64bit, 64, 0x1234567890ABCDEFULL); + + // 128-bit: transition using Vec32 (4 uint32_t words) + uint32_t val128_bin[4] = {0x89ABCDEF, 0x01234567, 0x76543210, 0xFEDCBA98}; + fstWriterEmitValueChangeVec32(ctx, bus_128bit, 128, val128_bin); + + // ==================== TIME 20: More transitions ==================== + fstWriterEmitTimeChange(ctx, 20); + + // 1-bit: 1 -> Z, 0 -> X + fstWriterEmitValueChange(ctx, wire_1bit_a, "z"); + fstWriterEmitValueChange(ctx, wire_1bit_b, "x"); + + // 8-bit: all 0s + fstWriterEmitValueChange64(ctx, bus_8bit, 8, 0x00ULL); + + // 128-bit: all X + fstWriterEmitValueChange( + ctx, + bus_128bit, + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + ); + + // ==================== TIME 30: All Z on wide buses ==================== + fstWriterEmitTimeChange(ctx, 30); + + // 128-bit: set all 128 bits to Z + fstWriterEmitValueChange( + ctx, + bus_128bit, + "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + ); + + // ==================== TIME 40: End simulation ==================== + fstWriterEmitTimeChange(ctx, 40); + + fstWriterClose(ctx); + cout << "FST trace generated successfully." << endl; + return 0; +} From 93a16248cd452ead60637621664033bb890aadf5 Mon Sep 17 00:00:00 2001 From: johnjohnlin Date: Tue, 15 Sep 2026 02:17:52 +0800 Subject: [PATCH 2/2] Minor refactor for "Why is there an encoding for 'H' but not for 'L'?" --- fstcpp/fstcpp_writer.cpp | 22 +++++++--------------- fstcpp/fstcpp_writer.h | 3 ++- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/fstcpp/fstcpp_writer.cpp b/fstcpp/fstcpp_writer.cpp index b48fa93..91f20df 100644 --- a/fstcpp/fstcpp_writer.cpp +++ b/fstcpp/fstcpp_writer.cpp @@ -269,15 +269,8 @@ void Writer::emitValueChange(Handle handle, const char *val) { const uint32_t bitwidth{var_info.bitwidth()}; FST_DCHECK_NE(bitwidth, 0); - bool has_non_binary = false; - for (uint32_t i = 0; i < bitwidth; ++i) { - char c = val[i]; - if (c == 'x' || c == 'X' || c == 'z' || c == 'Z' || c == 'u' || c == 'h' || c == 'w' || - c == 'l') { - has_non_binary = true; - break; - } - } + const bool has_non_binary = + std::any_of(val, val + bitwidth, [](const char c) { return c != '0' && c != '1'; }); val += bitwidth; const unsigned num_words{(bitwidth + 63) / 64}; @@ -317,12 +310,11 @@ void Writer::emitValueChange(Handle handle, const char *val) { b0 |= 1; } else if (c == 'x' || c == 'X') { b1 |= 1; - } else if (c == 'z' || c == 'Z') { - b0 |= 1; - b1 |= 1; - } else if (c == 'h' || c == 'H') { - b1 |= 1; - } else if (c == 'u' || c == 'U') { + } else if ( + c == 'z' || c == 'Z' || c == 'h' || c == 'H' || c == 'l' || c == 'L' || + c == 'u' || c == 'U' || c == 'w' || c == 'W' + ) { + // VHDL is not supported yet, treat VHDL-only values as Verilog 'z' b0 |= 1; b1 |= 1; } diff --git a/fstcpp/fstcpp_writer.h b/fstcpp/fstcpp_writer.h index 532fdff..30cf37a 100644 --- a/fstcpp/fstcpp_writer.h +++ b/fstcpp/fstcpp_writer.h @@ -78,7 +78,8 @@ class Writer { std::vector m_hierarchy_buffer_{}; std::vector m_geometry_buffer_{}; // Temporary buffer for packing bit strings into words - // Only used in emitValueChange(Handle, const char*) + // Only used in emitValueChange(Handle, const char*) for performance consideration + // We want to avoid reallocating a temporary buffer for each call to emitValueChange() std::vector m_packed_value_buffer_{}; Header m_header_{}; detail::BlackoutData m_blackout_data_{}; // Not implemented yet