From ee5357ac6e69d70cc6dc93e06a165810ba2c9c91 Mon Sep 17 00:00:00 2001 From: Makiras <22570332+Makiras@users.noreply.github.com> Date: Thu, 8 Oct 2026 23:15:27 +0800 Subject: [PATCH 01/10] fix(build): disable strict aliasing for native signal buffers XData accesses signal buffers through both 32-bit and 64-bit views. GCC 11 Release builds with -O3 and LTO can treat these accesses as independent and return stale values after Set(), breaking the full-value wheel smoke test. Compile native targets and standalone wheels with -fno-strict-aliasing while preserving -O3, LTO and the existing optimization flags. Add integer round-trip and unknown-bit mask regressions across 1-64-bit signals, with checks that remain active under NDEBUG. --- cmake/xcomm.cmake | 4 +++- tests/test_xdata.cpp | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/cmake/xcomm.cmake b/cmake/xcomm.cmake index d40d250..3ab0d44 100644 --- a/cmake/xcomm.cmake +++ b/cmake/xcomm.cmake @@ -32,7 +32,9 @@ if(DEFINED CMAKE_BUILD_PARALLEL) set(INNER_CMAKE_BUILD_PARALLEL "=${CMAKE_BUILD_PARALLEL}") endif() -set(CFLAGS "$ENV{CFLAGS} -ftls-model=global-dynamic -flto -O3 -ftree-vectorize -mtune=native -funroll-loops -funswitch-loops -fomit-frame-pointer") +# Native signal buffers are accessed through both 32-bit and 64-bit views. +# Keep those accesses dependent under optimization, including wheel builds. +set(CFLAGS "$ENV{CFLAGS} -ftls-model=global-dynamic -flto -O3 -fno-strict-aliasing -ftree-vectorize -mtune=native -funroll-loops -funswitch-loops -fomit-frame-pointer") include(CheckCXXCompilerFlag) CHECK_CXX_COMPILER_FLAG("-std=c++20" COMPILER_SUPPORTS_CXX20) diff --git a/tests/test_xdata.cpp b/tests/test_xdata.cpp index a9e22da..e0d00e4 100644 --- a/tests/test_xdata.cpp +++ b/tests/test_xdata.cpp @@ -3,10 +3,36 @@ using namespace xspcomm; +static void test_optimized_signal_buffers() +{ + for (unsigned width : {1U, 8U, 16U, 31U, 32U, 33U, 63U, 64U}) { + XData signal(width, XData::InOut); + const uint64_t mask = width == 64 ? UINT64_MAX : (uint64_t(1) << width) - 1; + for (uint64_t value : {uint64_t(0), uint64_t(1), uint64_t(0xA5), + uint64_t(0x89ABCDEF01234567), UINT64_MAX}) { + signal.Set(value); + const uint64_t actual = signal.U(); + Assert(actual == (value & mask), + "XData(%u) round-trip: expected 0x%llx, got 0x%llx", width, + static_cast(value & mask), + static_cast(actual)); + Assert(signal.XMask() == 0 && signal.DataValid(), + "XData(%u) integer assignment retained unknown bits", width); + } + + signal.pVecData[0].bval = 1; + const uint64_t unknown = width > 32 ? (uint64_t(1) << 32) | 1 : 1; + if (width > 32) signal.pVecData[1].bval = 1; + Assert(signal.XMask() == unknown && !signal.DataValid(), + "XData(%u) lost its unknown-bit mask", width); + } +} + int main(int argsc, const char **argsv) { Debug("version: %s", version().c_str()); checkVersion(); + test_optimized_signal_buffers(); test_xdata(); return 0; } From 267dbfe69de58f00b9472683ef1a8a7e272fe664 Mon Sep 17 00:00:00 2001 From: Makiras <22570332+Makiras@users.noreply.github.com> Date: Thu, 8 Oct 2026 23:16:11 +0800 Subject: [PATCH 02/10] fix(data): harden native buffer access and integer boundaries Read 1-8-bit and 9-16-bit native bindings at their actual storage width instead of reading a full 32-bit word. Make range comparisons read only the configured 1-8 bytes and support unaligned addresses. Select the comparison width when creating the callback; fixed-size copies inline to one load per input for 1/2/4/8 bytes, or two overlapping loads for 3/5/6/7 bytes, without per-call allocation. Avoid shifting by 64 during signed conversion and signed overflow when constructing 31-bit masks. Treat out-of-capacity word offsets in SetBits as empty writes. Handle oversized shifts and INT_MIN in big_shift using unsigned arithmetic and zero-fill results outside the buffer width. Add exact-allocation, unaligned-input, slice, signed-value, masked-write and shift-boundary regressions, and document the pointer comparison and SetBits bounds. Public signatures and native class layouts retain ABI 2. Validation of the complete series: - GCC 11 Release: all 7 native tests and the Python suite pass. - ASan/UBSan: all 7 native tests pass (-O1, no LTO or leak detection). - Picker integration: all 24 CTest cases, Java bindings and 11,451 generated Adder vectors pass, including ABI 2 signal checks. - Local x86_64 callback benchmarks match baseline checksums; non-power- of-two widths add approximately 0.2-0.8 ns per comparison in this setup. --- docs/APIs.cn.md | 4 +- include/xspcomm/xcomuse_base.h | 46 +++++++++++++++++--- include/xspcomm/xdata.h | 2 +- include/xspcomm/xutil.h | 11 ++++- src/xdata.cpp | 35 +++++++++------ tests/test_xcomuse_base.cpp | 23 ++++++++++ tests/test_xdata.cpp | 79 ++++++++++++++++++++++++++++++++++ 7 files changed, 176 insertions(+), 24 deletions(-) diff --git a/docs/APIs.cn.md b/docs/APIs.cn.md index 5d6b259..02f5206 100644 --- a/docs/APIs.cn.md +++ b/docs/APIs.cn.md @@ -167,7 +167,7 @@ bool eq = (a == "000000ff"); | `std::shared_ptr SubDataRef(uint32_t start, uint32_t width, std::string name = "")` | 创建引用原对象 `[start, start+width)` 的切片。写切片会同步回原对象。 | | `XData *SubDataRefRaw(...)` | 与 `SubDataRef` 相同,但返回裸指针。主要用于 Go 包装。 | | `void SetBits(uint8_t *buffer, int count, uint8_t *mask = nullptr, int start = 0)` | 以 byte 为单位写入,`start` 是 byte 偏移。 | -| `void SetBits(uint32_t *buffer, uint32_t count, uint32_t *mask = nullptr, uint32_t start = 0)` | 以 32-bit word 为单位写入,`start` 是 word 偏移。 | +| `void SetBits(uint32_t *buffer, uint32_t count, uint32_t *mask = nullptr, uint32_t start = 0)` | 以 32-bit word 为单位写入,`start` 是 word 偏移;超出信号容量的部分忽略,起点在容量外时不写入。 | | `bool GetBits(uint8_t *buffer, uint32_t count)` | 读出 byte 数据;如果读取范围含 X/Z 返回 false。 | | `bool GetBits(uint32_t *buffer, uint32_t count)` | 读出 32-bit word 数据;如果读取范围含 X/Z 返回 false。 | @@ -675,7 +675,7 @@ state S2: | API | 说明 | | --- | --- | -| `ComUseRangeCheck(int range, int bytes)` | `bytes <= 8`。 | +| `ComUseRangeCheck(int range, int bytes)` | `1 <= bytes <= 8`;pointer 比较只读取指定的字节数,支持未对齐地址。 | | `static bool cmp(uint64_t t, uint64_t c, int r)` | 当 `r >= 0` 时检查 `c-r <= t <= c`;当 `r < 0` 时检查 `c <= t <= c-r`。 | | `uint64_t CSelf()` | 返回自身地址,用作 callback arg。 | | `GetArrayCmp()` | 返回 pointer 比较 callback。 | diff --git a/include/xspcomm/xcomuse_base.h b/include/xspcomm/xcomuse_base.h index 66e898c..0e2184c 100644 --- a/include/xspcomm/xcomuse_base.h +++ b/include/xspcomm/xcomuse_base.h @@ -220,9 +220,43 @@ namespace xspcomm { class ComUseRangeCheck { int bytes; int range; + template + static uint64_t Read(uint64_t address){ + T value; + std::memcpy(&value, (const void *)address, sizeof(value)); + return value; + } + template + static uint64_t Load(uint64_t address){ + if constexpr(Bytes == 1) return Read(address); + else if constexpr(Bytes == 2) return Read(address); + else if constexpr(Bytes == 4) return Read(address); + else if constexpr(Bytes == 8) return Read(address); + // Overlapping loads cover 3/5/6/7 bytes without reading past the end. + else if constexpr(Bytes == 3) return Read(address) | (Read(address + 1) << 8); + else return Read(address) | (Read(address + Bytes - 4) << ((Bytes - 4) * 8)); + } + template + static bool Compare(uint64_t a, uint64_t b, uint64_t self){ + return cmp(Load(a), Load(b), ((ComUseRangeCheck*)self)->range); + } + using ArrayCompare = bool (*)(uint64_t, uint64_t, uint64_t); + static ArrayCompare Select(int bytes){ + switch(bytes){ + case 1: return Compare<1>; + case 2: return Compare<2>; + case 3: return Compare<3>; + case 4: return Compare<4>; + case 5: return Compare<5>; + case 6: return Compare<6>; + case 7: return Compare<7>; + case 8: return Compare<8>; + default: return nullptr; + } + } public: ComUseRangeCheck(int range, int bytes):bytes(bytes), range(range){ - Assert(bytes <= 8, "FIXME: bytes more than 8 is not supported!"); + Assert(bytes >= 1 && bytes <= 8, "Need 1 <= bytes <= 8"); } static bool cmp(uint64_t t, uint64_t c, int r){ if(r >= 0){ @@ -232,17 +266,17 @@ namespace xspcomm { } static bool ArrayCmp(uint64_t a, uint64_t b, uint64_t self){ ComUseRangeCheck * p = (ComUseRangeCheck*)self; - uint64_t vamask = p->bytes == 8 ? -1 : (((uint64_t)1 << (8*p->bytes)) - 1); - uint64_t target = vamask & (*(uint64_t*)a); - uint64_t cvalue = vamask & (*(uint64_t*)b); - return cmp(target, cvalue, p->range); + if(p->bytes == 8) return Compare<8>(a, b, self); + auto compare = Select(p->bytes); + return compare && compare(a, b, self); } static bool XDataCmp(XData *a, XData *b, uint64_t self){ return cmp(a->U(), b->U(), ((ComUseRangeCheck*)self)->range); } uint64_t CSelf(){return (uint64_t)this;} xfunction GetArrayCmp(){ - return (bool (*)(uint64_t, uint64_t, uint64_t))ArrayCmp; + // Select once when creating the callback, outside the hot path. + return Select(this->bytes); } xfunction GetXDataCmp(){ return (bool (*)(XData*, XData*, uint64_t))XDataCmp; diff --git a/include/xspcomm/xdata.h b/include/xspcomm/xdata.h index 433e397..a757a71 100644 --- a/include/xspcomm/xdata.h +++ b/include/xspcomm/xdata.h @@ -53,7 +53,7 @@ enum class XDataBackendKind { #define bit32_one(tar, msk) tar = msk | tar #define bit32_zro(tar, msk) tar = (~msk) & tar #define bit32_val(tar, msk) (msk & tar) == 0 ? 0 : 1 -#define bit32_msk(ones) (1 << ones) - 1 +#define bit32_msk(ones) ((uint32_t(1) << (ones)) - 1U) #define bit32_set(tar, idx) tar = (tar) | (1 << idx) #define bit32_hex(tar, idx, val) \ tar = ((~(0xF << idx * 4)) & (tar)) | (val << idx * 4) diff --git a/include/xspcomm/xutil.h b/include/xspcomm/xutil.h index 0510148..bb88d32 100644 --- a/include/xspcomm/xutil.h +++ b/include/xspcomm/xutil.h @@ -264,9 +264,16 @@ std::string inline removeSuffix(const std::string& str, const std::string& suffi inline void big_shift(int* a, int size, int shift) { + if (size <= 0) return; int intBits = sizeof(int) * 8; - int absShift = std::abs(shift); + // Unsigned arithmetic also handles the magnitude of INT_MIN. + unsigned int absShift = shift < 0 ? 0U - static_cast(shift) + : static_cast(shift); int numInts = absShift / intBits; + if (numInts >= size) { + std::memset(a, 0, size * sizeof(int)); + return; + } int bitShift = absShift % intBits; if (shift < 0) { if (numInts > 0) { @@ -277,7 +284,7 @@ inline void big_shift(int* a, int size, int shift) { int carry = 0; for (int i = 0; i < size; ++i) { int newCarry = static_cast(a[i]) >> (intBits - bitShift); - a[i] = (a[i] << bitShift) | carry; + a[i] = (static_cast(a[i]) << bitShift) | carry; carry = newCarry; } } diff --git a/src/xdata.cpp b/src/xdata.cpp index aacdfec..68cd7d6 100644 --- a/src/xdata.cpp +++ b/src/xdata.cpp @@ -578,7 +578,7 @@ void XData::_sub_data_fake_dpirw(void *data, bool is_read){ temp_mask[secs - 1] = 0xFFFFFFFF; } else { // Otherwise, set only the required bits - temp_mask[secs - 1] = (1 << last_bits) - 1; + temp_mask[secs - 1] = bit32_msk(last_bits); } } // Apply the mask and update the data @@ -668,23 +668,32 @@ void XData::BindNativeData(uint64_t pdata){ *(unsigned char *)pdata = d; }; }else{ - this->vecRead = [this, pdata](void *d){ - for(int i = 0; i < this->vecSize; i++){ - ((xsvLogicVecVal *)d)[i].aval = ((uint32_t *)pdata)[i]; - } - }; // 1 - 8 if(this->mWidth <=8){ - this->vecWrite = [this, pdata](void *d){ - *(unsigned char *)pdata = *(unsigned char *)(&(((xsvLogicVecVal *)d)[0].aval)); + this->vecRead = [pdata](void *d){ + ((xsvLogicVecVal *)d)->aval = *(uint8_t *)pdata; + }; + this->vecWrite = [pdata](void *d){ + *(uint8_t *)pdata = (uint8_t)((xsvLogicVecVal *)d)->aval; }; // 9 - 16 }else if (this->mWidth <= 16){ - this->vecWrite = [this, pdata](void *d){ - *(unsigned short *)pdata = *(unsigned short *)(&(((xsvLogicVecVal *)d)[0].aval)); + this->vecRead = [pdata](void *d){ + uint16_t value; + std::memcpy(&value, (const void *)pdata, sizeof(value)); + ((xsvLogicVecVal *)d)->aval = value; + }; + this->vecWrite = [pdata](void *d){ + uint16_t value = (uint16_t)((xsvLogicVecVal *)d)->aval; + std::memcpy((void *)pdata, &value, sizeof(value)); }; // 17 + }else{ + this->vecRead = [this, pdata](void *d){ + for(int i = 0; i < this->vecSize; i++){ + ((xsvLogicVecVal *)d)[i].aval = ((uint32_t *)pdata)[i]; + } + }; this->vecWrite = [this, pdata](const void *d){ for(int i = 0; i < this->vecSize; i++){ ((uint32_t *)pdata)[i] = ((xsvLogicVecVal *)d)[i].aval; @@ -829,7 +838,7 @@ void XData::SetBits(u_int32_t *buffer, u_int32_t count, u_int32_t *mask, this->mName.c_str()); } Assert(this->mWidth > 0, "only svVec support SetBits"); - auto range = std::min(count, this->vecSize - start); + auto range = start < this->vecSize ? std::min(count, this->vecSize - start) : 0; for (int i = 0; i < range; i++) { if (mask == nullptr) { this->pVecData[i + start].aval = buffer[i]; @@ -966,8 +975,8 @@ uint64_t XData::XMask() int64_t XData::S() { this->update_read(); - // less 2 bit or more than 64 bit, ignore cast - if (this->mWidth == 0 || this->mWidth > 64) { + // A full native word already has its sign bit in the correct position. + if (this->mWidth == 0 || this->mWidth >= 64) { return static_cast(this->udata); } diff --git a/tests/test_xcomuse_base.cpp b/tests/test_xcomuse_base.cpp index 04d77a6..8c762da 100644 --- a/tests/test_xcomuse_base.cpp +++ b/tests/test_xcomuse_base.cpp @@ -141,6 +141,29 @@ TEST_CASE("ComUseRangeCheck", "[xcomuse_base]") { REQUIRE(fn((uint64_t)&a, (uint64_t)&b, rc.CSelf()) == true); } +TEST_CASE("ComUseRangeCheck reads exactly its byte count", "[xcomuse_base]") { + for (int bytes = 1; bytes <= 8; ++bytes) { + // Exact allocations expose overreads; the offset also tests alignment. + auto a = std::make_unique(bytes + 1); + auto b = std::make_unique(bytes + 1); + std::memset(a.get(), 0x80, bytes + 1); + std::memset(b.get(), 0x80, bytes + 1); + a[1] = 4; + b[1] = 6; + auto lhs = reinterpret_cast(a.get() + 1); + auto rhs = reinterpret_cast(b.get() + 1); + ComUseRangeCheck rc(2, bytes); + auto compare = rc.GetArrayCmp(); + REQUIRE(compare(lhs, rhs, rc.CSelf())); + REQUIRE(ComUseRangeCheck::ArrayCmp(lhs, rhs, rc.CSelf())); + b[1] = 7; + REQUIRE_FALSE(compare(lhs, rhs, rc.CSelf())); + REQUIRE_FALSE(ComUseRangeCheck::ArrayCmp(lhs, rhs, rc.CSelf())); + b[1] = 4; + REQUIRE(compare(lhs, rhs, rc.CSelf())); + } +} + TEST_CASE("CString basic", "[xcomuse_base]") { CString s("abc"); REQUIRE(s.Get() == "abc"); diff --git a/tests/test_xdata.cpp b/tests/test_xdata.cpp index e0d00e4..1fb6cb9 100644 --- a/tests/test_xdata.cpp +++ b/tests/test_xdata.cpp @@ -1,5 +1,6 @@ #include "xspcomm/xcomm.h" #include "xspcomm/xinstance.h" +#include using namespace xspcomm; @@ -28,11 +29,89 @@ static void test_optimized_signal_buffers() } } +static void test_native_storage() +{ + auto check = [](unsigned width, auto value) { + auto storage = std::make_unique(value); + XData signal(width, XData::InOut); + signal.BindNativeData(reinterpret_cast(storage.get())); + Assert(signal.U() == value, "XData(%u) native read failed", width); + signal.AsImmWrite(); + signal.Set(0x5A); + const auto expected = width == 1 ? 0 : 0x5A; + Assert(*storage == expected, "XData(%u) native write failed", width); + *storage = value; + Assert(signal.U() == value, "XData(%u) native refresh failed", width); + }; + check(1, uint8_t(1)); + check(8, uint8_t(0xA5)); + check(9, uint16_t(0x1A5)); + check(16, uint16_t(0xBEEF)); + check(32, uint32_t(0xDEADBEEF)); + check(64, uint64_t(0x89ABCDEF01234567)); +} + +static void test_signed_values_and_slices() +{ + XData full(64, XData::InOut); + for (int64_t value : {INT64_MIN, INT64_MIN + 1, int64_t(-123), + int64_t(-1), int64_t(0), INT64_MAX}) { + full.Set(value); + Assert(full.S() == value, "64-bit signed round-trip failed"); + } + for (unsigned width : {8U, 31U, 32U, 63U}) { + XData signal(width, XData::InOut); + signal.Set(-123); + Assert(signal.S() == -123, "XData(%u) sign extension failed", width); + } + full.Set(uint64_t(0x8000000000000001)); + auto slice = full.SubDataRef(1, 62); + slice->Set(UINT64_MAX); + Assert(full.U() == UINT64_MAX, "slice ending at bit 63 lost bits"); + slice->Set(0); + Assert(full.U() == UINT64_C(0x8000000000000001), "slice overwrote adjacent bits"); +} + +static void test_bit_buffer_boundaries() +{ + XData signal(32, XData::InOut); + uint32_t values[] = {0xA5, 0x5A}; + uint32_t mask = 0xFFFF; + signal.Set(0x12345678); + for (uint32_t start : {uint32_t(1), uint32_t(2), UINT32_MAX}) { + signal.SetBits(values, 1, nullptr, start); + signal.SetBits(values, 1, &mask, start); + Assert(signal.U() == 0x12345678, "out-of-range SetBits changed the signal"); + } + signal.SetBits(static_cast(nullptr), uint32_t(0), nullptr, UINT32_MAX); + signal.SetBits(values, 1, &mask); + Assert(signal.U() == 0x123400A5, "masked SetBits failed"); + signal.SetBits(values, 2); + Assert(signal.U() == 0xA5, "SetBits did not clip to the signal size"); + + const uint64_t initial = UINT64_C(0x89ABCDEF01234567); + for (int shift : {0, 1, 31, 32, 33, 63, 64, 65, 95, 96, + -1, -31, -32, -33, -63, -64, -65, -95, -96, + std::numeric_limits::min(), std::numeric_limits::max()}) { + int words[] = {int(uint32_t(initial)), int(uint32_t(initial >> 32))}; + big_shift(words, 2, shift); + const uint64_t actual = uint32_t(words[0]) | (uint64_t(uint32_t(words[1])) << 32); + uint64_t expected = 0; + if (shift >= 0 && shift < 64) expected = initial >> shift; + if (shift < 0 && shift > -64) expected = initial << -shift; + Assert(actual == expected, "big_shift failed for shift %d", shift); + } + big_shift(nullptr, 0, std::numeric_limits::min()); +} + int main(int argsc, const char **argsv) { Debug("version: %s", version().c_str()); checkVersion(); test_optimized_signal_buffers(); + test_native_storage(); + test_signed_values_and_slices(); + test_bit_buffer_boundaries(); test_xdata(); return 0; } From 8929374cafa8685dc419b966e4a3ec5397e47ea0 Mon Sep 17 00:00:00 2001 From: Makiras <22570332+Makiras@users.noreply.github.com> Date: Fri, 9 Oct 2026 11:27:01 +0800 Subject: [PATCH 03/10] fix(data): compare wide values and template native memory bindings Compare range-check buffers wider than 64 bits and use the complete XData signal width, with unsigned zero extension for operands of different widths. Compute word-wise differences with borrow and exact-size unaligned loads; retain the scalar fast path and avoid per-comparison allocation. Reject X/Z operands and refresh each signal once without writing to its backend. Prevent tolerance checks from wrapping at zero or UINT64_MAX, including negative ranges and INT_MIN. Add regressions for 9-33-byte buffers, 65-256-bit signals, cross-word borrow, unknown bits and callback read/write counts. Unify BindNativeData vector reads and writes in a typed memcpy helper with explicit C++ casts. Generate the 1-16-word fixed-count binding cases with an X-Macro and use a runtime loop for longer vectors. Preserve current bval behavior and document the unresolved native X/Z policy. Keep callbacks tied to the current word count so ReInit can change widths without stale accesses. Cover exact-size unaligned native buffers, all fixed-count paths, the runtime threshold, preserved unknown masks and ReInit growth/shrink transitions. Public API signatures and native class layouts retain ABI 2. Validation of the final tree: - GCC 11 Release: all 7 native tests and the Python regression suite pass. - ASan/UBSan: all 7 native tests pass, with leak detection disabled. - C++17 syntax check and git diff --check pass. - Local x86_64 full U()/Set() benchmarks: 2 million iterations, 7 samples per round, 3 rounds with alternating order on the same CPU. Checksums match; 8-64-bit timings stay within 2%, while 256-bit read/write times fall about 16%/10% and 1024-bit times fall about 35%/23%. --- docs/APIs.cn.md | 23 +++++- include/xspcomm/xcomuse_base.h | 13 ++-- src/xcomuse_base.cpp | 48 ++++++++++++ src/xdata.cpp | 137 ++++++++++++++++++++++++--------- tests/test_xcomuse_base.cpp | 122 +++++++++++++++++++++++++++++ tests/test_xdata.cpp | 86 +++++++++++++++++++++ 6 files changed, 383 insertions(+), 46 deletions(-) diff --git a/docs/APIs.cn.md b/docs/APIs.cn.md index 02f5206..2abc075 100644 --- a/docs/APIs.cn.md +++ b/docs/APIs.cn.md @@ -195,6 +195,19 @@ auto sub = full.SubDataRef(30, 64, "sub"); | `XData &SetVPIWriteFlag(int flag)` | 设置自定义 VPI write flag。 | | `XData &AsVPIAuto()` / `AsVPIScale()` / `AsVPIInt()` / `AsVPIVector()` | 设置 VPI 读写格式选择。`AsVPIScale` 为源码中现有拼写,语义是 scalar。 | +`BindNativeData` 注册两个独立回调:read 从原生内存读取到 XData,write 从 XData 写回原生内存。 +绑定结束时读取一次初始值;实际写回时机由 `WriteMode` 决定。回调在代码中的注册顺序不表示每次写入前都会读取。 + +原生 vector 读写的访问粒度为 `uint8_t`(1~8 bit)、`uint16_t`(9~16 bit)和连续的 32-bit word(17 bit 及以上,64-bit 存储按两个 word 访问)。每个 word 对应 XData 的一个 `aval`,不包含 `bval`;读写支持未对齐的原生地址。 + +**BindNativeData TODO(当前保留行为)** + +- [ ] 明确二态原生内存读取遇到已有 X/Z 状态时,绑定及每次刷新是否清理 `bval`。当前 vector 读取只更新 `aval`,保留 `bval`。 +- [ ] 明确 X/Z 写回二态存储的规则,并核对 scalar/vector 的语义。当前 vector 写回只保存 `aval`。 +- [ ] 补充重绑定、绑定后写入 X/Z,以及 scalar/vector 的回归覆盖。 + +新对象的 `bval` 由 `calloc` 初始化为零,但已有 X/Z 状态可能残留。已复现:先将 8-bit XData 设为全 X,再绑定值为 `0x5A` 的 `uint8_t`,读回 `U() == 0x5A`,却仍有 `XMask() == 0xFF`、`DataValid() == false`;16/64 bit 也有相同现象。 + ### 连接、比较、回调 | API | 说明 | @@ -675,11 +688,15 @@ state S2: | API | 说明 | | --- | --- | -| `ComUseRangeCheck(int range, int bytes)` | `1 <= bytes <= 8`;pointer 比较只读取指定的字节数,支持未对齐地址。 | -| `static bool cmp(uint64_t t, uint64_t c, int r)` | 当 `r >= 0` 时检查 `c-r <= t <= c`;当 `r < 0` 时检查 `c <= t <= c-r`。 | +| `ComUseRangeCheck(int range, int bytes)` | `bytes >= 1`;pointer 比较按 little-endian 读取指定的字节数,支持未对齐地址和超过 64 bit 的值。 | +| `static bool cmp(uint64_t t, uint64_t c, int r)` | 当 `r >= 0` 时检查 `c-r <= t <= c`;当 `r < 0` 时检查 `c <= t <= c-r`。按数学整数判断,不在零或最大值处回绕。 | | `uint64_t CSelf()` | 返回自身地址,用作 callback arg。 | | `GetArrayCmp()` | 返回 pointer 比较 callback。 | -| `GetXDataCmp()` | 返回 XData 比较 callback。 | +| `GetXDataCmp()` | 返回 XData 比较 callback,使用信号的完整位宽;不同位宽按无符号零扩展,含 X/Z 时返回 false。`bytes` 不截断 XData。 | + +`range` 是有符号 `int` 容差,比较值的位宽不受其限制。例如 `ComUseRangeCheck(1, 16)` +可以比较两个 128-bit buffer;`0xffffffffffffffff` 位于 `0x10000000000000000` 下方 1 的范围内。 +宽比较直接逐 32-bit word 计算差值,不分配临时大整数;每个 XData 操作数只刷新一次,不写回。 ### CString diff --git a/include/xspcomm/xcomuse_base.h b/include/xspcomm/xcomuse_base.h index 0e2184c..49d9d1b 100644 --- a/include/xspcomm/xcomuse_base.h +++ b/include/xspcomm/xcomuse_base.h @@ -240,6 +240,7 @@ namespace xspcomm { static bool Compare(uint64_t a, uint64_t b, uint64_t self){ return cmp(Load(a), Load(b), ((ComUseRangeCheck*)self)->range); } + static bool ArrayWideCmp(uint64_t a, uint64_t b, uint64_t self); using ArrayCompare = bool (*)(uint64_t, uint64_t, uint64_t); static ArrayCompare Select(int bytes){ switch(bytes){ @@ -251,18 +252,18 @@ namespace xspcomm { case 6: return Compare<6>; case 7: return Compare<7>; case 8: return Compare<8>; - default: return nullptr; + default: return ArrayWideCmp; } } public: ComUseRangeCheck(int range, int bytes):bytes(bytes), range(range){ - Assert(bytes >= 1 && bytes <= 8, "Need 1 <= bytes <= 8"); + Assert(bytes >= 1, "Need bytes >= 1"); } static bool cmp(uint64_t t, uint64_t c, int r){ if(r >= 0){ - return (c - r <= t) && (c >= t); + return t <= c && c - t <= (uint32_t)r; } - return (c - r >= t) && (c <= t); + return t >= c && t - c <= (uint64_t)(-(int64_t)r); } static bool ArrayCmp(uint64_t a, uint64_t b, uint64_t self){ ComUseRangeCheck * p = (ComUseRangeCheck*)self; @@ -270,9 +271,7 @@ namespace xspcomm { auto compare = Select(p->bytes); return compare && compare(a, b, self); } - static bool XDataCmp(XData *a, XData *b, uint64_t self){ - return cmp(a->U(), b->U(), ((ComUseRangeCheck*)self)->range); - } + static bool XDataCmp(XData *a, XData *b, uint64_t self); uint64_t CSelf(){return (uint64_t)this;} xfunction GetArrayCmp(){ // Select once when creating the callback, outside the hot path. diff --git a/src/xcomuse_base.cpp b/src/xcomuse_base.cpp index a577ba8..db6b545 100644 --- a/src/xcomuse_base.cpp +++ b/src/xcomuse_base.cpp @@ -8,6 +8,54 @@ namespace xspcomm { +namespace { +template +bool range_compare(size_t words, int range, ReadA a, ReadB b){ + const uint32_t limit = range >= 0 ? (uint32_t)range : (uint32_t)(-(int64_t)range); + uint64_t borrow = 0; + for(size_t i = 0; i < words; ++i){ + const uint64_t lhs = range >= 0 ? b(i) : a(i); + const uint64_t rhs = (range >= 0 ? a(i) : b(i)) + borrow; + const uint32_t difference = (uint32_t)(lhs - rhs); + borrow = lhs < rhs; + // The tolerance fits in the low word; all higher difference words must be zero. + if(difference > (i == 0 ? limit : 0U)) return false; + } + return borrow == 0; +} +} + +bool ComUseRangeCheck::ArrayWideCmp(uint64_t a, uint64_t b, uint64_t self){ + auto *check = (ComUseRangeCheck*)self; + const size_t bytes = check->bytes; + auto word = [bytes](uint64_t address, size_t i) -> uint32_t { + const size_t offset = i * 4; + if(bytes - offset >= 4) return (uint32_t)Read(address + offset); + switch(bytes - offset){ + case 1: return (uint32_t)Load<1>(address + offset); + case 2: return (uint32_t)Load<2>(address + offset); + default: return (uint32_t)Load<3>(address + offset); + } + }; + return range_compare((bytes + 3) / 4, check->range, + [=](size_t i){ return word(a, i); }, + [=](size_t i){ return word(b, i); }); +} + +bool ComUseRangeCheck::XDataCmp(XData *a, XData *b, uint64_t self){ + // Refresh each signal once; subsequent word reads use its existing cache. + const uint64_t av = a->U(); + const uint64_t bv = b->U(); + if(!a->DataValid() || !b->DataValid()) return false; + const int range = ((ComUseRangeCheck*)self)->range; + if(a->W() <= 64 && b->W() <= 64) return cmp(av, bv, range); + const size_t awords = ((uint64_t)a->W() + 31) / 32; + const size_t bwords = ((uint64_t)b->W() + 31) / 32; + return range_compare(std::max(awords, bwords), range, + [=](size_t i){ return i == 0 ? (uint32_t)av : (i < awords ? a->pVecData[i].aval : 0U); }, + [=](size_t i){ return i == 0 ? (uint32_t)bv : (i < bwords ? b->pVecData[i].aval : 0U); }); +} + u_int64_t ComUseStepCb::GetCb(){ return (u_int64_t)ComUseStepCb::Cb; } diff --git a/src/xdata.cpp b/src/xdata.cpp index 68cd7d6..c186802 100644 --- a/src/xdata.cpp +++ b/src/xdata.cpp @@ -2,9 +2,82 @@ #include "xspcomm/xdata.h" #include "xspcomm/xexpr.h" #include "xspcomm/xutil.h" +#include +#include namespace xspcomm { +namespace { +// Count == 0 uses a runtime loop; fixed counts are expanded at compile time. +template +struct VecMemoryIO { + static_assert(std::is_same_v || + std::is_same_v || + std::is_same_v); + + static T Load(const unsigned char* src) { + T value; + std::memcpy(&value, src, sizeof(T)); + return value; + } + + static void Store(unsigned char* dst, uint32_t value) { + const T native = static_cast(value); + std::memcpy(dst, &native, sizeof(T)); + } + + template + static void ReadFixed(xsvLogicVecVal* dst, const unsigned char* src, + std::index_sequence) { + ((dst[I].aval = static_cast(Load(src + I * sizeof(T)))), ...); + } + + template + static void WriteFixed(unsigned char* dst, const xsvLogicVecVal* src, + std::index_sequence) { + (Store(dst + I * sizeof(T), src[I].aval), ...); + } + + static void Read(xsvLogicVecVal* dst, const void* src, size_t count) { + const auto* bytes = static_cast(src); + const size_t words = sizeof(T) < sizeof(uint32_t) ? 1 : count; + if constexpr (Count != 0) { + // ReInit preserves callbacks, so the current width can differ. + if (likely(words == Count)) { + ReadFixed(dst, bytes, std::make_index_sequence{}); + return; + } + } + for (size_t i = 0; i < words; ++i) + dst[i].aval = static_cast(Load(bytes + i * sizeof(T))); + } + + static void Write(void* dst, const xsvLogicVecVal* src, size_t count) { + auto* bytes = static_cast(dst); + const size_t words = sizeof(T) < sizeof(uint32_t) ? 1 : count; + if constexpr (Count != 0) { + if (likely(words == Count)) { + WriteFixed(bytes, src, std::make_index_sequence{}); + return; + } + } + for (size_t i = 0; i < words; ++i) + Store(bytes + i * sizeof(T), src[i].aval); + } + + using VecCallback = xfunction; + static void Bind(uint64_t address, const uint32_t& count, + VecCallback& read, VecCallback& write) { + read = [address, &count](xsvLogicVecVal* dst) { + Read(dst, reinterpret_cast(address), count); + }; + write = [address, &count](const xsvLogicVecVal* src) { + Write(reinterpret_cast(address), src, count); + }; + } +}; +} // namespace + const IOType XData::In; const IOType XData::Out; const IOType XData::InOut; @@ -659,47 +732,39 @@ void XData::BindDPIRW(void (*read)(void *), void (*write)(const unsigned char)) this->readonly_backend = false; this->update_read(); } +// TODO: Define how native two-state reads handle an existing X/Z mask and how +// X/Z writes map to native storage. Preserve bval until that policy is settled; +// see docs/APIs.cn.md (BindNativeData TODO). void XData::BindNativeData(uint64_t pdata){ if (this->mWidth == 0){ - this->bitRead = [pdata](void *d){ - *(unsigned char *)d = *(unsigned char *)pdata; + auto* native = reinterpret_cast(pdata); + this->bitRead = [native](xsvLogic* dst){ + *dst = *native; }; - this->bitWrite = [pdata](const unsigned char d){ - *(unsigned char *)pdata = d; + this->bitWrite = [native](xsvLogic value){ + *native = value; }; - }else{ - // 1 - 8 - if(this->mWidth <=8){ - this->vecRead = [pdata](void *d){ - ((xsvLogicVecVal *)d)->aval = *(uint8_t *)pdata; - }; - this->vecWrite = [pdata](void *d){ - *(uint8_t *)pdata = (uint8_t)((xsvLogicVecVal *)d)->aval; - }; - // 9 - 16 - }else if (this->mWidth <= 16){ - this->vecRead = [pdata](void *d){ - uint16_t value; - std::memcpy(&value, (const void *)pdata, sizeof(value)); - ((xsvLogicVecVal *)d)->aval = value; - }; - this->vecWrite = [pdata](void *d){ - uint16_t value = (uint16_t)((xsvLogicVecVal *)d)->aval; - std::memcpy((void *)pdata, &value, sizeof(value)); - }; - // 17 + - }else{ - this->vecRead = [this, pdata](void *d){ - for(int i = 0; i < this->vecSize; i++){ - ((xsvLogicVecVal *)d)[i].aval = ((uint32_t *)pdata)[i]; - } - }; - this->vecWrite = [this, pdata](const void *d){ - for(int i = 0; i < this->vecSize; i++){ - ((uint32_t *)pdata)[i] = ((xsvLogicVecVal *)d)[i].aval; - } - }; + } else if (this->mWidth <= 8) { + VecMemoryIO::Bind(pdata, this->vecSize, this->vecRead, this->vecWrite); + } else if (this->mWidth <= 16) { + VecMemoryIO::Bind(pdata, this->vecSize, this->vecRead, this->vecWrite); + } else { +#define XCOMM_NATIVE_WORD_COUNTS(M) \ + M(1) M(2) M(3) M(4) M(5) M(6) M(7) M(8) \ + M(9) M(10) M(11) M(12) M(13) M(14) M(15) M(16) +#define XCOMM_BIND_NATIVE_CASE(N) \ + case N: \ + VecMemoryIO::Bind(pdata, this->vecSize, this->vecRead, this->vecWrite); \ + break; + + switch (this->vecSize) { + XCOMM_NATIVE_WORD_COUNTS(XCOMM_BIND_NATIVE_CASE) + default: + VecMemoryIO::Bind(pdata, this->vecSize, this->vecRead, this->vecWrite); + break; } +#undef XCOMM_BIND_NATIVE_CASE +#undef XCOMM_NATIVE_WORD_COUNTS } this->backend_kind = XDataBackendKind::MemDirect; this->readonly_backend = false; diff --git a/tests/test_xcomuse_base.cpp b/tests/test_xcomuse_base.cpp index 8c762da..a74538d 100644 --- a/tests/test_xcomuse_base.cpp +++ b/tests/test_xcomuse_base.cpp @@ -4,6 +4,7 @@ #include "xspcomm/xcomuse_base.h" #include "xspcomm/xclock.h" #include "xspcomm/xdata.h" +#include using namespace xspcomm; @@ -164,6 +165,127 @@ TEST_CASE("ComUseRangeCheck reads exactly its byte count", "[xcomuse_base]") { } } +TEST_CASE("ComUseRangeCheck does not wrap at integer boundaries", "[xcomuse_base]") { + REQUIRE(ComUseRangeCheck::cmp(0, 0, 2)); + REQUIRE(ComUseRangeCheck::cmp(0, 1, 2)); + REQUIRE_FALSE(ComUseRangeCheck::cmp(UINT64_MAX, 0, 2)); + REQUIRE(ComUseRangeCheck::cmp(UINT64_MAX, UINT64_MAX, -2)); + REQUIRE_FALSE(ComUseRangeCheck::cmp(0, UINT64_MAX, -2)); + REQUIRE(ComUseRangeCheck::cmp(uint64_t(1) << 31, 0, std::numeric_limits::min())); + REQUIRE_FALSE(ComUseRangeCheck::cmp((uint64_t(1) << 31) + 1, 0, std::numeric_limits::min())); +} + +TEST_CASE("ComUseRangeCheck compares full wide buffers and signals", "[xcomuse_base]") { + struct Case { const char *target; const char *center; int range; bool expected; }; + const Case cases[] = { + {"0x0", "0x10000000000000000", 0, false}, + {"0xffffffffffffffff", "0x10000000000000000", 1, true}, + {"0xfffffffffffffffe", "0x10000000000000000", 1, false}, + {"0x10000000000000000", "0xffffffffffffffff", -1, true}, + {"0x10000000000000001", "0xffffffffffffffff", -1, false}, + {"0x10000000000000001", "0x10000000000000000", 2, false}, + {"0x10000000000000001", "0x10000000000000001", 0, true}, + {"0x0", "0x1", 2, true}, + {"0x80000000", "0x0", std::numeric_limits::min(), true}, + {"0x80000001", "0x0", std::numeric_limits::min(), false}, + }; + for (int bytes = 9; bytes <= 33; ++bytes) { + auto lhs = std::make_unique(bytes + 1); + auto rhs = std::make_unique(bytes + 1); + XData a(bytes * 8, XData::InOut), b(bytes * 8, XData::InOut); + for (const auto &test : cases) { + a.Set(test.target); + b.Set(test.center); + const auto av = a.GetBytes(), bv = b.GetBytes(); + std::copy_n(av.begin(), bytes, lhs.get() + 1); + std::copy_n(bv.begin(), bytes, rhs.get() + 1); + ComUseRangeCheck check(test.range, bytes); + const auto x = reinterpret_cast(lhs.get() + 1); + const auto y = reinterpret_cast(rhs.get() + 1); + auto array_compare = check.GetArrayCmp(); + auto signal_compare = check.GetXDataCmp(); + REQUIRE(array_compare(x, y, check.CSelf()) == test.expected); + REQUIRE(ComUseRangeCheck::ArrayCmp(x, y, check.CSelf()) == test.expected); + REQUIRE(signal_compare(&a, &b, check.CSelf()) == test.expected); + } + // Borrow must propagate through every word, including a partial last word. + std::memset(lhs.get() + 1, 0xFF, bytes); + std::memset(rhs.get() + 1, 0, bytes); + lhs[bytes] = 0; + rhs[bytes] = 1; + ComUseRangeCheck check(1, bytes); + auto compare = check.GetArrayCmp(); + const auto x = reinterpret_cast(lhs.get() + 1); + const auto y = reinterpret_cast(rhs.get() + 1); + REQUIRE(compare(x, y, check.CSelf())); + rhs[bytes] = 2; + REQUIRE_FALSE(compare(x, y, check.CSelf())); + std::memset(lhs.get() + 1, 0xFF, bytes); + std::memset(rhs.get() + 1, 0, bytes); + REQUIRE_FALSE(compare(x, y, check.CSelf())); + ComUseRangeCheck above(-1, bytes); + REQUIRE_FALSE(ComUseRangeCheck::ArrayCmp(y, x, above.CSelf())); + } +} + +TEST_CASE("ComUseRangeCheck uses signal width and rejects unknowns", "[xcomuse_base]") { + // The byte count configures pointer comparisons; XData uses its actual width. + ComUseRangeCheck check(1, 8); + auto compare = check.GetXDataCmp(); + XData narrow(8, XData::InOut); + narrow.Set(1); + for (unsigned width : {65U, 127U, 128U, 129U, 256U}) { + XData a(width, XData::InOut), b(width, XData::InOut); + a.Set("0xffffffffffffffff"); + b.Set("0x10000000000000000"); + REQUIRE((a < b)); + REQUIRE(compare(&a, &b, check.CSelf())); + a.Set("0x10000000000000001"); + b.Set(1); + REQUIRE((a > b)); + REQUIRE_FALSE(compare(&a, &b, check.CSelf())); + a.Set(0); + REQUIRE(compare(&a, &narrow, check.CSelf())); + const std::string maximum = "0x" + std::string((width + 3) / 4, 'f'); + a.Set(maximum.c_str()); + b.Set(0); + REQUIRE_FALSE(compare(&a, &b, check.CSelf())); + a.Set(0); + a.pVecData[(width - 1) / 32].bval = 1; + REQUIRE_FALSE(compare(&a, &b, check.CSelf())); + } + narrow.Set("x"); + REQUIRE_FALSE(compare(&narrow, &narrow, check.CSelf())); +} + +TEST_CASE("ComUseRangeCheck refreshes each wide backend once without writing", "[xcomuse_base]") { + XData a(129, XData::InOut), b(129, XData::InOut); + uint32_t av[] = {UINT32_MAX, UINT32_MAX, 0, 0, 0}; + uint32_t bv[] = {0, 0, 1, 0, 0}; + int reads_a = 0, reads_b = 0, writes = 0; + auto bind = [&](XData &signal, uint32_t *words, int &reads) { + signal.BindDPIRW([words, &reads](void *data){ + ++reads; + auto *vec = (xsvLogicVecVal*)data; + for (int i = 0; i < 5; ++i) vec[i] = {words[i], 0}; + }, [&](void *){ ++writes; }); + }; + bind(a, av, reads_a); + bind(b, bv, reads_b); + reads_a = reads_b = 0; + ComUseRangeCheck check(1, 17); + auto compare = check.GetXDataCmp(); + REQUIRE(compare(&a, &b, check.CSelf())); + REQUIRE(reads_a == 1); + REQUIRE(reads_b == 1); + REQUIRE(writes == 0); + bv[4] = 1; + REQUIRE_FALSE(compare(&a, &b, check.CSelf())); + REQUIRE(reads_a == 2); + REQUIRE(reads_b == 2); + REQUIRE(writes == 0); +} + TEST_CASE("CString basic", "[xcomuse_base]") { CString s("abc"); REQUIRE(s.Get() == "abc"); diff --git a/tests/test_xdata.cpp b/tests/test_xdata.cpp index 1fb6cb9..661840d 100644 --- a/tests/test_xdata.cpp +++ b/tests/test_xdata.cpp @@ -1,6 +1,8 @@ #include "xspcomm/xcomm.h" #include "xspcomm/xinstance.h" +#include #include +#include using namespace xspcomm; @@ -51,6 +53,88 @@ static void test_native_storage() check(64, uint64_t(0x89ABCDEF01234567)); } +static void test_native_vector_boundaries() +{ + std::vector widths = {1, 8, 9, 16, 17, 31, 32, 33, 63, 64}; + for (unsigned words = 3; words <= 18; ++words) { + widths.push_back(words * 32 - 1); + widths.push_back(words * 32); + } + widths.push_back(1024); + for (unsigned width : widths) { + const unsigned words = (width + 31) / 32; + const unsigned bytes = width <= 8 ? 1 : width <= 16 ? 2 : words * 4; + // Leave exactly the native storage size after an unaligned address. + auto storage = std::make_unique(bytes + 1); + storage[0] = 0xA5; + auto* native = storage.get() + 1; + std::vector expected(words), actual(words); + const uint32_t last_mask = width % 32 ? (uint32_t(1) << (width % 32)) - 1 : UINT32_MAX; + for (unsigned i = 0; i < words; ++i) expected[i] = 0x9E3779B9U * (i + 1); + expected.back() &= last_mask; + auto store_native = [&](unsigned char* dst) { + if (width <= 8) { + *dst = static_cast(expected[0]); + } else if (width <= 16) { + const auto value = static_cast(expected[0]); + std::memcpy(dst, &value, sizeof(value)); + } else { + std::memcpy(dst, expected.data(), bytes); + } + }; + store_native(native); + XData signal(width, XData::InOut); + signal.BindNativeData(reinterpret_cast(native)); + signal.AsImmWrite(); + Assert(signal.GetBits(actual.data(), words) && actual == expected, + "XData(%u) native vector read failed", width); + + for (unsigned i = 0; i < words; ++i) { + signal.pVecData[i].bval = 1; + expected[i] = ~expected[i]; + } + expected.back() &= last_mask; + store_native(native); + Assert(!signal.GetBits(actual.data(), words) && actual == expected, + "XData(%u) native refresh changed the X/Z mask", width); + for (unsigned i = 0; i < words; ++i) + Assert(signal.pVecData[i].bval == 1, "native read changed bval[%u]", i); + + for (auto& word : expected) word ^= 0xA5A5A5A5U; + expected.back() &= last_mask; + signal.SetBits(expected.data(), words); + std::vector packed(bytes); + store_native(packed.data()); + Assert(std::memcmp(native, packed.data(), bytes) == 0 && storage[0] == 0xA5, + "XData(%u) native vector write crossed a boundary", width); + } +} + +static void test_native_reinit() +{ + for (auto widths : {std::make_pair(128U, 32U), std::make_pair(32U, 544U), + std::make_pair(544U, 64U), std::make_pair(512U, 513U)}) { + std::vector native(18, 0x12345678); + XData signal(widths.first, XData::InOut); + signal.BindNativeData(reinterpret_cast(native.data())); + signal.ReInit(widths.second, XData::InOut); + signal.AsImmWrite(); + const unsigned words = (widths.second + 31) / 32; + const uint32_t last_mask = widths.second % 32 ? + (uint32_t(1) << (widths.second % 32)) - 1 : UINT32_MAX; + std::vector actual(words), expected(words, 0x12345678); + expected.back() &= last_mask; + Assert(signal.GetBits(actual.data(), words) && actual == expected, + "native read used the old width after ReInit"); + expected.assign(words, 0x89ABCDEF); + expected.back() &= last_mask; + signal.SetBits(expected.data(), words); + Assert(std::equal(expected.begin(), expected.end(), native.begin()) && + native[words] == 0x12345678, + "native write used the old width after ReInit"); + } +} + static void test_signed_values_and_slices() { XData full(64, XData::InOut); @@ -110,6 +194,8 @@ int main(int argsc, const char **argsv) checkVersion(); test_optimized_signal_buffers(); test_native_storage(); + test_native_vector_boundaries(); + test_native_reinit(); test_signed_values_and_slices(); test_bit_buffer_boundaries(); test_xdata(); From d07b9880b21cbef2c78c9ce14fb34bfa9ef2e986 Mon Sep 17 00:00:00 2001 From: Makiras <22570332+Makiras@users.noreply.github.com> Date: Fri, 9 Oct 2026 15:30:29 +0800 Subject: [PATCH 04/10] refactor(comuse): split components and clarify range reads Split ComUse declarations into callback, condition, range, and utility headers under xspcomm/xcomuse/, with xcomuse.h as the aggregate entry point. Remove xcomuse_base.h and migrate expression and test includes to explicit dependencies. Preserve class layouts and existing callable APIs. Move range comparison internals into xcomuse_range.cpp. Express fixed-width reads through UIntWord/UIntParts specializations, using exact-length memcpy loads without overlapping reads or unaligned typed pointer dereferences. Retain callback selection at registration and full-width borrow comparison. Update the shared SWIG interface and recursively track component headers for Python wrapper regeneration. Move unresolved BindNativeData X/Z policy notes from the API reference to developer documentation without changing bindings. Validation: - C++17/C++20 standalone and mixed inclusion of seven public headers - Release and ASan/UBSan: all seven CTest targets pass in each build - Python suite and 4,000 arbitrary-precision comparison cases pass - Public Python exports and class sizes/alignment match the previous build - Client compiles and runs using only staged headers and the built library --- docs/APIs.cn.md | 12 +- docs/xdata_xclock_optimizations.md | 23 +++ include/xspcomm/xcomuse.h | 5 +- include/xspcomm/xcomuse/callback.h | 53 +++++ include/xspcomm/xcomuse/condition.h | 103 +++++++++ include/xspcomm/xcomuse/range.h | 63 ++++++ include/xspcomm/xcomuse/utils.h | 116 +++++++++++ include/xspcomm/xcomuse_base.h | 310 ---------------------------- include/xspcomm/xexpr.h | 4 +- src/CMakeLists.txt | 5 +- src/xcomuse_base.cpp | 59 +----- src/xcomuse_range.cpp | 110 ++++++++++ src/xdata.cpp | 2 +- swig/python/CMakeLists.txt | 6 +- swig/xcomm.i | 5 +- tests/test_xcomuse_base.cpp | 2 +- 16 files changed, 495 insertions(+), 383 deletions(-) create mode 100644 include/xspcomm/xcomuse/callback.h create mode 100644 include/xspcomm/xcomuse/condition.h create mode 100644 include/xspcomm/xcomuse/range.h create mode 100644 include/xspcomm/xcomuse/utils.h delete mode 100644 include/xspcomm/xcomuse_base.h create mode 100644 src/xcomuse_range.cpp diff --git a/docs/APIs.cn.md b/docs/APIs.cn.md index 2abc075..8ccab13 100644 --- a/docs/APIs.cn.md +++ b/docs/APIs.cn.md @@ -3,7 +3,7 @@ 本文档基于当前项目源码整理,主要对应以下文件: - 核心 C++ API:`include/xspcomm/xdata.h`、`xport.h`、`xclock.h`、`xsignal_cfg.h` -- 组合检查与表达式/FSM:`include/xspcomm/xcomuse_base.h`、`xexpr.h`、`xfsm.h` +- 组合检查与表达式/FSM:`include/xspcomm/xcomuse.h`、`xcomuse/`、`xexpr.h`、`xfsm.h` - 多语言封装:`swig/python/xcomm.py`、`swig/java/java.i`、`swig/scala/xsp.scala`、`swig/golang/golang.i`、`swig/lua/lua.i`、`swig/javascript/xspcomm.js` xspcomm 的核心抽象是: @@ -25,6 +25,8 @@ C++ API 位于 `xspcomm` 命名空间。常用头文件: #include "xspcomm/xcomuse.h" // ComUse、Expr、FSM 汇总 ``` +ComUse 组件位于 `xspcomm/xcomuse/`,可以按需包含 `callback.h`、`condition.h`、`range.h`、`utils.h`,分别提供回调、条件检查、范围比较和数组/字符串工具。 + 多语言 SWIG 包通常复用 C++ 名称,部分语言会增加更符合本语言习惯的包装方法,见“多语言差异”。 ### IOType @@ -200,14 +202,6 @@ auto sub = full.SubDataRef(30, 64, "sub"); 原生 vector 读写的访问粒度为 `uint8_t`(1~8 bit)、`uint16_t`(9~16 bit)和连续的 32-bit word(17 bit 及以上,64-bit 存储按两个 word 访问)。每个 word 对应 XData 的一个 `aval`,不包含 `bval`;读写支持未对齐的原生地址。 -**BindNativeData TODO(当前保留行为)** - -- [ ] 明确二态原生内存读取遇到已有 X/Z 状态时,绑定及每次刷新是否清理 `bval`。当前 vector 读取只更新 `aval`,保留 `bval`。 -- [ ] 明确 X/Z 写回二态存储的规则,并核对 scalar/vector 的语义。当前 vector 写回只保存 `aval`。 -- [ ] 补充重绑定、绑定后写入 X/Z,以及 scalar/vector 的回归覆盖。 - -新对象的 `bval` 由 `calloc` 初始化为零,但已有 X/Z 状态可能残留。已复现:先将 8-bit XData 设为全 X,再绑定值为 `0x5A` 的 `uint8_t`,读回 `U() == 0x5A`,却仍有 `XMask() == 0xFF`、`DataValid() == false`;16/64 bit 也有相同现象。 - ### 连接、比较、回调 | API | 说明 | diff --git a/docs/xdata_xclock_optimizations.md b/docs/xdata_xclock_optimizations.md index 231b7b0..9a7273f 100644 --- a/docs/xdata_xclock_optimizations.md +++ b/docs/xdata_xclock_optimizations.md @@ -110,3 +110,26 @@ Fix: - Check pVecData[i].bval in the loop; any nonzero makes validate=false. Behavior impact: - Callback validity flag becomes correct for wide vectors. + +--- + +## Follow-up: BindNativeData X/Z semantics + +Location: src/xdata.cpp : XData::BindNativeData + +Current behavior: + +- Native vector reads update only `aval` and preserve `bval`; writes store only `aval`. +- Fresh buffers have zeroed `bval` from `calloc`, but existing X/Z masks can survive binding and refresh. + +Reproducer: + +- Set an 8-bit XData to all X, then bind a `uint8_t` containing `0x5A`. +- `U() == 0x5A`, but `XMask() == 0xFF` and `DataValid() == false`. +- The same stale-mask behavior was reproduced at 16 and 64 bits. + +TODO: + +- [ ] Decide whether binding and refreshing two-state native storage should clear an existing `bval` mask. +- [ ] Define how X/Z writes map to two-state storage and check scalar/vector consistency. +- [ ] Add regressions for rebinding, X/Z writes after binding, and scalar/vector behavior. diff --git a/include/xspcomm/xcomuse.h b/include/xspcomm/xcomuse.h index 2574748..4945149 100644 --- a/include/xspcomm/xcomuse.h +++ b/include/xspcomm/xcomuse.h @@ -1,7 +1,10 @@ #ifndef __xspcomm_xcomuse_h__ #define __xspcomm_xcomuse_h__ -#include "xspcomm/xcomuse_base.h" +#include "xspcomm/xcomuse/callback.h" +#include "xspcomm/xcomuse/condition.h" +#include "xspcomm/xcomuse/range.h" +#include "xspcomm/xcomuse/utils.h" #include "xspcomm/xexpr.h" #include "xspcomm/xfsm.h" diff --git a/include/xspcomm/xcomuse/callback.h b/include/xspcomm/xcomuse/callback.h new file mode 100644 index 0000000..1372684 --- /dev/null +++ b/include/xspcomm/xcomuse/callback.h @@ -0,0 +1,53 @@ +#ifndef XSPCOMM_XCOMUSE_CALLBACK_H +#define XSPCOMM_XCOMUSE_CALLBACK_H + +#include "xspcomm/xutil.h" +#include + +namespace xspcomm { + + class XData; + + class ComUseStepCb{ + int cb_maxcts = -1; + int cb_counts = 0; + bool cb_enable = true; + public: + uint64_t cycle; + ComUseStepCb(){} + void Disable(); + void Enable(); + bool IsDisable(); + void SetMaxCbs(int c); + int GetCbCount(); + int IncCbCount(); + int DecCbCount(); + void Reset(); + static u_int64_t GetCb(); + u_int64_t CSelf(){return (u_int64_t)this;}; + static void Cb(uint64_t c, void *self); + virtual void Call(); + }; + + // Echo data when valid != 0 + class ComUseEcho: public ComUseStepCb{ + public: + bool stderr_echo; + XData* valid = NULL; + XData* data = NULL; + std::string fmt; + int convert; // 0 (char), 1 (int), 2 (float), 3 (double), 4 (string) + ComUseEcho(u_int64_t valid, u_int64_t data, + bool stderr_echo = true, + std::string fmt="%c", + int convert = 0): + stderr_echo(stderr_echo), fmt(fmt), convert(convert){ + this->valid = (XData*) valid; + this->data = (XData*) data; + } + virtual void Call(); + }; + +} // namespace xspcomm + +#endif diff --git a/include/xspcomm/xcomuse/condition.h b/include/xspcomm/xcomuse/condition.h new file mode 100644 index 0000000..c3f10a3 --- /dev/null +++ b/include/xspcomm/xcomuse/condition.h @@ -0,0 +1,103 @@ +#ifndef XSPCOMM_XCOMUSE_CONDITION_H +#define XSPCOMM_XCOMUSE_CONDITION_H + +#include "xspcomm/xcomuse/callback.h" +#include "xspcomm/xdata.h" +#include +#include +#include +#include + +namespace xspcomm { + + class XClock; + + // Condition Checker + enum class ComUseCondCmp{ + EQ = 0, + NE = 1, + GT = 2, + GE = 3, + LT = 4, + LE = 5, + }; + class ComUseCondCheck: public ComUseStepCb{ + using XDataCmpFn = bool (*)(XData*, XData*); + using PtrCmpFn = bool (*)(ComUseCondCheck*, uint64_t, uint64_t, int); + static bool XDataCmpEq(XData* a, XData* b){ return *a == *b; } + static bool XDataCmpNe(XData* a, XData* b){ return *a != *b; } + static bool XDataCmpGt(XData* a, XData* b){ return *a > *b; } + static bool XDataCmpGe(XData* a, XData* b){ return *a >= *b; } + static bool XDataCmpLt(XData* a, XData* b){ return *a < *b; } + static bool XDataCmpLe(XData* a, XData* b){ return *a <= *b; } + static bool PtrCmpEq(ComUseCondCheck* self, uint64_t a, uint64_t b, int bytes){ return self->_valcmp(a, b, bytes) == 0; } + static bool PtrCmpNe(ComUseCondCheck* self, uint64_t a, uint64_t b, int bytes){ return self->_valcmp(a, b, bytes) != 0; } + static bool PtrCmpGt(ComUseCondCheck* self, uint64_t a, uint64_t b, int bytes){ return self->_valcmp(a, b, bytes) > 0; } + static bool PtrCmpGe(ComUseCondCheck* self, uint64_t a, uint64_t b, int bytes){ return self->_valcmp(a, b, bytes) >= 0; } + static bool PtrCmpLt(ComUseCondCheck* self, uint64_t a, uint64_t b, int bytes){ return self->_valcmp(a, b, bytes) < 0; } + static bool PtrCmpLe(ComUseCondCheck* self, uint64_t a, uint64_t b, int bytes){ return self->_valcmp(a, b, bytes) <= 0; } + static XDataCmpFn SelectXDataCmpFn(ComUseCondCmp cmp); + static PtrCmpFn SelectPtrCmpFn(ComUseCondCmp cmp); + std::vector clk_list; + struct CondXDataEntry { + std::string name; + XData* pin = nullptr; + XData* val = nullptr; + ComUseCondCmp cmp = ComUseCondCmp::EQ; + XData* valid = nullptr; + XData* valid_value = nullptr; + xfunction func = nullptr; + int triggered = 0; + uint64_t arg = 0; + ComUseCondCmp valid_cmp = ComUseCondCmp::EQ; + XDataCmpFn cmp_fn = nullptr; + XDataCmpFn valid_cmp_fn = nullptr; + }; + struct CondUint64Entry { + std::string name; + uint64_t pin_ptr = 0; + uint64_t val_ptr = 0; + ComUseCondCmp cmp = ComUseCondCmp::EQ; + int bytes = 0; + uint64_t valid_ptr = 0; + uint64_t valid_value_ptr = 0; + int valid_bytes = 1; + xfunction func = nullptr; + int triggered = 0; + uint64_t arg = 0; + ComUseCondCmp valid_cmp = ComUseCondCmp::EQ; + PtrCmpFn cmp_fn = nullptr; + PtrCmpFn valid_cmp_fn = nullptr; + }; + std::unordered_map cond_idx_xdata; + std::unordered_map cond_idx_uint64; + std::vector cond_vec_xdata; + std::vector cond_vec_uint64; + static bool RemoveXDataCond(std::unordered_map &idx, + std::vector &vec, + const std::string &name); + static bool RemoveUint64Cond(std::unordered_map &idx, + std::vector &vec, + const std::string &name); + int _valcmp(uint64_t a, u_int64_t b, int bytes); + public: + ComUseCondCheck(XClock* clk=nullptr){if(clk)this->clk_list.push_back(clk);} + void BindXClock(XClock *clk); + void SetCondition(std::string unique_name, XData* pin, XData* val, ComUseCondCmp cmp, XData *valid = nullptr, XData *valid_value = nullptr, xfunction func = nullptr, uint64_t arg=0); + void SetCondition(std::string unique_name, uint64_t pin_ptr, uint64_t val_ptr, ComUseCondCmp cmp, int bytes, uint64_t valid_ptr = 0, uint64_t valid_value_ptr = 0, int valid_bytes = 1, xfunction func = nullptr, uint64_t arg=0); + ComUseCondCmp GetValidCmpMode(std::string unique_name); + void SetValidCmpMode(std::string unique_name, ComUseCondCmp cmp); + void RemoveCondition(std::string unique_name); + std::map ListCondition(); + std::vector GetTriggeredConditionKeys(); + void ClearClock(); + void ClearCondition(); + void ClearAll(){this->ClearClock(); this->ClearCondition();}; + virtual void Call(); + xfunction AsXDataXFunc(uint64_t func); + xfunction AsPtrXFunc(uint64_t func); + }; + +} // namespace xspcomm + +#endif diff --git a/include/xspcomm/xcomuse/range.h b/include/xspcomm/xcomuse/range.h new file mode 100644 index 0000000..2527f15 --- /dev/null +++ b/include/xspcomm/xcomuse/range.h @@ -0,0 +1,63 @@ +#ifndef XSPCOMM_XCOMUSE_RANGE_H +#define XSPCOMM_XCOMUSE_RANGE_H + +#include "xspcomm/xcallback.h" +#include "xspcomm/xutil.h" +#include + +namespace xspcomm { + + class XData; + + class ComUseRangeCheck { + int bytes; + int range; + + template + static bool Compare(uint64_t a, uint64_t b, uint64_t self); + static bool ArrayWideCmp(uint64_t a, uint64_t b, uint64_t self); + using ArrayCompare = bool (*)(uint64_t, uint64_t, uint64_t); + static const ArrayCompare comparisons[8]; + static ArrayCompare Select(int bytes) { + return bytes <= 8 ? comparisons[bytes - 1] : ArrayWideCmp; + } + public: + ComUseRangeCheck(int range, int bytes) : bytes(bytes), range(range) { + Assert(bytes >= 1, "Need bytes >= 1"); + } + + static bool cmp(uint64_t target, uint64_t center, int range) { + if (range >= 0) { + return target <= center && center - target <= static_cast(range); + } + return target >= center && target - center <= static_cast(-static_cast(range)); + } + + static bool ArrayCmp(uint64_t a, uint64_t b, uint64_t self) { + const auto* check = reinterpret_cast(self); + if (check->bytes == sizeof(uint64_t)) { + uint64_t target, center; + std::memcpy(&target, reinterpret_cast(a), sizeof(target)); + std::memcpy(¢er, reinterpret_cast(b), sizeof(center)); + return cmp(target, center, check->range); + } + return Select(check->bytes)(a, b, self); + } + static bool XDataCmp(XData* a, XData* b, uint64_t self); + + uint64_t CSelf() { + return reinterpret_cast(this); + } + + xfunction GetArrayCmp() { + // Select once when creating the callback, outside the hot path. + return Select(this->bytes); + } + xfunction GetXDataCmp() { + return XDataCmp; + } + }; + +} // namespace xspcomm + +#endif diff --git a/include/xspcomm/xcomuse/utils.h b/include/xspcomm/xcomuse/utils.h new file mode 100644 index 0000000..379a7ee --- /dev/null +++ b/include/xspcomm/xcomuse/utils.h @@ -0,0 +1,116 @@ +#ifndef XSPCOMM_XCOMUSE_UTILS_H +#define XSPCOMM_XCOMUSE_UTILS_H + +#include "xspcomm/xutil.h" +#include +#include +#include +#include + +namespace xspcomm { + + // Get Array Item + uint64_t GetFromU64Array(uint64_t address, int index); + uint32_t GetFromU32Array(uint64_t address, int index); + uint8_t GetFromU8Array(uint64_t address, int index); + + uint64_t GetFromU64Array(unsigned long long *address, int index); + uint32_t GetFromU32Array(unsigned int *address, int index); + uint8_t GetFromU8Array(unsigned char *address, int index); + + // Get Array Item + void SetU64Array(uint64_t address, int index, uint64_t data); + void SetU32Array(uint64_t address, int index, uint32_t data); + void SetU8Array(uint64_t address, int index, uint8_t data); + + void SetU64Array(unsigned long long *address, int index, uint64_t data); + void SetU32Array(unsigned int *address, int index, uint32_t data); + void SetU8Array(unsigned char *address, int index, uint8_t data); + + // Ptr As U64 + uint64_t U64PtrAsU64(unsigned long long *p); + uint64_t U32PtrAsU64(unsigned int *p); + uint64_t U8PtrAsU64(unsigned char *p); + unsigned long long *U64AsU64Ptr(uint64_t p); + unsigned int *U64AsU32Ptr(uint64_t p); + unsigned char *U64AsU8Ptr(uint64_t p); + + class ComUseDataArray{ + bool is_ref = false; + int byte_size; + int *buffer = nullptr; + public: + ComUseDataArray(int byte_size):byte_size(byte_size){ + Assert(byte_size > 0, "Need size > 0"); + int n = byte_size/4 + (byte_size % 4 == 0 ? 0:1); + this->buffer = new int[n]; + memset(this->buffer, 0, this->byte_size); + } + ComUseDataArray(uint64_t base, int byte_size):byte_size(byte_size){ + this->buffer = (int*)base; + this->is_ref = true; + } + ~ComUseDataArray(){if(!this->is_ref)delete[] this->buffer;} + bool operator==(const ComUseDataArray & t) const{ + if(this->byte_size != t.byte_size)return false; + return memcmp(this->buffer, t.buffer, this->byte_size) == 0; + } + ComUseDataArray * Copy(){ + auto ret = new ComUseDataArray(this->byte_size); + ret->SyncFrom(this->BaseAddr(), this->byte_size); + return ret; + } + void SyncFrom(uint64_t addr, int size){ + memcpy(this->buffer, (void*)addr, size); + } + void SyncTo(uint64_t addr, int size){ + memcpy((void*)addr, this->buffer, size); + } + void SetZero(){memset(this->buffer, 0, this->byte_size);} + uint64_t BaseAddr(){return (u_int64_t)this->buffer;} + int Size(){return this->byte_size;} + std::vector AsBytes(){ + std::vector ret; + unsigned char * base = (unsigned char*)this->buffer; + for(int i=0; ibyte_size; i++){ + ret.push_back(base[i]); + } + return ret; + } + int FromBytes(std::vector &input){ + auto size = std::min(this->byte_size, (int)input.size()); + unsigned char * base = (unsigned char*)this->buffer; + for(int i=0; i < size; i++){ + base[i] = input[i]; + } + return size; + } + }; + + class CString { + public: + std::string str; + CString(std::string val):str(val){} + CString():str(""){} + uint64_t CharAddress(){ + return (uint64_t)this->str.c_str(); + } + void AssignTo(char * addr){ + addr = (char *)this->str.c_str(); + } + void AssignTo(uint64_t addr){ + return this->AssignTo((char*)addr); + } + void AssignFrom(const char * val){ + this->str = std::string(val); + } + void AssignFrom(uint64_t val){ + return this->AssignFrom((const char*)val); + } + std::string Get(){return this->str;} + void Set(std::string val){this->str = val;} + }; + +} // namespace xspcomm + +#endif diff --git a/include/xspcomm/xcomuse_base.h b/include/xspcomm/xcomuse_base.h deleted file mode 100644 index 49d9d1b..0000000 --- a/include/xspcomm/xcomuse_base.h +++ /dev/null @@ -1,310 +0,0 @@ -#ifndef __xspcomm_xcomuse_base_h__ -#define __xspcomm_xcomuse_base_h__ - -#include "xspcomm/xcomm.h" -#include -#include -#include -#include -#include -#include -#include -#include - -namespace xspcomm { - - class ComUseStepCb{ - int cb_maxcts = -1; - int cb_counts = 0; - bool cb_enable = true; - public: - uint64_t cycle; - ComUseStepCb(){} - void Disable(); - void Enable(); - bool IsDisable(); - void SetMaxCbs(int c); - int GetCbCount(); - int IncCbCount(); - int DecCbCount(); - void Reset(); - static u_int64_t GetCb(); - u_int64_t CSelf(){return (u_int64_t)this;}; - static void Cb(uint64_t c, void *self); - virtual void Call(); - }; - - // Echo data when valid != 0 - class ComUseEcho: public ComUseStepCb{ - public: - bool stderr_echo; - XData* valid = NULL; - XData* data = NULL; - std::string fmt; - int convert; // 0 (char), 1 (int), 2 (float), 3 (double), 4 (string) - ComUseEcho(u_int64_t valid, u_int64_t data, - bool stderr_echo = true, - std::string fmt="%c", - int convert = 0): - stderr_echo(stderr_echo), fmt(fmt), convert(convert){ - this->valid = (XData*) valid; - this->data = (XData*) data; - } - virtual void Call(); - }; - - // Get Array Item - uint64_t GetFromU64Array(uint64_t address, int index); - uint32_t GetFromU32Array(uint64_t address, int index); - uint8_t GetFromU8Array(uint64_t address, int index); - - uint64_t GetFromU64Array(unsigned long long *address, int index); - uint32_t GetFromU32Array(unsigned int *address, int index); - uint8_t GetFromU8Array(unsigned char *address, int index); - - // Get Array Item - void SetU64Array(uint64_t address, int index, uint64_t data); - void SetU32Array(uint64_t address, int index, uint32_t data); - void SetU8Array(uint64_t address, int index, uint8_t data); - - void SetU64Array(unsigned long long *address, int index, uint64_t data); - void SetU32Array(unsigned int *address, int index, uint32_t data); - void SetU8Array(unsigned char *address, int index, uint8_t data); - - // Ptr As U64 - uint64_t U64PtrAsU64(unsigned long long *p); - uint64_t U32PtrAsU64(unsigned int *p); - uint64_t U8PtrAsU64(unsigned char *p); - unsigned long long *U64AsU64Ptr(uint64_t p); - unsigned int *U64AsU32Ptr(uint64_t p); - unsigned char *U64AsU8Ptr(uint64_t p); - - // Condition Checker - enum class ComUseCondCmp{ - EQ = 0, - NE = 1, - GT = 2, - GE = 3, - LT = 4, - LE = 5, - }; - class ComUseCondCheck: public ComUseStepCb{ - using XDataCmpFn = bool (*)(XData*, XData*); - using PtrCmpFn = bool (*)(ComUseCondCheck*, uint64_t, uint64_t, int); - static bool XDataCmpEq(XData* a, XData* b){ return *a == *b; } - static bool XDataCmpNe(XData* a, XData* b){ return *a != *b; } - static bool XDataCmpGt(XData* a, XData* b){ return *a > *b; } - static bool XDataCmpGe(XData* a, XData* b){ return *a >= *b; } - static bool XDataCmpLt(XData* a, XData* b){ return *a < *b; } - static bool XDataCmpLe(XData* a, XData* b){ return *a <= *b; } - static bool PtrCmpEq(ComUseCondCheck* self, uint64_t a, uint64_t b, int bytes){ return self->_valcmp(a, b, bytes) == 0; } - static bool PtrCmpNe(ComUseCondCheck* self, uint64_t a, uint64_t b, int bytes){ return self->_valcmp(a, b, bytes) != 0; } - static bool PtrCmpGt(ComUseCondCheck* self, uint64_t a, uint64_t b, int bytes){ return self->_valcmp(a, b, bytes) > 0; } - static bool PtrCmpGe(ComUseCondCheck* self, uint64_t a, uint64_t b, int bytes){ return self->_valcmp(a, b, bytes) >= 0; } - static bool PtrCmpLt(ComUseCondCheck* self, uint64_t a, uint64_t b, int bytes){ return self->_valcmp(a, b, bytes) < 0; } - static bool PtrCmpLe(ComUseCondCheck* self, uint64_t a, uint64_t b, int bytes){ return self->_valcmp(a, b, bytes) <= 0; } - static XDataCmpFn SelectXDataCmpFn(ComUseCondCmp cmp); - static PtrCmpFn SelectPtrCmpFn(ComUseCondCmp cmp); - std::vector clk_list; - struct CondXDataEntry { - std::string name; - XData* pin = nullptr; - XData* val = nullptr; - ComUseCondCmp cmp = ComUseCondCmp::EQ; - XData* valid = nullptr; - XData* valid_value = nullptr; - xfunction func = nullptr; - int triggered = 0; - uint64_t arg = 0; - ComUseCondCmp valid_cmp = ComUseCondCmp::EQ; - XDataCmpFn cmp_fn = nullptr; - XDataCmpFn valid_cmp_fn = nullptr; - }; - struct CondUint64Entry { - std::string name; - uint64_t pin_ptr = 0; - uint64_t val_ptr = 0; - ComUseCondCmp cmp = ComUseCondCmp::EQ; - int bytes = 0; - uint64_t valid_ptr = 0; - uint64_t valid_value_ptr = 0; - int valid_bytes = 1; - xfunction func = nullptr; - int triggered = 0; - uint64_t arg = 0; - ComUseCondCmp valid_cmp = ComUseCondCmp::EQ; - PtrCmpFn cmp_fn = nullptr; - PtrCmpFn valid_cmp_fn = nullptr; - }; - std::unordered_map cond_idx_xdata; - std::unordered_map cond_idx_uint64; - std::vector cond_vec_xdata; - std::vector cond_vec_uint64; - static bool RemoveXDataCond(std::unordered_map &idx, - std::vector &vec, - const std::string &name); - static bool RemoveUint64Cond(std::unordered_map &idx, - std::vector &vec, - const std::string &name); - int _valcmp(uint64_t a, u_int64_t b, int bytes); - public: - ComUseCondCheck(XClock* clk=nullptr){if(clk)this->clk_list.push_back(clk);} - void BindXClock(XClock *clk); - void SetCondition(std::string unique_name, XData* pin, XData* val, ComUseCondCmp cmp, XData *valid = nullptr, XData *valid_value = nullptr, xfunction func = nullptr, uint64_t arg=0); - void SetCondition(std::string unique_name, uint64_t pin_ptr, uint64_t val_ptr, ComUseCondCmp cmp, int bytes, uint64_t valid_ptr = 0, uint64_t valid_value_ptr = 0, int valid_bytes = 1, xfunction func = nullptr, uint64_t arg=0); - ComUseCondCmp GetValidCmpMode(std::string unique_name); - void SetValidCmpMode(std::string unique_name, ComUseCondCmp cmp); - void RemoveCondition(std::string unique_name); - std::map ListCondition(); - std::vector GetTriggeredConditionKeys(); - void ClearClock(); - void ClearCondition(); - void ClearAll(){this->ClearClock(); this->ClearCondition();}; - virtual void Call(); - xfunction AsXDataXFunc(uint64_t func); - xfunction AsPtrXFunc(uint64_t func); - }; - - class ComUseDataArray{ - bool is_ref = false; - int byte_size; - int *buffer = nullptr; - public: - ComUseDataArray(int byte_size):byte_size(byte_size){ - Assert(byte_size > 0, "Need size > 0"); - int n = byte_size/4 + (byte_size % 4 == 0 ? 0:1); - this->buffer = new int[n]; - memset(this->buffer, 0, this->byte_size); - } - ComUseDataArray(uint64_t base, int byte_size):byte_size(byte_size){ - this->buffer = (int*)base; - this->is_ref = true; - } - ~ComUseDataArray(){if(!this->is_ref)delete[] this->buffer;} - bool operator==(const ComUseDataArray & t) const{ - if(this->byte_size != t.byte_size)return false; - return memcmp(this->buffer, t.buffer, this->byte_size) == 0; - } - ComUseDataArray * Copy(){ - auto ret = new ComUseDataArray(this->byte_size); - ret->SyncFrom(this->BaseAddr(), this->byte_size); - return ret; - } - void SyncFrom(uint64_t addr, int size){ - memcpy(this->buffer, (void*)addr, size); - } - void SyncTo(uint64_t addr, int size){ - memcpy((void*)addr, this->buffer, size); - } - void SetZero(){memset(this->buffer, 0, this->byte_size);} - uint64_t BaseAddr(){return (u_int64_t)this->buffer;} - int Size(){return this->byte_size;} - std::vector AsBytes(){ - std::vector ret; - unsigned char * base = (unsigned char*)this->buffer; - for(int i=0; ibyte_size; i++){ - ret.push_back(base[i]); - } - return ret; - } - int FromBytes(std::vector &input){ - auto size = std::min(this->byte_size, (int)input.size()); - unsigned char * base = (unsigned char*)this->buffer; - for(int i=0; i < size; i++){ - base[i] = input[i]; - } - return size; - } - }; - - class ComUseRangeCheck { - int bytes; - int range; - template - static uint64_t Read(uint64_t address){ - T value; - std::memcpy(&value, (const void *)address, sizeof(value)); - return value; - } - template - static uint64_t Load(uint64_t address){ - if constexpr(Bytes == 1) return Read(address); - else if constexpr(Bytes == 2) return Read(address); - else if constexpr(Bytes == 4) return Read(address); - else if constexpr(Bytes == 8) return Read(address); - // Overlapping loads cover 3/5/6/7 bytes without reading past the end. - else if constexpr(Bytes == 3) return Read(address) | (Read(address + 1) << 8); - else return Read(address) | (Read(address + Bytes - 4) << ((Bytes - 4) * 8)); - } - template - static bool Compare(uint64_t a, uint64_t b, uint64_t self){ - return cmp(Load(a), Load(b), ((ComUseRangeCheck*)self)->range); - } - static bool ArrayWideCmp(uint64_t a, uint64_t b, uint64_t self); - using ArrayCompare = bool (*)(uint64_t, uint64_t, uint64_t); - static ArrayCompare Select(int bytes){ - switch(bytes){ - case 1: return Compare<1>; - case 2: return Compare<2>; - case 3: return Compare<3>; - case 4: return Compare<4>; - case 5: return Compare<5>; - case 6: return Compare<6>; - case 7: return Compare<7>; - case 8: return Compare<8>; - default: return ArrayWideCmp; - } - } - public: - ComUseRangeCheck(int range, int bytes):bytes(bytes), range(range){ - Assert(bytes >= 1, "Need bytes >= 1"); - } - static bool cmp(uint64_t t, uint64_t c, int r){ - if(r >= 0){ - return t <= c && c - t <= (uint32_t)r; - } - return t >= c && t - c <= (uint64_t)(-(int64_t)r); - } - static bool ArrayCmp(uint64_t a, uint64_t b, uint64_t self){ - ComUseRangeCheck * p = (ComUseRangeCheck*)self; - if(p->bytes == 8) return Compare<8>(a, b, self); - auto compare = Select(p->bytes); - return compare && compare(a, b, self); - } - static bool XDataCmp(XData *a, XData *b, uint64_t self); - uint64_t CSelf(){return (uint64_t)this;} - xfunction GetArrayCmp(){ - // Select once when creating the callback, outside the hot path. - return Select(this->bytes); - } - xfunction GetXDataCmp(){ - return (bool (*)(XData*, XData*, uint64_t))XDataCmp; - } - }; - - class CString { - public: - std::string str; - CString(std::string val):str(val){} - CString():str(""){} - uint64_t CharAddress(){ - return (uint64_t)this->str.c_str(); - } - void AssignTo(char * addr){ - addr = (char *)this->str.c_str(); - } - void AssignTo(uint64_t addr){ - return this->AssignTo((char*)addr); - } - void AssignFrom(const char * val){ - this->str = std::string(val); - } - void AssignFrom(uint64_t val){ - return this->AssignFrom((const char*)val); - } - std::string Get(){return this->str;} - void Set(std::string val){this->str = val;} - }; -} - -#endif diff --git a/include/xspcomm/xexpr.h b/include/xspcomm/xexpr.h index 85057de..b58a2ef 100644 --- a/include/xspcomm/xexpr.h +++ b/include/xspcomm/xexpr.h @@ -1,7 +1,9 @@ #ifndef __xspcomm_xexpr_h__ #define __xspcomm_xexpr_h__ -#include "xspcomm/xcomuse_base.h" +#include "xspcomm/xcomuse/callback.h" +#include "xspcomm/xclock.h" +#include "xspcomm/xsignal_cfg.h" #include #include #include diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 800cfd6..47f4eb6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,9 @@ include_directories(../include) -set(SRC_FILES xclock.cpp xdata.cpp xport.cpp xutil.cpp xcoroutine.cpp xsignal_cfg.cpp xinstance.cpp xcomuse_base.cpp xexpr_engine.cpp xexpr_parser.cpp xfsm_trigger.cpp xtrigger.cpp) +set(SRC_FILES + xclock.cpp xdata.cpp xport.cpp xutil.cpp xcoroutine.cpp xsignal_cfg.cpp xinstance.cpp + xcomuse_base.cpp xcomuse_range.cpp + xexpr_engine.cpp xexpr_parser.cpp xfsm_trigger.cpp xtrigger.cpp) if(${XSPCOMM_WASM_LIB}) set(CMAKE_C_COMPILER emcc) diff --git a/src/xcomuse_base.cpp b/src/xcomuse_base.cpp index db6b545..6ab09c8 100644 --- a/src/xcomuse_base.cpp +++ b/src/xcomuse_base.cpp @@ -1,61 +1,10 @@ - -#include "xspcomm/xcomuse_base.h" - -#include -#include -#include - +#include "xspcomm/xclock.h" +#include "xspcomm/xcomuse/callback.h" +#include "xspcomm/xcomuse/condition.h" +#include "xspcomm/xcomuse/utils.h" namespace xspcomm { -namespace { -template -bool range_compare(size_t words, int range, ReadA a, ReadB b){ - const uint32_t limit = range >= 0 ? (uint32_t)range : (uint32_t)(-(int64_t)range); - uint64_t borrow = 0; - for(size_t i = 0; i < words; ++i){ - const uint64_t lhs = range >= 0 ? b(i) : a(i); - const uint64_t rhs = (range >= 0 ? a(i) : b(i)) + borrow; - const uint32_t difference = (uint32_t)(lhs - rhs); - borrow = lhs < rhs; - // The tolerance fits in the low word; all higher difference words must be zero. - if(difference > (i == 0 ? limit : 0U)) return false; - } - return borrow == 0; -} -} - -bool ComUseRangeCheck::ArrayWideCmp(uint64_t a, uint64_t b, uint64_t self){ - auto *check = (ComUseRangeCheck*)self; - const size_t bytes = check->bytes; - auto word = [bytes](uint64_t address, size_t i) -> uint32_t { - const size_t offset = i * 4; - if(bytes - offset >= 4) return (uint32_t)Read(address + offset); - switch(bytes - offset){ - case 1: return (uint32_t)Load<1>(address + offset); - case 2: return (uint32_t)Load<2>(address + offset); - default: return (uint32_t)Load<3>(address + offset); - } - }; - return range_compare((bytes + 3) / 4, check->range, - [=](size_t i){ return word(a, i); }, - [=](size_t i){ return word(b, i); }); -} - -bool ComUseRangeCheck::XDataCmp(XData *a, XData *b, uint64_t self){ - // Refresh each signal once; subsequent word reads use its existing cache. - const uint64_t av = a->U(); - const uint64_t bv = b->U(); - if(!a->DataValid() || !b->DataValid()) return false; - const int range = ((ComUseRangeCheck*)self)->range; - if(a->W() <= 64 && b->W() <= 64) return cmp(av, bv, range); - const size_t awords = ((uint64_t)a->W() + 31) / 32; - const size_t bwords = ((uint64_t)b->W() + 31) / 32; - return range_compare(std::max(awords, bwords), range, - [=](size_t i){ return i == 0 ? (uint32_t)av : (i < awords ? a->pVecData[i].aval : 0U); }, - [=](size_t i){ return i == 0 ? (uint32_t)bv : (i < bwords ? b->pVecData[i].aval : 0U); }); -} - u_int64_t ComUseStepCb::GetCb(){ return (u_int64_t)ComUseStepCb::Cb; } diff --git a/src/xcomuse_range.cpp b/src/xcomuse_range.cpp new file mode 100644 index 0000000..95b3f16 --- /dev/null +++ b/src/xcomuse_range.cpp @@ -0,0 +1,110 @@ +#include "xspcomm/xcomuse/range.h" +#include "xspcomm/xdata.h" + +#include +#include + +namespace xspcomm { + +namespace { +template struct UIntBytes; + +template +struct UIntWord { + static uint64_t Load(const unsigned char* data) { + T value; + std::memcpy(&value, data, sizeof(value)); + return value; + } +}; + +template +struct UIntParts { + static uint64_t Load(const unsigned char* data) { + return UIntBytes::Load(data) | + (UIntBytes::Load(data + LowBytes) << (LowBytes * 8)); + } +}; + +// Fixed byte widths use native integers or consecutive low/high parts. +#define XSPCOMM_UINT_BYTES(Bytes, ...) \ + template <> struct UIntBytes : __VA_ARGS__ {}; +XSPCOMM_UINT_BYTES(1, UIntWord) +XSPCOMM_UINT_BYTES(2, UIntWord) +XSPCOMM_UINT_BYTES(3, UIntParts<2, 1>) +XSPCOMM_UINT_BYTES(4, UIntWord) +XSPCOMM_UINT_BYTES(5, UIntParts<4, 1>) +XSPCOMM_UINT_BYTES(6, UIntParts<4, 2>) +XSPCOMM_UINT_BYTES(7, UIntParts<6, 1>) +XSPCOMM_UINT_BYTES(8, UIntWord) +#undef XSPCOMM_UINT_BYTES + +template +bool range_compare(size_t words, int range, ReadA a, ReadB b) { + const uint32_t limit = static_cast(range >= 0 ? range : -static_cast(range)); + uint64_t borrow = 0; + for (size_t i = 0; i < words; ++i) { + const uint64_t lhs = range >= 0 ? b(i) : a(i); + const uint64_t rhs = (range >= 0 ? a(i) : b(i)) + borrow; + const uint32_t difference = static_cast(lhs - rhs); + borrow = lhs < rhs; + // The tolerance fits in the low word; all higher difference words must be zero. + if (difference > (i == 0 ? limit : 0U)) return false; + } + return borrow == 0; +} +} + +template +bool ComUseRangeCheck::Compare(uint64_t a, uint64_t b, uint64_t self) { + const auto* check = reinterpret_cast(self); + const auto target = UIntBytes::Load(reinterpret_cast(a)); + const auto center = UIntBytes::Load(reinterpret_cast(b)); + return cmp(target, center, check->range); +} + +const ComUseRangeCheck::ArrayCompare ComUseRangeCheck::comparisons[] = { + Compare<1>, Compare<2>, Compare<3>, Compare<4>, + Compare<5>, Compare<6>, Compare<7>, Compare<8>, +}; + +bool ComUseRangeCheck::ArrayWideCmp(uint64_t a, uint64_t b, uint64_t self) { + const auto* check = reinterpret_cast(self); + const size_t bytes = check->bytes; + auto word = [bytes](uint64_t address, size_t i) { + const size_t offset = i * 4; + const size_t count = std::min(sizeof(uint32_t), bytes - offset); + const auto* data = reinterpret_cast(address) + offset; + if (count == sizeof(uint32_t)) return UIntBytes<4>::Load(data); + switch (count) { + case 1: return UIntBytes<1>::Load(data); + case 2: return UIntBytes<2>::Load(data); + default: return UIntBytes<3>::Load(data); + } + }; + return range_compare((bytes + 3) / 4, check->range, + [=](size_t i) { return word(a, i); }, + [=](size_t i) { return word(b, i); }); +} + +bool ComUseRangeCheck::XDataCmp(XData* a, XData* b, uint64_t self) { + // Refresh each signal once; subsequent word reads use its existing cache. + const uint64_t av = a->U(); + const uint64_t bv = b->U(); + if (!a->DataValid() || !b->DataValid()) return false; + const int range = reinterpret_cast(self)->range; + if (a->W() <= 64 && b->W() <= 64) return cmp(av, bv, range); + const size_t awords = (static_cast(a->W()) + 31) / 32; + const size_t bwords = (static_cast(b->W()) + 31) / 32; + return range_compare(std::max(awords, bwords), range, + [=](size_t i) { + if (i == 0) return static_cast(av); + return i < awords ? a->pVecData[i].aval : 0U; + }, + [=](size_t i) { + if (i == 0) return static_cast(bv); + return i < bwords ? b->pVecData[i].aval : 0U; + }); +} + +} // namespace xspcomm diff --git a/src/xdata.cpp b/src/xdata.cpp index c186802..29e3b37 100644 --- a/src/xdata.cpp +++ b/src/xdata.cpp @@ -734,7 +734,7 @@ void XData::BindDPIRW(void (*read)(void *), void (*write)(const unsigned char)) } // TODO: Define how native two-state reads handle an existing X/Z mask and how // X/Z writes map to native storage. Preserve bval until that policy is settled; -// see docs/APIs.cn.md (BindNativeData TODO). +// see docs/xdata_xclock_optimizations.md (BindNativeData X/Z semantics). void XData::BindNativeData(uint64_t pdata){ if (this->mWidth == 0){ auto* native = reinterpret_cast(pdata); diff --git a/swig/python/CMakeLists.txt b/swig/python/CMakeLists.txt index bb4fb52..9ece1eb 100644 --- a/swig/python/CMakeLists.txt +++ b/swig/python/CMakeLists.txt @@ -5,9 +5,9 @@ find_package(Python3 REQUIRED COMPONENTS Development.Module) set(SWIG_OUTFILE_DIR ${CMAKE_CURRENT_BINARY_DIR}/_pyswig) # CMake's SWIG scanner does not reliably follow the nested %include chain in -# python.i -> xcomm.i. Track public headers explicitly so incremental builds -# cannot leave a stale Python wrapper after an ABI change. -file(GLOB XSPCOMM_SWIG_HEADERS CONFIGURE_DEPENDS +# python.i -> xcomm.i. Track public headers recursively, including component +# subdirectories, so incremental builds cannot leave a stale Python wrapper. +file(GLOB_RECURSE XSPCOMM_SWIG_HEADERS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/../../include/xspcomm/*.h") set_property(SOURCE python.i PROPERTY DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/../xcomm.i;${XSPCOMM_SWIG_HEADERS}") diff --git a/swig/xcomm.i b/swig/xcomm.i index cfe5c67..e44df0b 100644 --- a/swig/xcomm.i +++ b/swig/xcomm.i @@ -63,7 +63,10 @@ namespace std { %include "xspcomm/xport.h" %include "xspcomm/xutil.h" %include "xspcomm/xsignal_cfg.h" -%include "xspcomm/xcomuse_base.h" +%include "xspcomm/xcomuse/callback.h" +%include "xspcomm/xcomuse/condition.h" +%include "xspcomm/xcomuse/range.h" +%include "xspcomm/xcomuse/utils.h" %include "xspcomm/xexpr.h" %include "xspcomm/xfsm.h" %include "xspcomm/xcomuse.h" diff --git a/tests/test_xcomuse_base.cpp b/tests/test_xcomuse_base.cpp index a74538d..21a4f4b 100644 --- a/tests/test_xcomuse_base.cpp +++ b/tests/test_xcomuse_base.cpp @@ -1,7 +1,7 @@ #define CATCH_CONFIG_MAIN #include "catch.hpp" -#include "xspcomm/xcomuse_base.h" +#include "xspcomm/xcomuse.h" #include "xspcomm/xclock.h" #include "xspcomm/xdata.h" #include From b3468620304ab0f29e99927bdc5edbc42eccd2e4 Mon Sep 17 00:00:00 2001 From: Makiras <22570332+Makiras@users.noreply.github.com> Date: Fri, 9 Oct 2026 19:39:04 +0800 Subject: [PATCH 05/10] refactor: organize xcomm modules and separate coverage sampling Group native sources by core, common, expression, ComUse, trigger, and coverage responsibilities. Split XData implementation files and move third-party headers, language binding tests, and TLM integration sources out of the public SDK and production test code out of the shared library. Share comparison helpers between XData and ComUse checkers. Extract sequence/FSM matching for reuse by trigger and coverage, and give coverage its own sampling state, counters, bins, crosses, gates, and diagnostics. Keep trigger coverage entry points as small adapters and avoid copying pattern descriptions or adding allocations during sampling. Separate expression engine headers from checker adapters, move utilities to common headers, and update include paths directly without legacy forwarding headers. Keep runtime class layouts and Python public methods. Centralize native and JavaScript source lists, stage only current public headers, register tests at the build root, and install the standalone TLM source SDK under share/xspcomm/tlm. Update SWIG interfaces, package contents, and architecture documentation for the new layout. Validation: Release build and all 9 C++ tests plus Python regression passed from the actual workspace. The identical proposal also passed ASan/UBSan, 50 standalone C++17/C++20 header checks, layout and behavior comparisons, all six SWIG generators, and clean wheel/sdist installation checks. VCS/SystemC and Emscripten execution were unavailable locally. --- CMakeLists.txt | 9 +- Makefile | 22 +- README.cn.md | 18 +- README.en.md | 18 +- cmake/Sources.cmake | 25 + cmake/StageHeaders.cmake | 6 + cmake/xcomm.cmake | 4 +- docs/APIs.cn.md | 6 +- docs/architecture.md | 60 + docs/xdata_xclock_optimizations.md | 16 +- include/xspcomm/common/compare.h | 151 ++ .../{xcomuse/utils.h => common/memory.h} | 4 +- .../{_not_export.h => detail/function.h} | 4 +- include/xspcomm/detail/pattern.h | 36 + include/xspcomm/{tlm_msg.h => tlm/message.h} | 0 include/xspcomm/{tlm_pbsb.h => tlm/pubsub.h} | 2 +- include/xspcomm/xcallback.h | 2 +- include/xspcomm/xcomuse.h | 7 +- include/xspcomm/xcomuse/condition.h | 25 +- include/xspcomm/xcomuse/expr.h | 51 + include/xspcomm/{xfsm.h => xcomuse/fsm.h} | 7 +- include/xspcomm/xcomuse/range.h | 6 +- include/xspcomm/xcoverage.h | 50 + include/xspcomm/xexpr.h | 45 +- include/xspcomm/xinstance.h | 10 - include/xspcomm/xpattern.h | 33 + include/xspcomm/xsignal_cfg.h | 6 +- include/xspcomm/xtrigger.h | 93 +- integrations/tlm/CMakeLists.txt | 41 + .../tlm/pubsub.cpp | 4 +- .../tlm/python.i | 8 +- .../tlm/uvmc_bridge.h | 2 +- src/CMakeLists.txt | 89 +- src/common/memory.cpp | 42 + src/comuse/callback.cpp | 70 + .../condition.cpp} | 145 +- src/comuse/expr.cpp | 135 ++ src/{xfsm_trigger.cpp => comuse/fsm.cpp} | 4 +- src/comuse/range.cpp | 49 + src/{xclock.cpp => core/clock.cpp} | 0 src/{xcoroutine.cpp => core/coroutine.cpp} | 0 src/core/data.cpp | 708 +++++++ src/core/data_backends.cpp | 280 +++ src/core/data_value.cpp | 537 +++++ src/core/data_vpi.cpp | 127 ++ src/core/pin.cpp | 107 + src/{xport.cpp => core/port.cpp} | 0 src/{xsignal_cfg.cpp => core/signal_cfg.cpp} | 41 +- src/{xutil.cpp => core/util.cpp} | 0 src/coverage/engine.cpp | 246 +++ src/coverage/state.h | 76 + src/{xexpr_engine.cpp => expr/engine.cpp} | 177 +- src/{xexpr_parser.cpp => expr/parser.cpp} | 1 + src/pattern.cpp | 78 + src/trigger/coverage.cpp | 48 + src/{xtrigger.cpp => trigger/engine.cpp} | 391 +--- src/xcomuse_range.cpp | 110 -- src/xdata.cpp | 1742 ----------------- swig/CMakeLists.txt | 8 +- swig/javascript/CMakeLists.txt | 11 +- swig/python/CMakeLists.txt | 2 + swig/python/python.i | 4 +- swig/python/thirdcall.cpp | 2 +- {include/xspcomm => swig/python}/thirdcall.h | 0 swig/xcomm.i | 9 +- tests/CMakeLists.txt | 45 +- tests/{ => bindings/golang}/test_golang.go | 0 tests/{ => bindings/java}/test_java.java | 0 tests/{ => bindings/lua}/test_lua.lua | 0 .../{ => bindings/python}/test_pythirdcall.py | 0 tests/{ => bindings/python}/test_python.py | 6 +- .../python}/test_python_exception.py | 0 tests/{ => bindings/scala}/test_scala.scala | 0 .../signal_cfg.yaml} | 0 .../signal_cfg_map.yaml} | 0 tests/integration/tlm/Makefile | 10 + tests/{ => integration}/tlm/Makefile.vcs | 6 +- tests/{ => integration}/tlm/consumer.sv | 0 tests/{ => integration}/tlm/producer.sv | 0 tests/{ => integration}/tlm/test.py | 0 tests/{ => integration}/tlm/tlm.sv | 0 tests/tlm/Makefile | 10 - tests/unit/test_coverage_core.cpp | 80 + tests/{ => unit}/test_xclock.cpp | 0 .../test_xcomuse.cpp} | 66 + tests/unit/test_xcoverage.cpp | 164 ++ tests/{ => unit}/test_xdata.cpp | 4 +- tests/{ => unit}/test_xexpr.cpp | 1 + tests/{ => unit}/test_xfsm.cpp | 2 +- tests/{ => unit}/test_xtrigger.cpp | 157 -- tests/{ => unit}/test_xvpi.cpp | 0 .../unit/xdata_cases.cpp | 1 - {tests => third_party/catch2}/catch.hpp | 0 .../xspcomm => third_party/fkyaml}/node.hpp | 0 94 files changed, 3531 insertions(+), 3031 deletions(-) create mode 100644 cmake/Sources.cmake create mode 100644 cmake/StageHeaders.cmake create mode 100644 docs/architecture.md create mode 100644 include/xspcomm/common/compare.h rename include/xspcomm/{xcomuse/utils.h => common/memory.h} (98%) rename include/xspcomm/{_not_export.h => detail/function.h} (69%) create mode 100644 include/xspcomm/detail/pattern.h rename include/xspcomm/{tlm_msg.h => tlm/message.h} (100%) rename include/xspcomm/{tlm_pbsb.h => tlm/pubsub.h} (97%) create mode 100644 include/xspcomm/xcomuse/expr.h rename include/xspcomm/{xfsm.h => xcomuse/fsm.h} (94%) create mode 100644 include/xspcomm/xcoverage.h delete mode 100644 include/xspcomm/xinstance.h create mode 100644 include/xspcomm/xpattern.h create mode 100644 integrations/tlm/CMakeLists.txt rename include/xspcomm/tlm_pbsb.cpp => integrations/tlm/pubsub.cpp (96%) rename include/xspcomm/python_tlm_pbsb.i => integrations/tlm/python.i (95%) rename include/xspcomm/_uvmc_pbsb.h => integrations/tlm/uvmc_bridge.h (99%) create mode 100644 src/common/memory.cpp create mode 100644 src/comuse/callback.cpp rename src/{xcomuse_base.cpp => comuse/condition.cpp} (67%) create mode 100644 src/comuse/expr.cpp rename src/{xfsm_trigger.cpp => comuse/fsm.cpp} (99%) create mode 100644 src/comuse/range.cpp rename src/{xclock.cpp => core/clock.cpp} (100%) rename src/{xcoroutine.cpp => core/coroutine.cpp} (100%) create mode 100644 src/core/data.cpp create mode 100644 src/core/data_backends.cpp create mode 100644 src/core/data_value.cpp create mode 100644 src/core/data_vpi.cpp create mode 100644 src/core/pin.cpp rename src/{xport.cpp => core/port.cpp} (100%) rename src/{xsignal_cfg.cpp => core/signal_cfg.cpp} (90%) rename src/{xutil.cpp => core/util.cpp} (100%) create mode 100644 src/coverage/engine.cpp create mode 100644 src/coverage/state.h rename src/{xexpr_engine.cpp => expr/engine.cpp} (80%) rename src/{xexpr_parser.cpp => expr/parser.cpp} (99%) create mode 100644 src/pattern.cpp create mode 100644 src/trigger/coverage.cpp rename src/{xtrigger.cpp => trigger/engine.cpp} (52%) delete mode 100644 src/xcomuse_range.cpp delete mode 100644 src/xdata.cpp rename {include/xspcomm => swig/python}/thirdcall.h (100%) rename tests/{ => bindings/golang}/test_golang.go (100%) rename tests/{ => bindings/java}/test_java.java (100%) rename tests/{ => bindings/lua}/test_lua.lua (100%) rename tests/{ => bindings/python}/test_pythirdcall.py (100%) rename tests/{ => bindings/python}/test_python.py (96%) rename tests/{ => bindings/python}/test_python_exception.py (100%) rename tests/{ => bindings/scala}/test_scala.scala (100%) rename tests/{test_signal_cfg.yaml => fixtures/signal_cfg.yaml} (100%) rename tests/{test_signal_cfg_map.yaml => fixtures/signal_cfg_map.yaml} (100%) create mode 100644 tests/integration/tlm/Makefile rename tests/{ => integration}/tlm/Makefile.vcs (91%) rename tests/{ => integration}/tlm/consumer.sv (100%) rename tests/{ => integration}/tlm/producer.sv (100%) rename tests/{ => integration}/tlm/test.py (100%) rename tests/{ => integration}/tlm/tlm.sv (100%) delete mode 100644 tests/tlm/Makefile create mode 100644 tests/unit/test_coverage_core.cpp rename tests/{ => unit}/test_xclock.cpp (100%) rename tests/{test_xcomuse_base.cpp => unit/test_xcomuse.cpp} (78%) create mode 100644 tests/unit/test_xcoverage.cpp rename tests/{ => unit}/test_xdata.cpp (98%) rename tests/{ => unit}/test_xexpr.cpp (99%) rename tests/{ => unit}/test_xfsm.cpp (98%) rename tests/{ => unit}/test_xtrigger.cpp (66%) rename tests/{ => unit}/test_xvpi.cpp (100%) rename src/xinstance.cpp => tests/unit/xdata_cases.cpp (99%) rename {tests => third_party/catch2}/catch.hpp (100%) rename {include/xspcomm => third_party/fkyaml}/node.hpp (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index a98ab7a..c378f0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,8 +24,13 @@ init_xspcomm() include_directories(BEFORE ${PROJECT_SOURCE_DIR}/include) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) +set(XSPCOMM_HEADER_STAGE ${CMAKE_CURRENT_BINARY_DIR}/include) add_subdirectory(src) -add_subdirectory(tests) +include(CTest) +if(BUILD_TESTING) + add_subdirectory(tests) +endif() +add_subdirectory(integrations/tlm) add_subdirectory(swig) diff --git a/Makefile b/Makefile index ec18934..c85bd3f 100644 --- a/Makefile +++ b/Makefile @@ -11,42 +11,42 @@ clean: rm -rf build dist test_lua: build - LUA_PATH=build/lua/?.lua lua tests/test_lua.lua + LUA_PATH=build/lua/?.lua lua tests/bindings/lua/test_lua.lua ifeq ($(BUILD_XSPCOMM_VCS_UVMPS),) test_python: build - PYTHONPATH=build/python python3 tests/test_python.py + PYTHONPATH=build/python python3 tests/bindings/python/test_python.py test_java: build - java -cp build/java/xspcomm-java.jar tests/test_java.java + java -cp build/java/xspcomm-java.jar tests/bindings/java/test_java.java test_scala: build - scala -cp build/scala/xspcomm-scala.jar tests/test_scala.scala + scala -cp build/scala/xspcomm-scala.jar tests/bindings/scala/test_scala.scala test_golang: build - GO111MODULE=off GOPATH="`pwd`/build/golang" go run tests/test_golang.go + GO111MODULE=off GOPATH="`pwd`/build/golang" go run tests/bindings/golang/test_golang.go else test_python: build - LD_PRELOAD=build/lib/libuvmps_vcs.so PYTHONPATH=build/python python3 tests/test_python.py + LD_PRELOAD=build/lib/libuvmps_vcs.so PYTHONPATH=build/python python3 tests/bindings/python/test_python.py test_java: build - LD_PRELOAD=build/lib/libuvmps_vcs.so java -cp build/java/xspcomm-java.jar tests/test_java.java + LD_PRELOAD=build/lib/libuvmps_vcs.so java -cp build/java/xspcomm-java.jar tests/bindings/java/test_java.java test_scala: build - LD_PRELOAD=build/lib/libuvmps_vcs.so scala -cp build/scala/xspcomm-scala.jar tests/test_scala.scala + LD_PRELOAD=build/lib/libuvmps_vcs.so scala -cp build/scala/xspcomm-scala.jar tests/bindings/scala/test_scala.scala test_golang: build - LD_PRELOAD=build/lib/libuvmps_vcs.so GO111MODULE=off GOPATH="`pwd`/build/golang" go run tests/test_golang.go + LD_PRELOAD=build/lib/libuvmps_vcs.so GO111MODULE=off GOPATH="`pwd`/build/golang" go run tests/bindings/golang/test_golang.go endif test_pythird_call: build - cd tests && (PYTHONPATH=../build/python PYTHON_THIRD_MODUEL=test_pythirdcall python3 test_pythirdcall.py) + cd tests/bindings/python && (PYTHONPATH=$(CURDIR)/build/python PYTHON_THIRD_MODUEL=test_pythirdcall python3 test_pythirdcall.py) run: build - ctest --test-dir build/tests + ctest --test-dir build --output-on-failure rbuild: clean build run diff --git a/README.cn.md b/README.cn.md index f14b735..fe62997 100644 --- a/README.cn.md +++ b/README.cn.md @@ -10,7 +10,7 @@ xspcomm 为 picker 的公用数据定义与操作接口,包括接口读/写、 make BUILD_XSPCOMM_SWIG=python,scala,java,golang,lua -# results +# results build/python/ └── xspcomm # python 模块 ├── __init__.py @@ -36,7 +36,7 @@ build/golang/ 以上示例为 Linux 下的产物形式。macOS 下原生动态库/扩展模块会保留平台原生命名(例如 `.dylib`、`.jnilib` 或模块实际文件名),不再额外拼接 Linux 风格的 `.${PROJECT_VERSION}` 后缀。 **测试:** -make命令默认编译执行 tests/test_xdata.cpp。 若要运行 tests/tests_python.py 执行如下命令: +make命令默认编译执行 tests/unit/test_xdata.cpp。 若要运行 tests/bindings/python/test_python.py 执行如下命令: ```bash $make BUILD_XSPCOMM_SWIG=python $make test_python @@ -90,7 +90,7 @@ XData(uint32_t width, IOType itype, std::string name = ""); 绑定DPI ```c++ -void BindDPIRW(xfunction read, +void BindDPIRW(xfunction read, xfunction write); void BindDPIRW(xfunction read, xfunction write); @@ -156,7 +156,7 @@ bool Del(std::string pin); // 删除引脚 bool Connect(XPort &target); // 和另外一个port进行连接 XPort &NewSubPort(std::string subprefix); // 创建子port xspcomm::XData &operator[](std::string key); // 按key获取引脚 -xspcomm::XData &Get(std::string key, +xspcomm::XData &Get(std::string key, bool raw_key = false); // 同上 XPort &Flip(); // port中所有引脚值进行反转 XPort &AsBiIO(); // 把port中所有引脚设置为BiIO模式 @@ -286,11 +286,11 @@ if __name__ == "__main__": ... # 1 通过swig生成python wrapper $swig -D'MODULE_NAME="tlm_pbsb"' -python -c++ -DUSE_VCS -I${XSP_COMM_INCLUDE} \ --o tlmps.cpp ${XSP_COMM_INCLUDE}/xspcomm/python_tlm_pbsb.i +-o tlmps.cpp ${XSP_COMM_INCLUDE}/../share/xspcomm/tlm/python.i # 2 编译通信模块 $SYSCAN -full64 -cflags -DUSE_VCS -cflags -I{PYTHON_INCLUDE} \ --cflags -I${XSP_COMM_INCLUDE} ${XSP_COMM_INCLUDE}/xspcomm/tlm_pbsb.cpp tlmps.cpp +-cflags -I${XSP_COMM_INCLUDE} ${XSP_COMM_INCLUDE}/../share/xspcomm/tlm/pubsub.cpp tlmps.cpp # 3 以slave模式编译 DUT $VLOGAN -full64 +incdir+common tlm.sv +define+UVM_OBJECT_MUST_HAVE_CONSTRUCTOR @@ -302,7 +302,7 @@ $mv simv.daidir _tlm_pbsb.so.daidir ... ``` -编译完成后,可以通过 import 加载dut,具体例子请参考:tests/tlm +编译完成后,可以通过 import 加载dut,具体例子请参考:tests/integration/tlm ### 五、其他可用接口 @@ -350,8 +350,6 @@ inline void XSeed(unsigned int seed) inline bool checkVersion() // 循环 n 次,,eg: FOR_COUNT(10){i++;} -FOR_COUNT(n) +FOR_COUNT(n) ``` - -xinstance.h 仅仅用于实例化模板类,无其他作用。 diff --git a/README.en.md b/README.en.md index e8af61c..19069b9 100644 --- a/README.en.md +++ b/README.en.md @@ -11,7 +11,7 @@ Compile using the make command. Enable support for Python and other languages by make BUILD_XSPCOMM_SWIG=python,scala,java,golang,lua -# results +# results build/python/ └── xspcomm # python module ├── __init__.py @@ -37,7 +37,7 @@ build/golang/ The example above shows Linux artifact names. On macOS, native shared libraries and extension modules keep the platform-native filename (for example `.dylib`, `.jnilib`, or the module's actual filename) instead of appending an extra Linux-style `.${PROJECT_VERSION}` suffix. **Testing:** -The make command compiles and executes tests/test_xdata.cpp by default. To run tests/tests_python.py, execute the following commands: +The make command compiles and executes tests/unit/test_xdata.cpp by default. To run tests/bindings/python/test_python.py, execute the following commands: ```bash $make BUILD_XSPCOMM_SWIG=python $make test_python @@ -92,7 +92,7 @@ XData(uint32_t width, IOType itype, std::string name = ""); Bind DPI ```c++ -void BindDPIRW(xfunction read, +void BindDPIRW(xfunction read, xfunction write); void BindDPIRW(xfunction read, xfunction write); @@ -158,7 +158,7 @@ bool Del(std::string pin); // Delete pin bool Connect(XPort &target); // Connect with another port XPort &NewSubPort(std::string subprefix); // Create subport xspcomm::XData &operator[](std::string key); // Get pin by key -xspcomm::XData &Get(std::string key, +xspcomm::XData &Get(std::string key, bool raw_key = false); // Same as above XPort &Flip(); // Flip all pin values in the port XPort &AsBiIO(); // Set all pins in the port to BiIO mode @@ -290,11 +290,11 @@ Taking VCS as an example, the compilation steps of the DUT example are as follow ... # 1 Generate python wrapper through swig $swig -D'MODULE_NAME="tlm_pbsb"' -python -c++ -DUSE_VCS -I${XSP_COMM_INCLUDE} \ --o tlmps.cpp ${XSP_COMM_INCLUDE}/xspcomm/python_tlm_pbsb.i +-o tlmps.cpp ${XSP_COMM_INCLUDE}/../share/xspcomm/tlm/python.i # 2 Compile the communication module $SYSCAN -full64 -cflags -DUSE_VCS -cflags -I{PYTHON_INCLUDE} \ --cflags -I${XSP_COMM_INCLUDE} ${XSP_COMM_INCLUDE}/xspcomm/tlm_pbsb.cpp tlmps.cpp +-cflags -I${XSP_COMM_INCLUDE} ${XSP_COMM_INCLUDE}/../share/xspcomm/tlm/pubsub.cpp tlmps.cpp # 3 Compile DUT in slave mode $VLOGAN -full64 +incdir+common tlm.sv +define+UVM_OBJECT_MUST_HAVE_CONSTRUCTOR @@ -306,7 +306,7 @@ $mv simv.daidir _tlm_pbsb.so.daidir ... ``` -After compilation, you can load the DUT through import. For specific examples, please refer to: tests/tlm +After compilation, you can load the DUT through import. For specific examples, please refer to: tests/integration/tlm ### V. Other Available Interfaces @@ -355,8 +355,6 @@ inline void XSeed(unsigned int seed) inline bool checkVersion() // Loop n times, eg: FOR_COUNT(10){i++;} -FOR_COUNT(n) +FOR_COUNT(n) ``` - -xinstance.h is only used for instantiating template classes and has no other functions. diff --git a/cmake/Sources.cmake b/cmake/Sources.cmake new file mode 100644 index 0000000..8cd51e7 --- /dev/null +++ b/cmake/Sources.cmake @@ -0,0 +1,25 @@ +set(XSPCOMM_SOURCES + common/memory.cpp + comuse/callback.cpp + comuse/condition.cpp + comuse/expr.cpp + comuse/fsm.cpp + comuse/range.cpp + core/clock.cpp + core/coroutine.cpp + core/data.cpp + core/data_backends.cpp + core/data_value.cpp + core/data_vpi.cpp + core/pin.cpp + core/port.cpp + core/signal_cfg.cpp + core/util.cpp + coverage/engine.cpp + expr/engine.cpp + expr/parser.cpp + pattern.cpp + trigger/coverage.cpp + trigger/engine.cpp +) +list(TRANSFORM XSPCOMM_SOURCES PREPEND "${CMAKE_CURRENT_LIST_DIR}/../src/") diff --git a/cmake/StageHeaders.cmake b/cmake/StageHeaders.cmake new file mode 100644 index 0000000..1037783 --- /dev/null +++ b/cmake/StageHeaders.cmake @@ -0,0 +1,6 @@ +# Build-tree staging must not retain headers removed or renamed in the source. +file(REMOVE_RECURSE "${STAGE_INCLUDE}") +file(MAKE_DIRECTORY "${STAGE_INCLUDE}") +file(COPY "${SOURCE_INCLUDE}/" DESTINATION "${STAGE_INCLUDE}" + FILES_MATCHING PATTERN "*.h") +file(COPY "${GENERATED_INCLUDE}/xconfig.h" DESTINATION "${STAGE_INCLUDE}") diff --git a/cmake/xcomm.cmake b/cmake/xcomm.cmake index 3ab0d44..7a72578 100644 --- a/cmake/xcomm.cmake +++ b/cmake/xcomm.cmake @@ -74,8 +74,8 @@ message(STATUS "GIT_BRANCH: ${GIT_BRANCH}") message(STATUS "GIT_HASH: ${GIT_HASH}") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/include/xspcomm/xconfig.h.in - ${CMAKE_CURRENT_BINARY_DIR}/include/xspcomm/xconfig.h) -include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) + ${CMAKE_CURRENT_BINARY_DIR}/generated/xspcomm/xconfig.h) +include_directories(${CMAKE_CURRENT_BINARY_DIR}/generated) if (NOT "$ENV{XSPCOMM_INSTALL_PREFIX}" STREQUAL "") set(XSPCOMM_INSTALL_PREFIX "$ENV{XSPCOMM_INSTALL_PREFIX}" PARENT_SCOPE) diff --git a/docs/APIs.cn.md b/docs/APIs.cn.md index 8ccab13..0f2a15f 100644 --- a/docs/APIs.cn.md +++ b/docs/APIs.cn.md @@ -3,7 +3,7 @@ 本文档基于当前项目源码整理,主要对应以下文件: - 核心 C++ API:`include/xspcomm/xdata.h`、`xport.h`、`xclock.h`、`xsignal_cfg.h` -- 组合检查与表达式/FSM:`include/xspcomm/xcomuse.h`、`xcomuse/`、`xexpr.h`、`xfsm.h` +- 组合检查与表达式/FSM:`include/xspcomm/xcomuse.h`、`xcomuse/`、`xexpr.h`、`xcomuse/expr.h`、`xcomuse/fsm.h` - 多语言封装:`swig/python/xcomm.py`、`swig/java/java.i`、`swig/scala/xsp.scala`、`swig/golang/golang.i`、`swig/lua/lua.i`、`swig/javascript/xspcomm.js` xspcomm 的核心抽象是: @@ -25,7 +25,7 @@ C++ API 位于 `xspcomm` 命名空间。常用头文件: #include "xspcomm/xcomuse.h" // ComUse、Expr、FSM 汇总 ``` -ComUse 组件位于 `xspcomm/xcomuse/`,可以按需包含 `callback.h`、`condition.h`、`range.h`、`utils.h`,分别提供回调、条件检查、范围比较和数组/字符串工具。 +ComUse 组件位于 `xspcomm/xcomuse/`,可以按需包含 `callback.h`、`condition.h`、`range.h`、`expr.h` 和 `fsm.h`。独立的表达式引擎位于 `xexpr.h`。`common/compare.h` 提供 XData、表达式和检查器共用的比较算法;`common/memory.h` 提供数组、指针及字符串工具。 多语言 SWIG 包通常复用 C++ 名称,部分语言会增加更符合本语言习惯的包装方法,见“多语言差异”。 @@ -418,7 +418,7 @@ variables: ```c++ uint64_t mem[16] = {}; -XSignalCFG cfg("tests/test_signal_cfg.yaml", (uint64_t)mem); +XSignalCFG cfg("tests/fixtures/signal_cfg.yaml", (uint64_t)mem); auto clk = cfg.NewXData("Cache_top.clock"); auto rst = cfg.NewXData("Cache_top.reset"); diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..08b590f --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,60 @@ +# xcomm source layout + +Public interfaces are installed below `include/xspcomm`. Common class entry +headers (`xdata.h`, `xclock.h`, `xexpr.h`, `xtrigger.h`) describe their own modules; +`xcomm.h` and `xcomuse.h` are the aggregate entry points. + +- `common/`: comparison algorithms and memory/string utilities. Comparison + algorithms depend only on standard headers, so XData and checkers share them. +- `xcomuse/`: clock callbacks, condition/range checkers, expression checkers and + the text-program FSM adapter. `ExprEngine` itself belongs to `xexpr.h`. +- `xpattern.h`: shared sequence/FSM descriptors. `detail/pattern.h` holds the + matching state required by registrations; `src/pattern.cpp` implements the + shared matching algorithms. +- `xcoverage.h`: coverage descriptors and snapshots. The independent coverage + component in `src/coverage` owns sampling history, bins, crosses, counters and + diagnostics, and has no dependency on trigger types. +- `src/trigger`: trigger registration/execution and a small coverage adapter. + Both consumers use the same expression and pattern implementations. Coverage + runs in the native sampling phase, with no event queue or language callback. +- `src/core`: signal storage/value operations, backend bindings, ports, clocks, + coroutines and configuration. YAML parsing stays in the configuration source; + third-party YAML headers are not part of the public header dependency graph. +- `swig/`: common binding definitions and language-specific wrappers. Native and + JavaScript builds use the source list in `cmake/Sources.cmake`. +- `tests/unit`, `tests/bindings`, `tests/integration`, `tests/fixtures`: native + unit tests, language checks, simulator integration tests and configuration data. + Run native tests with `ctest --test-dir build --output-on-failure`. +- `third_party/`: vendored fkYAML and Catch2 headers, with their existing licenses. + +## Header migration + +Update includes directly; removed paths do not have forwarding headers: + +| Previous include | Current include | +| --- | --- | +| `xspcomm/xcomuse/compare.h` | `xspcomm/common/compare.h` | +| `xspcomm/xcomuse/utils.h` | `xspcomm/common/memory.h` | +| `xspcomm/xfsm.h` | `xspcomm/xcomuse/fsm.h` | +| `xspcomm/xexpr.h` for `ComUseExprCheck` | `xspcomm/xcomuse/expr.h` | +| `xspcomm/tlm_msg.h` | `xspcomm/tlm/message.h` | +| `xspcomm/tlm_pbsb.h` | `xspcomm/tlm/pubsub.h` | + +`xinstance.h` and its `test_xdata()` helper are removed from the production SDK; +that test implementation is compiled into `test_xdata` instead. Python-only +third-call declarations live beside the Python wrapper. Existing runtime class +layouts and Python public methods are retained; source clients must update the +includes above and rebuild bindings as needed. + +## TLM source SDK + +The standalone TLM integration lives in `integrations/tlm`. Installation exports +`pubsub.cpp`, `uvmc_bridge.h` and `python.i` under `share/xspcomm/tlm`, alongside the +public `xspcomm/tlm/` headers. Simulator makefiles can set `XSP_COMM_TLM`; its +default is `$(XSP_COMM_INCLUDE)/../share/xspcomm/tlm`. The integration tests use the +source SDK and staged build headers directly. + +The optional legacy `BUILD_XSPCOMM_VCS_UVMPS` combined build retains its previous +configuration. It still references the pre-existing absent `src/uvm_pbsb.cpp` +and `swig/python/python_uvmps.i`; use the standalone TLM SDK instead of that path. +Actual VCS/SystemC execution requires the corresponding external tools. diff --git a/docs/xdata_xclock_optimizations.md b/docs/xdata_xclock_optimizations.md index 9a7273f..041658e 100644 --- a/docs/xdata_xclock_optimizations.md +++ b/docs/xdata_xclock_optimizations.md @@ -7,7 +7,7 @@ No changes for divider algorithm (4) or runtime fast_mode advice (6). --- ## 1) XData::_update_shadow lower cost when no callbacks -Location: src/xdata.cpp : XData::_update_shadow +Location: src/core/data.cpp : XData::_update_shadow Current: Every read/write does shadow compare/copy even when no callbacks. Idea: - When has_on_change_cbs == false, skip per-element compare and callback logic. @@ -25,7 +25,7 @@ Expected gain: --- ## 2) XData::_need_write use memcmp/memcpy -Location: src/xdata.cpp : XData::_need_write / _update_last_write +Location: src/core/data.cpp : XData::_need_write / _update_last_write Current: Element-by-element compare and copy for vec data. Idea: - Use memcmp for change detection on xsvLogicVecVal arrays. @@ -40,7 +40,7 @@ Expected gain: --- ## 3) XPort hot-iteration vector -Location: src/xport.cpp / include/xspcomm/xport.h +Location: src/core/port.cpp / include/xspcomm/xport.h Current: WriteOnRise/Fall/ReadFresh iterate std::map every time. Idea: - Keep map for lookups. @@ -57,7 +57,7 @@ Expected gain: --- ## 5) Callback assert cost moved to registration -Location: src/xclock.cpp : _call_back / _add_cb +Location: src/core/clock.cpp : _call_back / _add_cb Current: Assert(func != nullptr) on every callback invocation. Idea: - Validate in _add_cb (registration), not every call. @@ -72,7 +72,7 @@ Expected gain: --- ## Correctness Fix: Reset last_write state on ReInit -Location: src/xdata.cpp : XData::ReInit, XData::~XData +Location: src/core/data.cpp : XData::ReInit, XData::~XData Reason: - ReInit does not reset last_is_write/last_pVecData, which can leave stale pointers and sizes after width changes. @@ -89,7 +89,7 @@ Behavior impact: --- ## Correctness Fix: ReInit memory management -Location: src/xdata.cpp : XData::ReInit / ~XData +Location: src/core/data.cpp : XData::ReInit / ~XData Reason: - ReInit allocates pVecData/__pVecData/pinbind_vec with calloc but does not free old allocations on repeated ReInit (leak). @@ -103,7 +103,7 @@ Behavior impact: --- ## Correctness Fix: validate checks all bval words -Location: src/xdata.cpp : XData::_update_shadow +Location: src/core/data.cpp : XData::_update_shadow Reason: - validate only checks pVecData[0].bval, missing X/Z in higher words. Fix: @@ -115,7 +115,7 @@ Behavior impact: ## Follow-up: BindNativeData X/Z semantics -Location: src/xdata.cpp : XData::BindNativeData +Location: src/core/data_backends.cpp : XData::BindNativeData Current behavior: diff --git a/include/xspcomm/common/compare.h b/include/xspcomm/common/compare.h new file mode 100644 index 0000000..9d32b6e --- /dev/null +++ b/include/xspcomm/common/compare.h @@ -0,0 +1,151 @@ +#ifndef XSPCOMM_COMMON_COMPARE_H +#define XSPCOMM_COMMON_COMPARE_H + +#include +#include +#include +#include + +namespace xspcomm::compare { + +// Operation enums supply EQ/NE/GT/GE/LT/LE; no dependency on their owners. +template +bool Compare(Left&& lhs, Right&& rhs) { + using Op = decltype(Operation); + if constexpr (Operation == Op::EQ) return lhs == rhs; + else if constexpr (Operation == Op::NE) return !(lhs == rhs); + else if constexpr (Operation == Op::GT) return lhs > rhs; + else if constexpr (Operation == Op::GE) return lhs >= rhs; + else if constexpr (Operation == Op::LT) return lhs < rhs; + else if constexpr (Operation == Op::LE) return lhs <= rhs; + else return false; +} + +template +bool Compare(Op operation, Left&& lhs, Right&& rhs) { + switch (operation) { + case Op::EQ: return Compare(lhs, rhs); + case Op::NE: return Compare(lhs, rhs); + case Op::GT: return Compare(lhs, rhs); + case Op::GE: return Compare(lhs, rhs); + case Op::LT: return Compare(lhs, rhs); + case Op::LE: return Compare(lhs, rhs); + default: return false; + } +} + +template struct UIntBytes; + +template +struct UIntWord { + static uint64_t Load(const unsigned char* data) { + T value; + std::memcpy(&value, data, sizeof(value)); + return value; + } +}; + +template +struct UIntParts { + static uint64_t Load(const unsigned char* data) { + return UIntBytes::Load(data) | + (UIntBytes::Load(data + LowBytes) << (LowBytes * 8)); + } +}; + +// Fixed byte widths use native integers or consecutive low/high parts. +#define XSPCOMM_UINT_BYTES(Bytes, ...) \ + template <> struct UIntBytes : __VA_ARGS__ {}; +XSPCOMM_UINT_BYTES(1, UIntWord) +XSPCOMM_UINT_BYTES(2, UIntWord) +XSPCOMM_UINT_BYTES(3, UIntParts<2, 1>) +XSPCOMM_UINT_BYTES(4, UIntWord) +XSPCOMM_UINT_BYTES(5, UIntParts<4, 1>) +XSPCOMM_UINT_BYTES(6, UIntParts<4, 2>) +XSPCOMM_UINT_BYTES(7, UIntParts<6, 1>) +XSPCOMM_UINT_BYTES(8, UIntWord) +#undef XSPCOMM_UINT_BYTES + +// A non-owning reader for little-endian buffers, including a partial last word. +struct ByteWords { + const unsigned char* data; + size_t bytes; + + size_t Count() const { return bytes / 4 + (bytes % 4 != 0); } + + uint32_t operator()(size_t i) const { + const size_t offset = i * 4; + const size_t count = std::min(sizeof(uint32_t), bytes - offset); + const auto* word = data + offset; + if (count == sizeof(uint32_t)) return UIntBytes<4>::Load(word); + switch (count) { + case 1: return UIntBytes<1>::Load(word); + case 2: return UIntBytes<2>::Load(word); + default: return UIntBytes<3>::Load(word); + } + } +}; + +template +int Order(T lhs, T rhs) { + return lhs < rhs ? -1 : lhs > rhs ? 1 : 0; +} + +// Readers provide words from least to most significant; ordering scans backward. +template +inline int CompareWords(size_t words, ReadA a, ReadB b) { + while (words != 0) { + --words; + const auto lhs = a(words); + const auto rhs = b(words); + if (lhs != rhs) return lhs < rhs ? -1 : 1; + } + return 0; +} + +// Equality permits zero extension. A word may also contain a four-state mask. +template +inline bool EqualWords(size_t awords, size_t bwords, ReadA a, ReadB b) { + const size_t common = std::min(awords, bwords); + for (size_t i = common; i < awords; ++i) if (a(i) != 0) return false; + for (size_t i = common; i < bwords; ++i) if (b(i) != 0) return false; + for (size_t i = 0; i < common; ++i) if (a(i) != b(i)) return false; + return true; +} + +// Equal-width two's-complement buffers; bytes must be positive. +inline int CompareSignedBytes(const unsigned char* a, const unsigned char* b, size_t bytes) { + const bool anegative = (a[bytes - 1] & 0x80) != 0; + const bool bnegative = (b[bytes - 1] & 0x80) != 0; + if (anegative != bnegative) return anegative ? -1 : 1; + // Within the same sign, two's-complement values retain unsigned ordering. + const ByteWords lhs{a, bytes}; + const ByteWords rhs{b, bytes}; + return CompareWords(lhs.Count(), lhs, rhs); +} + +inline bool WithinRange(uint64_t target, uint64_t center, int range) { + if (range >= 0) { + return target <= center && center - target <= static_cast(range); + } + return target >= center && target - center <= static_cast(-static_cast(range)); +} + +// Range checks subtract low words first; the tolerance fits in one 32-bit word. +template +bool WithinRange(size_t words, int range, ReadA a, ReadB b) { + const uint32_t limit = static_cast(range >= 0 ? range : -static_cast(range)); + uint64_t borrow = 0; + for (size_t i = 0; i < words; ++i) { + const uint64_t lhs = range >= 0 ? b(i) : a(i); + const uint64_t rhs = (range >= 0 ? a(i) : b(i)) + borrow; + const uint32_t difference = static_cast(lhs - rhs); + borrow = lhs < rhs; + if (difference > (i == 0 ? limit : 0U)) return false; + } + return borrow == 0; +} + +} // namespace xspcomm::compare + +#endif diff --git a/include/xspcomm/xcomuse/utils.h b/include/xspcomm/common/memory.h similarity index 98% rename from include/xspcomm/xcomuse/utils.h rename to include/xspcomm/common/memory.h index 379a7ee..6495d3d 100644 --- a/include/xspcomm/xcomuse/utils.h +++ b/include/xspcomm/common/memory.h @@ -1,5 +1,5 @@ -#ifndef XSPCOMM_XCOMUSE_UTILS_H -#define XSPCOMM_XCOMUSE_UTILS_H +#ifndef XSPCOMM_COMMON_MEMORY_H +#define XSPCOMM_COMMON_MEMORY_H #include "xspcomm/xutil.h" #include diff --git a/include/xspcomm/_not_export.h b/include/xspcomm/detail/function.h similarity index 69% rename from include/xspcomm/_not_export.h rename to include/xspcomm/detail/function.h index ddbf841..1ec4394 100644 --- a/include/xspcomm/_not_export.h +++ b/include/xspcomm/detail/function.h @@ -1,5 +1,5 @@ -#ifndef __xspcomm_no_export__ -#define __xspcomm_no_export__ +#ifndef XSPCOMM_DETAIL_FUNCTION_H +#define XSPCOMM_DETAIL_FUNCTION_H #include diff --git a/include/xspcomm/detail/pattern.h b/include/xspcomm/detail/pattern.h new file mode 100644 index 0000000..f3fae7c --- /dev/null +++ b/include/xspcomm/detail/pattern.h @@ -0,0 +1,36 @@ +#ifndef XSPCOMM_DETAIL_PATTERN_H +#define XSPCOMM_DETAIL_PATTERN_H + +#include "xspcomm/xpattern.h" +#include +#include + +namespace xspcomm { +class ExprEngine; +namespace detail { + +struct PatternState { + bool sequence_failed = false; + bool sequence_expired = false; + size_t sequence_index = 0; + uint64_t sequence_age = 0; + uint64_t sequence_held = 0; + uint32_t fsm_current_state = 0; +}; + +// A non-owning view, valid only while evaluating the owning registration. +struct PatternView { + const std::vector *sequence = nullptr; + const std::vector *fsm = nullptr; + uint32_t start_state = 0; +}; + +bool AdvanceSequence(const std::vector &steps, + PatternState &state, ExprEngine &expr); +bool AdvanceFsm(const std::vector &transitions, + PatternState &state, ExprEngine &expr, uint32_t &terminal); + +} // namespace detail +} // namespace xspcomm + +#endif diff --git a/include/xspcomm/tlm_msg.h b/include/xspcomm/tlm/message.h similarity index 100% rename from include/xspcomm/tlm_msg.h rename to include/xspcomm/tlm/message.h diff --git a/include/xspcomm/tlm_pbsb.h b/include/xspcomm/tlm/pubsub.h similarity index 97% rename from include/xspcomm/tlm_pbsb.h rename to include/xspcomm/tlm/pubsub.h index d842d5b..5e90545 100644 --- a/include/xspcomm/tlm_pbsb.h +++ b/include/xspcomm/tlm/pubsub.h @@ -1,7 +1,7 @@ #ifndef __xspcomm_tlm_pbsb__ #define __xspcomm_tlm_pbsb__ -#include "xspcomm/tlm_msg.h" +#include "xspcomm/tlm/message.h" #include "xspcomm/xcallback.h" #include diff --git a/include/xspcomm/xcallback.h b/include/xspcomm/xcallback.h index 2f52f95..32a6857 100644 --- a/include/xspcomm/xcallback.h +++ b/include/xspcomm/xcallback.h @@ -2,7 +2,7 @@ #define __xspcomm_xcallback__ //#include "xspcomm/xutil.h" -#include "xspcomm/_not_export.h" +#include "xspcomm/detail/function.h" #include #include #include diff --git a/include/xspcomm/xcomuse.h b/include/xspcomm/xcomuse.h index 4945149..2466478 100644 --- a/include/xspcomm/xcomuse.h +++ b/include/xspcomm/xcomuse.h @@ -1,11 +1,12 @@ #ifndef __xspcomm_xcomuse_h__ #define __xspcomm_xcomuse_h__ +#include "xspcomm/common/compare.h" #include "xspcomm/xcomuse/callback.h" #include "xspcomm/xcomuse/condition.h" #include "xspcomm/xcomuse/range.h" -#include "xspcomm/xcomuse/utils.h" -#include "xspcomm/xexpr.h" -#include "xspcomm/xfsm.h" +#include "xspcomm/common/memory.h" +#include "xspcomm/xcomuse/expr.h" +#include "xspcomm/xcomuse/fsm.h" #endif diff --git a/include/xspcomm/xcomuse/condition.h b/include/xspcomm/xcomuse/condition.h index c3f10a3..cc78d99 100644 --- a/include/xspcomm/xcomuse/condition.h +++ b/include/xspcomm/xcomuse/condition.h @@ -2,6 +2,7 @@ #define XSPCOMM_XCOMUSE_CONDITION_H #include "xspcomm/xcomuse/callback.h" +#include "xspcomm/common/compare.h" #include "xspcomm/xdata.h" #include #include @@ -24,18 +25,17 @@ namespace xspcomm { class ComUseCondCheck: public ComUseStepCb{ using XDataCmpFn = bool (*)(XData*, XData*); using PtrCmpFn = bool (*)(ComUseCondCheck*, uint64_t, uint64_t, int); - static bool XDataCmpEq(XData* a, XData* b){ return *a == *b; } - static bool XDataCmpNe(XData* a, XData* b){ return *a != *b; } - static bool XDataCmpGt(XData* a, XData* b){ return *a > *b; } - static bool XDataCmpGe(XData* a, XData* b){ return *a >= *b; } - static bool XDataCmpLt(XData* a, XData* b){ return *a < *b; } - static bool XDataCmpLe(XData* a, XData* b){ return *a <= *b; } - static bool PtrCmpEq(ComUseCondCheck* self, uint64_t a, uint64_t b, int bytes){ return self->_valcmp(a, b, bytes) == 0; } - static bool PtrCmpNe(ComUseCondCheck* self, uint64_t a, uint64_t b, int bytes){ return self->_valcmp(a, b, bytes) != 0; } - static bool PtrCmpGt(ComUseCondCheck* self, uint64_t a, uint64_t b, int bytes){ return self->_valcmp(a, b, bytes) > 0; } - static bool PtrCmpGe(ComUseCondCheck* self, uint64_t a, uint64_t b, int bytes){ return self->_valcmp(a, b, bytes) >= 0; } - static bool PtrCmpLt(ComUseCondCheck* self, uint64_t a, uint64_t b, int bytes){ return self->_valcmp(a, b, bytes) < 0; } - static bool PtrCmpLe(ComUseCondCheck* self, uint64_t a, uint64_t b, int bytes){ return self->_valcmp(a, b, bytes) <= 0; } + template + static bool XDataCmp(XData* a, XData* b) { + return compare::Compare(*a, *b); + } + template + static bool PtrCmp(ComUseCondCheck*, uint64_t a, uint64_t b, int bytes) { + const int order = compare::CompareSignedBytes( + reinterpret_cast(a), + reinterpret_cast(b), bytes); + return compare::Compare(order, 0); + } static XDataCmpFn SelectXDataCmpFn(ComUseCondCmp cmp); static PtrCmpFn SelectPtrCmpFn(ComUseCondCmp cmp); std::vector clk_list; @@ -79,7 +79,6 @@ namespace xspcomm { static bool RemoveUint64Cond(std::unordered_map &idx, std::vector &vec, const std::string &name); - int _valcmp(uint64_t a, u_int64_t b, int bytes); public: ComUseCondCheck(XClock* clk=nullptr){if(clk)this->clk_list.push_back(clk);} void BindXClock(XClock *clk); diff --git a/include/xspcomm/xcomuse/expr.h b/include/xspcomm/xcomuse/expr.h new file mode 100644 index 0000000..a5cadbc --- /dev/null +++ b/include/xspcomm/xcomuse/expr.h @@ -0,0 +1,51 @@ +#ifndef XSPCOMM_XCOMUSE_EXPR_H +#define XSPCOMM_XCOMUSE_EXPR_H + +#include "xspcomm/xcomuse/callback.h" +#include "xspcomm/xexpr.h" + +#include + +namespace xspcomm { + +class XClock; + + class ComUseExprCheck: public ComUseStepCb{ + ExprEngine engine; + struct ExprItem{ + std::string name; + int root = -1; + uint64_t last_trigger_cycle = (uint64_t)-1; + }; + std::vector clk_list; + std::vector expr_list; + std::unordered_map expr_index; + uint64_t last_eval_cycle = 0; + public: + ComUseExprCheck(XClock* clk=nullptr){if(clk)this->clk_list.push_back(clk);} + void BindXClock(XClock *clk); + int ExprNewConst(uint64_t v); + int ExprNewSignal(XData* sig); + int ExprNewUnary(int op, int child); + int ExprNewBinary(int op, int lhs, int rhs); + int ExprNewCompare(int op, int lhs, int rhs); + int ExprNewCompareSigSig(int op, XData* lhs, XData* rhs); + int ExprNewCompareSigConst(int op, XData* lhs, uint64_t rhs); + int ExprNewCompareConstSig(int op, uint64_t lhs, XData* rhs); + int ExprNewCompareSigConstBytes(int op, XData* lhs, + std::vector &rhs); + int ExprNewCompareConstBytesSig(int op, + std::vector &lhs, + XData* rhs); + int CompileExpr(std::string expr, XSignalCFG* cfg); + void SetExpr(std::string name, int root); + void RemoveExpr(std::string name); + std::map ListExpr(); + std::vector GetTriggeredExprKeys(); + void ClearExpr(); + void ClearAll(){this->ClearExpr();}; + virtual void Call(); + }; +} + +#endif diff --git a/include/xspcomm/xfsm.h b/include/xspcomm/xcomuse/fsm.h similarity index 94% rename from include/xspcomm/xfsm.h rename to include/xspcomm/xcomuse/fsm.h index 022effd..2c5ab42 100644 --- a/include/xspcomm/xfsm.h +++ b/include/xspcomm/xcomuse/fsm.h @@ -1,10 +1,13 @@ -#ifndef __xspcomm_xfsm_h__ -#define __xspcomm_xfsm_h__ +#ifndef XSPCOMM_XCOMUSE_FSM_H +#define XSPCOMM_XCOMUSE_FSM_H +#include "xspcomm/xcomuse/callback.h" #include "xspcomm/xexpr.h" namespace xspcomm { + class XClock; + class ComUseFsmTrigger: public ComUseStepCb{ struct FsmTransition{ int cond_root = -1; diff --git a/include/xspcomm/xcomuse/range.h b/include/xspcomm/xcomuse/range.h index 2527f15..7f511ca 100644 --- a/include/xspcomm/xcomuse/range.h +++ b/include/xspcomm/xcomuse/range.h @@ -2,6 +2,7 @@ #define XSPCOMM_XCOMUSE_RANGE_H #include "xspcomm/xcallback.h" +#include "xspcomm/common/compare.h" #include "xspcomm/xutil.h" #include @@ -27,10 +28,7 @@ namespace xspcomm { } static bool cmp(uint64_t target, uint64_t center, int range) { - if (range >= 0) { - return target <= center && center - target <= static_cast(range); - } - return target >= center && target - center <= static_cast(-static_cast(range)); + return compare::WithinRange(target, center, range); } static bool ArrayCmp(uint64_t a, uint64_t b, uint64_t self) { diff --git a/include/xspcomm/xcoverage.h b/include/xspcomm/xcoverage.h new file mode 100644 index 0000000..faa5633 --- /dev/null +++ b/include/xspcomm/xcoverage.h @@ -0,0 +1,50 @@ +#ifndef XSPCOMM_XCOVERAGE_H +#define XSPCOMM_XCOVERAGE_H + +#include "xspcomm/xpattern.h" +#include +#include +#include + +namespace xspcomm { + +class XData; +namespace detail { class CoverageState; } + +// Coverage uses numeric IDs in the hot path. Human names live in the client. +struct XCoverageItem { + int gate = -1; + XData *signal = nullptr; // direct source, any XData width; null for a cross + std::vector dimensions; // point IDs; empty for a point +}; + +struct XCoverageBin { + uint32_t item = 0; + uint32_t kind = 0; // normal / ignore / illegal / default + int root = -1; + std::vector steps; + bool overlap = true; + std::vector dimensions; // normal bin IDs for a cross tuple +}; + +struct XCoverageSnapshot { + uint32_t generation = 0; + uint64_t epoch = 0; + uint64_t tick = 0; + // group samples/gated; samples/gated/ignored/unmatched/unknown per item; + // then bin counts in declaration order. + std::vector counters; + std::vector illegal_bins; + std::vector illegal_values; // full known value in hexadecimal, sampled at the hit + std::vector illegal_ticks; + // started/completed/failed/expired/aborted/cleared/peak_active for source, + // then each bin. Empty when summary diagnostics are disabled. + std::vector diagnostics; + // Optional rows: pattern ID (0=source, bin+1), step, age, held, FSM state. + std::vector progress; + bool incomplete = false; +}; + +} // namespace xspcomm + +#endif diff --git a/include/xspcomm/xexpr.h b/include/xspcomm/xexpr.h index b58a2ef..182277f 100644 --- a/include/xspcomm/xexpr.h +++ b/include/xspcomm/xexpr.h @@ -1,17 +1,16 @@ #ifndef __xspcomm_xexpr_h__ #define __xspcomm_xexpr_h__ -#include "xspcomm/xcomuse/callback.h" -#include "xspcomm/xclock.h" -#include "xspcomm/xsignal_cfg.h" +#include "xspcomm/xdata.h" #include #include -#include #include #include namespace xspcomm { + class XSignalCFG; + // Expression Engine enum class ExprOp{ CONST = 0, @@ -75,7 +74,6 @@ namespace xspcomm { uint64_t EvalNode(int id); bool IsKnownNode(int id); bool EvalCompareNode(const ExprNode &node); - static bool CompareXData(XData* lhs, XData* rhs, ExprOp op); XData* MakeConstXData(uint32_t width, uint64_t value); XData* MakeConstXDataBytes(uint32_t width, std::vector &value); @@ -116,42 +114,7 @@ namespace xspcomm { void Clear(); }; - class ComUseExprCheck: public ComUseStepCb{ - ExprEngine engine; - struct ExprItem{ - std::string name; - int root = -1; - uint64_t last_trigger_cycle = (uint64_t)-1; - }; - std::vector clk_list; - std::vector expr_list; - std::unordered_map expr_index; - uint64_t last_eval_cycle = 0; - public: - ComUseExprCheck(XClock* clk=nullptr){if(clk)this->clk_list.push_back(clk);} - void BindXClock(XClock *clk); - int ExprNewConst(uint64_t v); - int ExprNewSignal(XData* sig); - int ExprNewUnary(int op, int child); - int ExprNewBinary(int op, int lhs, int rhs); - int ExprNewCompare(int op, int lhs, int rhs); - int ExprNewCompareSigSig(int op, XData* lhs, XData* rhs); - int ExprNewCompareSigConst(int op, XData* lhs, uint64_t rhs); - int ExprNewCompareConstSig(int op, uint64_t lhs, XData* rhs); - int ExprNewCompareSigConstBytes(int op, XData* lhs, - std::vector &rhs); - int ExprNewCompareConstBytesSig(int op, - std::vector &lhs, - XData* rhs); - int CompileExpr(std::string expr, XSignalCFG* cfg); - void SetExpr(std::string name, int root); - void RemoveExpr(std::string name); - std::map ListExpr(); - std::vector GetTriggeredExprKeys(); - void ClearExpr(); - void ClearAll(){this->ClearExpr();}; - virtual void Call(); - }; + } #endif diff --git a/include/xspcomm/xinstance.h b/include/xspcomm/xinstance.h deleted file mode 100644 index 2cfa464..0000000 --- a/include/xspcomm/xinstance.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef __xspcomm_xinstance__ -#define __xspcomm_xinstance__ - -namespace xspcomm { - -int test_xdata(); - -} - -#endif diff --git a/include/xspcomm/xpattern.h b/include/xspcomm/xpattern.h new file mode 100644 index 0000000..58cfe00 --- /dev/null +++ b/include/xspcomm/xpattern.h @@ -0,0 +1,33 @@ +#ifndef XSPCOMM_XPATTERN_H +#define XSPCOMM_XPATTERN_H + +#include + +namespace xspcomm { + +enum class XSequenceStepKind : uint8_t { + Wait = 0, + Within = 1, + Hold = 2, + Next = 3, +}; + +struct XSequenceStep { + XSequenceStepKind kind = XSequenceStepKind::Wait; + int root = -1; + uint64_t minimum = 0; + uint64_t maximum = 0; + uint64_t cycles = 0; +}; + +struct XFsmTransition { + uint32_t from_state = 0; + int root = -1; + uint32_t next_state = 0; + uint32_t terminal_id = 0; + bool trigger = false; +}; + +} // namespace xspcomm + +#endif diff --git a/include/xspcomm/xsignal_cfg.h b/include/xspcomm/xsignal_cfg.h index b31cec6..53e68c7 100644 --- a/include/xspcomm/xsignal_cfg.h +++ b/include/xspcomm/xsignal_cfg.h @@ -2,9 +2,9 @@ #define __xspcomm_xcfg__ #include "xspcomm/xdata.h" -#include "xspcomm/node.hpp" #include "xspcomm/xutil.h" #include +#include namespace xspcomm { typedef struct @@ -59,9 +59,7 @@ class XSignalCFG { private: XData* new_empty_xdata(std::string name, std::string xname, s_xsignal_cfg &cfg, bool no_return=false); void load_cfg(); - bool _set_cfg_data(fkyaml::node &var, std::string prefix=""); - bool _set_signal_meta(fkyaml::node &signal); - int _rec_set_cfg_data(fkyaml::node &var, std::string prefix); + class Parser; void _register_native_meta(const std::string &name, const s_xsignal_cfg &cfg); std::string _normalize_expr(std::string expr) const; uint64_t _parse_const_value(const std::string &value) const; diff --git a/include/xspcomm/xtrigger.h b/include/xspcomm/xtrigger.h index e40bfd9..d619ff0 100644 --- a/include/xspcomm/xtrigger.h +++ b/include/xspcomm/xtrigger.h @@ -3,6 +3,8 @@ #include "xspcomm/xclock.h" #include "xspcomm/xdata.h" +#include "xspcomm/xcoverage.h" +#include "xspcomm/detail/pattern.h" #include #include @@ -75,63 +77,6 @@ struct XRunResult { } }; -enum class XSequenceStepKind : uint8_t { - Wait = 0, - Within = 1, - Hold = 2, - Next = 3, -}; - -struct XSequenceStep { - XSequenceStepKind kind = XSequenceStepKind::Wait; - int root = -1; - uint64_t minimum = 0; - uint64_t maximum = 0; - uint64_t cycles = 0; -}; - -struct XFsmTransition { - uint32_t from_state = 0; - int root = -1; - uint32_t next_state = 0; - uint32_t terminal_id = 0; - bool trigger = false; -}; - -// Coverage uses numeric IDs in the hot path. Human names live in the client. -struct XCoverageItem { - int gate = -1; - XData *signal = nullptr; // direct source, any XData width; null for a cross - std::vector dimensions; // point IDs; empty for a point -}; - -struct XCoverageBin { - uint32_t item = 0; - uint32_t kind = 0; // normal / ignore / illegal / default - int root = -1; - std::vector steps; - bool overlap = true; - std::vector dimensions; // normal bin IDs for a cross tuple -}; - -struct XCoverageSnapshot { - uint32_t generation = 0; - uint64_t epoch = 0; - uint64_t tick = 0; - // group samples/gated; samples/gated/ignored/unmatched/unknown per item; - // then bin counts in declaration order. - std::vector counters; - std::vector illegal_bins; - std::vector illegal_values; // full known value in hexadecimal, sampled at the hit - std::vector illegal_ticks; - // started/completed/failed/expired/aborted/cleared/peak_active for source, - // then each bin. Empty when summary diagnostics are disabled. - std::vector diagnostics; - // Optional rows: pattern ID (0=source, bin+1), step, age, held, FSM state. - std::vector progress; - bool incomplete = false; -}; - class XTriggerEngine { enum class WatcherKind : uint8_t { Edge, @@ -144,16 +89,7 @@ class XTriggerEngine { Fsm, }; - struct CoverageState; - struct MatchState { - bool sequence_failed = false; - bool sequence_expired = false; - size_t sequence_index = 0; - uint64_t sequence_age = 0; - uint64_t sequence_held = 0; - uint32_t fsm_current_state = 0; - }; - struct Watcher : MatchState { + struct Watcher : detail::PatternState { uint32_t generation = 0; bool occupied = false; bool armed = false; @@ -175,7 +111,13 @@ class XTriggerEngine { std::vector fsm_transitions; uint32_t fsm_state_count = 0; uint32_t fsm_start_state = 0; - std::shared_ptr coverage; + std::shared_ptr coverage; + + void ResetPattern() { + sequence_index = sequence_age = sequence_held = 0; + fsm_current_state = fsm_start_state; + remaining = initial_count; + } }; XClock *clock = nullptr; @@ -193,13 +135,14 @@ class XTriggerEngine { void AppendHit(uint32_t slot, Watcher &watcher, XHitKind kind, uint64_t value, uint64_t event_id = 0, uint64_t x_mask = 0); - bool AdvanceSequence(const std::vector &steps, MatchState &state); - bool AdvanceFsm(const Watcher &program, MatchState &state, uint32_t &terminal); - size_t AdvanceCoverageAttempts(Watcher &watcher, const std::vector &steps, - std::vector &attempts, size_t pattern, bool overlap, size_t max_active); - void SampleCoverage(Watcher &watcher); - void ClearCoverageHistory(Watcher &watcher, bool aborted = false); - Watcher &CoverageWatcher(XRegistrationHandle handle); + bool EvaluateCoverage(Watcher &watcher); + detail::PatternView CoverageSource(const Watcher &watcher) const + { + if (watcher.kind == WatcherKind::Sequence) return {&watcher.sequence_steps}; + if (watcher.kind == WatcherKind::Fsm) return {nullptr, &watcher.fsm_transitions, watcher.fsm_start_state}; + return {}; + } + void CheckCoverageHandle(XRegistrationHandle handle) const; public: explicit XTriggerEngine(XClock *clock, size_t capacity = 1024); diff --git a/integrations/tlm/CMakeLists.txt b/integrations/tlm/CMakeLists.txt new file mode 100644 index 0000000..1982679 --- /dev/null +++ b/integrations/tlm/CMakeLists.txt @@ -0,0 +1,41 @@ +string(TOLOWER "$ENV{BUILD_XSPCOMM_VCS_UVMPS}" swig_vcs_pubsub) +if (NOT "${swig_vcs_pubsub}" STREQUAL "") + +execute_process(COMMAND bash -c "syscan -h|grep \"SystemC version\"|awk '{print $NF}'|xargs" + OUTPUT_VARIABLE sysc_version + OUTPUT_STRIP_TRAILING_WHITESPACE) +set(SYSC_VERSION "${sysc_version}") +message("The systemc version is: ${SYSC_VERSION}") + +set(VCS_HOME "$ENV{VCS_HOME}") +set(UVMC_HOME "$ENV{UVMC_HOME}") + +if (NOT EXISTS "${VCS_HOME}") +message(FATAL_ERROR "VCS_HOME not set") +endif() + +if (NOT EXISTS "${UVMC_HOME}") +message(FATAL_ERROR "UVMC_HOME not set") +endif() + + +set(SYSCAN syscan -cpp g++ -cc gcc -full64 -cflags -g -cflags -DVCS -tlm2 -cflags -I${VCS_HOME}/include/systemc${SYSC_VERSION}/tlm_utils -cflags -I${UVMC_HOME}/src/connect/sc) +set(VCS vcs -q -e VcsMain -sysc=deltasync -lca -sysc -cpp g++ -cc gcc -timescale=1ns/1ps -CFLAGS -DVCS ${VCS_HOME}/etc/uvm/src/dpi/uvm_dpi.cc -slave -full64 +${VCS_HOME}/linux64/lib/vcs_tls.o +) + +add_custom_target( + uvmps_vcs ALL + COMMAND ${SYSCAN} -cflags -I${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/src/uvm_pbsb.cpp ${UVMC_HOME}/src/connect/sc/uvmc.cpp + COMMAND ${VCS} sc_main + COMMAND cp simv ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libuvmps_vcs.so.${PROJECT_VERSION} + COMMAND cd ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} && ln -s libuvmps_vcs.so.${PROJECT_VERSION} libuvmps_vcs.so +) + +endif() + +# Standalone simulator builds consume this source SDK, outside public includes. +foreach(destination IN ITEMS share/xspcomm/tlm share/picker/share/xspcomm/tlm) + install(FILES pubsub.cpp uvmc_bridge.h python.i + DESTINATION "${XSPCOMM_INSTALL_PREFIX}${destination}") +endforeach() diff --git a/include/xspcomm/tlm_pbsb.cpp b/integrations/tlm/pubsub.cpp similarity index 96% rename from include/xspcomm/tlm_pbsb.cpp rename to integrations/tlm/pubsub.cpp index 7275fa6..c2c1eb2 100644 --- a/include/xspcomm/tlm_pbsb.cpp +++ b/integrations/tlm/pubsub.cpp @@ -1,5 +1,5 @@ -#include "xspcomm/tlm_pbsb.h" -#include "xspcomm/_uvmc_pbsb.h" +#include "xspcomm/tlm/pubsub.h" +#include "uvmc_bridge.h" #include #include diff --git a/include/xspcomm/python_tlm_pbsb.i b/integrations/tlm/python.i similarity index 95% rename from include/xspcomm/python_tlm_pbsb.i rename to integrations/tlm/python.i index 8f75baf..1268b88 100644 --- a/include/xspcomm/python_tlm_pbsb.i +++ b/integrations/tlm/python.i @@ -43,8 +43,8 @@ } %{ - #include "xspcomm/tlm_msg.h" - #include "xspcomm/tlm_pbsb.h" + #include "xspcomm/tlm/message.h" + #include "xspcomm/tlm/pubsub.h" %} %feature("director") xfunction; @@ -70,8 +70,8 @@ %include std_map.i %include std_vector.i -%include "xspcomm/tlm_msg.h" -%include "xspcomm/tlm_pbsb.h" +%include "xspcomm/tlm/message.h" +%include "xspcomm/tlm/pubsub.h" %define %d_callback(Name, Ret, T...) %feature("director") xfunction; diff --git a/include/xspcomm/_uvmc_pbsb.h b/integrations/tlm/uvmc_bridge.h similarity index 99% rename from include/xspcomm/_uvmc_pbsb.h rename to integrations/tlm/uvmc_bridge.h index ffb7034..0f1857e 100644 --- a/include/xspcomm/_uvmc_pbsb.h +++ b/integrations/tlm/uvmc_bridge.h @@ -4,7 +4,7 @@ #include #include #include "uvmc.h" -#include "tlm_msg.h" +#include "xspcomm/tlm/message.h" #include #include #include "xspcomm/xcallback.h" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 47f4eb6..5ef2d89 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,69 +1,32 @@ -include_directories(../include) +include(${PROJECT_SOURCE_DIR}/cmake/Sources.cmake) -set(SRC_FILES - xclock.cpp xdata.cpp xport.cpp xutil.cpp xcoroutine.cpp xsignal_cfg.cpp xinstance.cpp - xcomuse_base.cpp xcomuse_range.cpp - xexpr_engine.cpp xexpr_parser.cpp xfsm_trigger.cpp xtrigger.cpp) - -if(${XSPCOMM_WASM_LIB}) -set(CMAKE_C_COMPILER emcc) -set(CMAKE_CXX_COMPILER em++) -add_definitions(-DBUILDING_NODE_EXTENSION) -add_definitions(-DFORCE_NO_EXECINFO_H) +if(XSPCOMM_WASM_LIB) + set(CMAKE_C_COMPILER emcc) + set(CMAKE_CXX_COMPILER em++) + add_definitions(-DBUILDING_NODE_EXTENSION -DFORCE_NO_EXECINFO_H) endif() -add_library(${PROJECT_NAME} SHARED ${SRC_FILES}) +add_library(${PROJECT_NAME} SHARED ${XSPCOMM_SOURCES}) +target_include_directories(${PROJECT_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/third_party) set_target_properties(${PROJECT_NAME} PROPERTIES - OUTPUT_NAME "${PROJECT_NAME}" - VERSION ${PROJECT_VERSION} - SOVERSION ${XSPCOMM_ABI_VERSION}) - -string(TOLOWER "$ENV{BUILD_XSPCOMM_VCS_UVMPS}" swig_vcs_pubsub) -if (NOT "${swig_vcs_pubsub}" STREQUAL "") - -execute_process(COMMAND bash -c "syscan -h|grep \"SystemC version\"|awk '{print $NF}'|xargs" - OUTPUT_VARIABLE sysc_version - OUTPUT_STRIP_TRAILING_WHITESPACE) -set(SYSC_VERSION "${sysc_version}") -message("The systemc version is: ${SYSC_VERSION}") - -set(VCS_HOME "$ENV{VCS_HOME}") -set(UVMC_HOME "$ENV{UVMC_HOME}") - -if (NOT EXISTS "${VCS_HOME}") -message(FATAL_ERROR "VCS_HOME not set") -endif() - -if (NOT EXISTS "${UVMC_HOME}") -message(FATAL_ERROR "UVMC_HOME not set") -endif() - - -set(SYSCAN syscan -cpp g++ -cc gcc -full64 -cflags -g -cflags -DVCS -tlm2 -cflags -I${VCS_HOME}/include/systemc${SYSC_VERSION}/tlm_utils -cflags -I${UVMC_HOME}/src/connect/sc) -set(VCS vcs -q -e VcsMain -sysc=deltasync -lca -sysc -cpp g++ -cc gcc -timescale=1ns/1ps -CFLAGS -DVCS ${VCS_HOME}/etc/uvm/src/dpi/uvm_dpi.cc -slave -full64 -${VCS_HOME}/linux64/lib/vcs_tls.o -) - -add_custom_target( - uvmps_vcs ALL - COMMAND ${SYSCAN} -cflags -I${CMAKE_CURRENT_SOURCE_DIR}/../include ${CMAKE_CURRENT_SOURCE_DIR}/uvm_pbsb.cpp ${UVMC_HOME}/src/connect/sc/uvmc.cpp - COMMAND ${VCS} sc_main - COMMAND cp simv ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libuvmps_vcs.so.${PROJECT_VERSION} - COMMAND cd ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} && ln -s libuvmps_vcs.so.${PROJECT_VERSION} libuvmps_vcs.so -) - -endif() + OUTPUT_NAME ${PROJECT_NAME} + VERSION ${PROJECT_VERSION} + SOVERSION ${XSPCOMM_ABI_VERSION}) add_custom_target(copy_xspcomm_include ALL -VERBATIM -COMMAND ${CMAKE_COMMAND} -E copy_directory - "${CMAKE_CURRENT_SOURCE_DIR}/../include" - "${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}" -DEPENDS ${PROJECT_NAME} -) - -# install C++ headers and libraries -install( DIRECTORY ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/ DESTINATION ${XSPCOMM_INSTALL_PREFIX}include ) -install( TARGETS ${PROJECT_NAME} DESTINATION ${XSPCOMM_INSTALL_PREFIX}lib ) -install( DIRECTORY ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/ DESTINATION ${XSPCOMM_INSTALL_PREFIX}share/picker/include ) -install( TARGETS ${PROJECT_NAME} DESTINATION ${XSPCOMM_INSTALL_PREFIX}share/picker/lib ) + COMMAND ${CMAKE_COMMAND} + "-DSOURCE_INCLUDE=${PROJECT_SOURCE_DIR}/include/xspcomm" + "-DGENERATED_INCLUDE=${PROJECT_BINARY_DIR}/generated/xspcomm" + "-DSTAGE_INCLUDE=${XSPCOMM_HEADER_STAGE}/xspcomm" + -P ${PROJECT_SOURCE_DIR}/cmake/StageHeaders.cmake + DEPENDS ${PROJECT_NAME} + VERBATIM) + +foreach(destination IN ITEMS include share/picker/include) + install(DIRECTORY ${XSPCOMM_HEADER_STAGE}/ + DESTINATION "${XSPCOMM_INSTALL_PREFIX}${destination}") +endforeach() +foreach(destination IN ITEMS lib share/picker/lib) + install(TARGETS ${PROJECT_NAME} + DESTINATION "${XSPCOMM_INSTALL_PREFIX}${destination}") +endforeach() diff --git a/src/common/memory.cpp b/src/common/memory.cpp new file mode 100644 index 0000000..459f73b --- /dev/null +++ b/src/common/memory.cpp @@ -0,0 +1,42 @@ +#include "xspcomm/common/memory.h" + +namespace xspcomm { + +uint64_t GetFromU64Array(uint64_t address, int index){ + return ((uint64_t *)address)[index]; +} +uint32_t GetFromU32Array(uint64_t address, int index){ + return ((uint32_t *)address)[index]; +} +uint8_t GetFromU8Array(uint64_t address, int index){ + return ((uint8_t *)address)[index]; +} + +uint64_t GetFromU64Array(unsigned long long * address, int index){ + return address[index]; +} +uint32_t GetFromU32Array(unsigned int * address, int index){ + return address[index]; +} +uint8_t GetFromU8Array(unsigned char * address, int index){ + return address[index]; +} + +// Get Array Item +void SetU64Array(uint64_t address, int index, uint64_t data){((uint64_t*)address)[index]=data;} +void SetU32Array(uint64_t address, int index, uint32_t data){((uint32_t*)address)[index]=data;} +void SetU8Array(uint64_t address, int index, uint8_t data){((uint8_t*)address)[index] =data;} + +void SetU64Array(unsigned long long *address, int index, uint64_t data){address[index]=data;} +void SetU32Array(unsigned int *address, int index, uint32_t data){address[index]=data;} +void SetU8Array(unsigned char *address, int index, uint8_t data){address[index]=data;} + +uint64_t U64PtrAsU64(unsigned long long *p){return (uint64_t)p;} +uint64_t U32PtrAsU64(unsigned int *p){return (uint64_t)p;} +uint64_t U8PtrAsU64(unsigned char *p){return (uint64_t)p;} + +unsigned long long *U64AsU64Ptr(uint64_t p){return (unsigned long long *)p;} +unsigned int *U64AsU32Ptr(uint64_t p){return (unsigned int *)p;} +unsigned char *U64AsU8Ptr(uint64_t p){return (unsigned char *)p;} + +} // namespace xspcomm diff --git a/src/comuse/callback.cpp b/src/comuse/callback.cpp new file mode 100644 index 0000000..2aa1519 --- /dev/null +++ b/src/comuse/callback.cpp @@ -0,0 +1,70 @@ +#include "xspcomm/xcomuse/callback.h" +#include "xspcomm/xclock.h" + +namespace xspcomm { + +u_int64_t ComUseStepCb::GetCb(){ + return (u_int64_t)ComUseStepCb::Cb; +} + +void ComUseStepCb::Cb(uint64_t c, void *self){ + ComUseStepCb *p = (ComUseStepCb*)self; + if (unlikely(!p->cb_enable)) return; + p->cycle = c; + p->Call(); + if (unlikely(p->cb_maxcts > 0 && p->cb_counts >= p->cb_maxcts)) p->Disable(); +} + +void ComUseStepCb::Disable(){ + this->cb_enable = false; +} +void ComUseStepCb::Enable(){ + this->cb_enable = true; +} +bool ComUseStepCb::IsDisable(){ + return !this->cb_enable; +} +int ComUseStepCb::GetCbCount(){ + return this->cb_counts; +} +int ComUseStepCb::IncCbCount(){ + this->cb_counts += 1; + return this->cb_counts; +} +int ComUseStepCb::DecCbCount(){ + this->cb_counts -= 1; + return this->cb_counts; +} +void ComUseStepCb::SetMaxCbs(int c){ + this->cb_maxcts = c; +} +void ComUseStepCb::Reset(){ + this->cb_enable = true; + this->cb_counts = 0; +} + +void ComUseStepCb::Call(){ + fprintf(stderr, "Error, This is a virtual Call!\n"); +} + +void ComUseEcho::Call(){ + //convert; // 0 (char), 1 (int), 2 (float), 3 (double), 4 (string) + Assert(this->valid != NULL, "Pin[valid] is Null"); + Assert(this->data != NULL, "Pin[data] is Null"); + auto o = this->stderr_echo ? stderr: stdout; + // Echo + if((*this->valid) != 0){ + switch (this->convert) + { + case 0:fprintf(o, this->fmt.c_str(), (char)(*this->data));break; + case 1:fprintf(o, this->fmt.c_str(), (int64_t)(*this->data));break; + case 2:fprintf(o, this->fmt.c_str(), (float)(*this->data));break; + case 3:fprintf(o, this->fmt.c_str(), (double)(*this->data));break; + case 4:fprintf(o, this->fmt.c_str(), this->data->String().c_str());break; + default: Assert(0, "convert type error: %d", this->convert); + break; + } + } +} + +} // namespace xspcomm diff --git a/src/xcomuse_base.cpp b/src/comuse/condition.cpp similarity index 67% rename from src/xcomuse_base.cpp rename to src/comuse/condition.cpp index 6ab09c8..8725e2f 100644 --- a/src/xcomuse_base.cpp +++ b/src/comuse/condition.cpp @@ -1,111 +1,8 @@ -#include "xspcomm/xclock.h" -#include "xspcomm/xcomuse/callback.h" #include "xspcomm/xcomuse/condition.h" -#include "xspcomm/xcomuse/utils.h" +#include "xspcomm/xclock.h" namespace xspcomm { -u_int64_t ComUseStepCb::GetCb(){ - return (u_int64_t)ComUseStepCb::Cb; -} - -void ComUseStepCb::Cb(uint64_t c, void *self){ - ComUseStepCb *p = (ComUseStepCb*)self; - if (unlikely(!p->cb_enable)) return; - p->cycle = c; - p->Call(); - if (unlikely(p->cb_maxcts > 0 && p->cb_counts >= p->cb_maxcts)) p->Disable(); -} - -void ComUseStepCb::Disable(){ - this->cb_enable = false; -} -void ComUseStepCb::Enable(){ - this->cb_enable = true; -} -bool ComUseStepCb::IsDisable(){ - return !this->cb_enable; -} -int ComUseStepCb::GetCbCount(){ - return this->cb_counts; -} -int ComUseStepCb::IncCbCount(){ - this->cb_counts += 1; - return this->cb_counts; -} -int ComUseStepCb::DecCbCount(){ - this->cb_counts -= 1; - return this->cb_counts; -} -void ComUseStepCb::SetMaxCbs(int c){ - this->cb_maxcts = c; -} -void ComUseStepCb::Reset(){ - this->cb_enable = true; - this->cb_counts = 0; -} - -void ComUseStepCb::Call(){ - fprintf(stderr, "Error, This is a virtual Call!\n"); -} - -void ComUseEcho::Call(){ - //convert; // 0 (char), 1 (int), 2 (float), 3 (double), 4 (string) - Assert(this->valid != NULL, "Pin[valid] is Null"); - Assert(this->data != NULL, "Pin[data] is Null"); - auto o = this->stderr_echo ? stderr: stdout; - // Echo - if((*this->valid) != 0){ - switch (this->convert) - { - case 0:fprintf(o, this->fmt.c_str(), (char)(*this->data));break; - case 1:fprintf(o, this->fmt.c_str(), (int64_t)(*this->data));break; - case 2:fprintf(o, this->fmt.c_str(), (float)(*this->data));break; - case 3:fprintf(o, this->fmt.c_str(), (double)(*this->data));break; - case 4:fprintf(o, this->fmt.c_str(), this->data->String().c_str());break; - default: Assert(0, "convert type error: %d", this->convert); - break; - } - } -} - -uint64_t GetFromU64Array(uint64_t address, int index){ - return ((uint64_t *)address)[index]; -} -uint32_t GetFromU32Array(uint64_t address, int index){ - return ((uint32_t *)address)[index]; -} -uint8_t GetFromU8Array(uint64_t address, int index){ - return ((uint8_t *)address)[index]; -} - -uint64_t GetFromU64Array(unsigned long long * address, int index){ - return address[index]; -} -uint32_t GetFromU32Array(unsigned int * address, int index){ - return address[index]; -} -uint8_t GetFromU8Array(unsigned char * address, int index){ - return address[index]; -} - -// Get Array Item -void SetU64Array(uint64_t address, int index, uint64_t data){((uint64_t*)address)[index]=data;} -void SetU32Array(uint64_t address, int index, uint32_t data){((uint32_t*)address)[index]=data;} -void SetU8Array(uint64_t address, int index, uint8_t data){((uint8_t*)address)[index] =data;} - -void SetU64Array(unsigned long long *address, int index, uint64_t data){address[index]=data;} -void SetU32Array(unsigned int *address, int index, uint32_t data){address[index]=data;} -void SetU8Array(unsigned char *address, int index, uint8_t data){address[index]=data;} - -uint64_t U64PtrAsU64(unsigned long long *p){return (uint64_t)p;} -uint64_t U32PtrAsU64(unsigned int *p){return (uint64_t)p;} -uint64_t U8PtrAsU64(unsigned char *p){return (uint64_t)p;} - -unsigned long long *U64AsU64Ptr(uint64_t p){return (unsigned long long *)p;} -unsigned int *U64AsU32Ptr(uint64_t p){return (unsigned int *)p;} -unsigned char *U64AsU8Ptr(uint64_t p){return (unsigned char *)p;} - void ComUseCondCheck::BindXClock(XClock *clk){ this->clk_list.push_back(clk); } @@ -236,22 +133,6 @@ void ComUseCondCheck::ClearCondition(){ this->cond_vec_xdata.clear(); this->cond_vec_uint64.clear(); }; -int ComUseCondCheck::_valcmp(uint64_t a, u_int64_t b, int bytes){ - uint8_t *a_ptr = (uint8_t*)a; - uint8_t *b_ptr = (uint8_t*)b; - uint8_t a_sig = a_ptr[bytes-1] & 0x80; - uint8_t b_sig = b_ptr[bytes-1] & 0x80; - if (a_sig != b_sig)return a_sig ? -1: 1; - for(int i=bytes-1; i>=0; i--){ - if(a_ptr[i] != b_ptr[i]){ - if(a_sig){ - return (a_ptr[i] < b_ptr[i]) ? 1 : -1; - } - return (a_ptr[i] < b_ptr[i]) ? -1 : 1; - } - } - return 0; -} xfunction ComUseCondCheck::AsXDataXFunc(uint64_t func){ xfunction ret = (bool (*)(XData*, XData*, uint64_t))func; return ret; @@ -342,24 +223,24 @@ void ComUseCondCheck::Call(){ }; ComUseCondCheck::XDataCmpFn ComUseCondCheck::SelectXDataCmpFn(ComUseCondCmp cmp){ switch (cmp) { - case ComUseCondCmp::EQ: return &ComUseCondCheck::XDataCmpEq; - case ComUseCondCmp::NE: return &ComUseCondCheck::XDataCmpNe; - case ComUseCondCmp::GT: return &ComUseCondCheck::XDataCmpGt; - case ComUseCondCmp::GE: return &ComUseCondCheck::XDataCmpGe; - case ComUseCondCmp::LT: return &ComUseCondCheck::XDataCmpLt; - case ComUseCondCmp::LE: return &ComUseCondCheck::XDataCmpLe; + case ComUseCondCmp::EQ: return &ComUseCondCheck::XDataCmp; + case ComUseCondCmp::NE: return &ComUseCondCheck::XDataCmp; + case ComUseCondCmp::GT: return &ComUseCondCheck::XDataCmp; + case ComUseCondCmp::GE: return &ComUseCondCheck::XDataCmp; + case ComUseCondCmp::LT: return &ComUseCondCheck::XDataCmp; + case ComUseCondCmp::LE: return &ComUseCondCheck::XDataCmp; default: return nullptr; } } ComUseCondCheck::PtrCmpFn ComUseCondCheck::SelectPtrCmpFn(ComUseCondCmp cmp){ switch (cmp) { - case ComUseCondCmp::EQ: return &ComUseCondCheck::PtrCmpEq; - case ComUseCondCmp::NE: return &ComUseCondCheck::PtrCmpNe; - case ComUseCondCmp::GT: return &ComUseCondCheck::PtrCmpGt; - case ComUseCondCmp::GE: return &ComUseCondCheck::PtrCmpGe; - case ComUseCondCmp::LT: return &ComUseCondCheck::PtrCmpLt; - case ComUseCondCmp::LE: return &ComUseCondCheck::PtrCmpLe; + case ComUseCondCmp::EQ: return &ComUseCondCheck::PtrCmp; + case ComUseCondCmp::NE: return &ComUseCondCheck::PtrCmp; + case ComUseCondCmp::GT: return &ComUseCondCheck::PtrCmp; + case ComUseCondCmp::GE: return &ComUseCondCheck::PtrCmp; + case ComUseCondCmp::LT: return &ComUseCondCheck::PtrCmp; + case ComUseCondCmp::LE: return &ComUseCondCheck::PtrCmp; default: return nullptr; } } diff --git a/src/comuse/expr.cpp b/src/comuse/expr.cpp new file mode 100644 index 0000000..776e0a1 --- /dev/null +++ b/src/comuse/expr.cpp @@ -0,0 +1,135 @@ +#include "xspcomm/xcomuse/expr.h" +#include "xspcomm/xclock.h" + +namespace xspcomm { + +void ComUseExprCheck::BindXClock(XClock *clk){ + this->clk_list.push_back(clk); +} + +int ComUseExprCheck::ExprNewConst(uint64_t v){ + return this->engine.NewConst(v); +} + +int ComUseExprCheck::ExprNewSignal(XData* sig){ + return this->engine.NewSignal(sig); +} + +int ComUseExprCheck::ExprNewUnary(int op, int child){ + return this->engine.NewUnary((ExprOp)op, child); +} + +int ComUseExprCheck::ExprNewBinary(int op, int lhs, int rhs){ + return this->engine.NewBinary((ExprOp)op, lhs, rhs); +} + +int ComUseExprCheck::ExprNewCompare(int op, int lhs, int rhs){ + return this->engine.NewCompare((ExprOp)op, lhs, rhs); +} + +int ComUseExprCheck::ExprNewCompareSigSig(int op, XData* lhs, XData* rhs){ + return this->engine.NewCompareSigSig((ExprOp)op, lhs, rhs); +} + +int ComUseExprCheck::ExprNewCompareSigConst(int op, XData* lhs, uint64_t rhs){ + return this->engine.NewCompareSigConst((ExprOp)op, lhs, rhs); +} + +int ComUseExprCheck::ExprNewCompareConstSig(int op, uint64_t lhs, XData* rhs){ + return this->engine.NewCompareConstSig((ExprOp)op, lhs, rhs); +} + +int ComUseExprCheck::ExprNewCompareSigConstBytes( + int op, XData* lhs, std::vector &rhs){ + return this->engine.NewCompareSigConstBytes((ExprOp)op, lhs, rhs); +} + +int ComUseExprCheck::ExprNewCompareConstBytesSig( + int op, std::vector &lhs, XData* rhs){ + return this->engine.NewCompareConstBytesSig((ExprOp)op, lhs, rhs); +} + +int ComUseExprCheck::CompileExpr(std::string expr, XSignalCFG* cfg){ + try{ + return this->engine.CompileExpr(expr, cfg); + }catch(const std::exception &e){ + Error("CompileExpr failed: %s", e.what()); + return -1; + } +} + +void ComUseExprCheck::SetExpr(std::string name, int root){ + auto it = this->expr_index.find(name); + if(it == this->expr_index.end()){ + ExprItem item; + item.name = name; + item.root = root; + this->expr_list.push_back(std::move(item)); + this->expr_index[name] = this->expr_list.size() - 1; + }else{ + this->expr_list[it->second].root = root; + } +} + +void ComUseExprCheck::RemoveExpr(std::string name){ + auto it = this->expr_index.find(name); + if(it == this->expr_index.end()) return; + size_t at = it->second; + size_t last = this->expr_list.size() - 1; + if(at != last){ + this->expr_list[at] = std::move(this->expr_list[last]); + this->expr_index[this->expr_list[at].name] = at; + } + this->expr_list.pop_back(); + this->expr_index.erase(it); +} + +std::map ComUseExprCheck::ListExpr(){ + std::map ret; + for(const auto &item : this->expr_list){ + ret[item.name] = (item.last_trigger_cycle == this->last_eval_cycle); + } + return ret; +} + +std::vector ComUseExprCheck::GetTriggeredExprKeys(){ + std::vector ret; + for(const auto &item : this->expr_list){ + if(item.last_trigger_cycle == this->last_eval_cycle){ + ret.push_back(item.name); + } + } + return ret; +} + +void ComUseExprCheck::ClearExpr(){ + this->expr_index.clear(); + this->expr_list.clear(); + this->last_eval_cycle = 0; + this->engine.Clear(); +} + +void ComUseExprCheck::Call(){ + if (likely(this->expr_list.empty())) { + return; + } + bool triggered = false; + this->engine.SetCycle(this->cycle); + this->last_eval_cycle = this->cycle; + for(auto &item : this->expr_list){ + if(item.root < 0) continue; + uint64_t v = this->engine.Eval(item.root); + if(v != 0){ + item.last_trigger_cycle = this->last_eval_cycle; + if(!triggered){ + for(auto &clk : this->clk_list){ + clk->Disable(); + } + triggered = true; + this->IncCbCount(); + } + } + } +} + +} // namespace xspcomm diff --git a/src/xfsm_trigger.cpp b/src/comuse/fsm.cpp similarity index 99% rename from src/xfsm_trigger.cpp rename to src/comuse/fsm.cpp index d608cce..2c2efd2 100644 --- a/src/xfsm_trigger.cpp +++ b/src/comuse/fsm.cpp @@ -1,4 +1,6 @@ -#include "xspcomm/xfsm.h" +#include "xspcomm/xcomuse/fsm.h" +#include "xspcomm/xclock.h" +#include "xspcomm/xsignal_cfg.h" #include #include diff --git a/src/comuse/range.cpp b/src/comuse/range.cpp new file mode 100644 index 0000000..c83675e --- /dev/null +++ b/src/comuse/range.cpp @@ -0,0 +1,49 @@ +#include "xspcomm/xcomuse/range.h" +#include "xspcomm/xdata.h" + +#include + +namespace xspcomm { + +template +bool ComUseRangeCheck::Compare(uint64_t a, uint64_t b, uint64_t self) { + const auto* check = reinterpret_cast(self); + const auto target = compare::UIntBytes::Load(reinterpret_cast(a)); + const auto center = compare::UIntBytes::Load(reinterpret_cast(b)); + return cmp(target, center, check->range); +} + +const ComUseRangeCheck::ArrayCompare ComUseRangeCheck::comparisons[] = { + Compare<1>, Compare<2>, Compare<3>, Compare<4>, + Compare<5>, Compare<6>, Compare<7>, Compare<8>, +}; + +bool ComUseRangeCheck::ArrayWideCmp(uint64_t a, uint64_t b, uint64_t self) { + const auto* check = reinterpret_cast(self); + const size_t bytes = check->bytes; + const compare::ByteWords target{reinterpret_cast(a), bytes}; + const compare::ByteWords center{reinterpret_cast(b), bytes}; + return compare::WithinRange(target.Count(), check->range, target, center); +} + +bool ComUseRangeCheck::XDataCmp(XData* a, XData* b, uint64_t self) { + // Refresh each signal once; subsequent word reads use its existing cache. + const uint64_t av = a->U(); + const uint64_t bv = b->U(); + if (!a->DataValid() || !b->DataValid()) return false; + const int range = reinterpret_cast(self)->range; + if (a->W() <= 64 && b->W() <= 64) return cmp(av, bv, range); + const size_t awords = (static_cast(a->W()) + 31) / 32; + const size_t bwords = (static_cast(b->W()) + 31) / 32; + return compare::WithinRange(std::max(awords, bwords), range, + [=](size_t i) { + if (i == 0) return static_cast(av); + return i < awords ? a->pVecData[i].aval : 0U; + }, + [=](size_t i) { + if (i == 0) return static_cast(bv); + return i < bwords ? b->pVecData[i].aval : 0U; + }); +} + +} // namespace xspcomm diff --git a/src/xclock.cpp b/src/core/clock.cpp similarity index 100% rename from src/xclock.cpp rename to src/core/clock.cpp diff --git a/src/xcoroutine.cpp b/src/core/coroutine.cpp similarity index 100% rename from src/xcoroutine.cpp rename to src/core/coroutine.cpp diff --git a/src/core/data.cpp b/src/core/data.cpp new file mode 100644 index 0000000..114c812 --- /dev/null +++ b/src/core/data.cpp @@ -0,0 +1,708 @@ + +#include "xspcomm/xdata.h" +#include "xspcomm/common/compare.h" +#include "xspcomm/xutil.h" +#include + +namespace xspcomm { + + + +const IOType XData::In; +const IOType XData::Out; +const IOType XData::InOut; +const WriteMode XData::Imme; +const WriteMode XData::Rise; +const WriteMode XData::Fall; + +void TEST_DPI_LR(void *v) +{ + Debug("test DPI Logic Read"); +} +void TEST_DPI_LW(unsigned char v) +{ + Debug("test DPI Logic Write"); +} +void TEST_DPI_VR(void *v) +{ + Debug("test DPI Vec Read"); +} +void TEST_DPI_VW(void *v) +{ + Debug("test DPI Vec Write"); +} + +// class XData:: +void XData::update_read() +{ + this->_dpi_read(); + this->_sv_to_local(); + this->_update_shadow(); +} +void XData::update_write() +{ + this->_local_to_sv(); + this->_dpi_write(); + this->_update_shadow(); +} +bool XData::DataValid() +{ + return this->udata_is_valid; +} +void XData::_sv_to_local() +{ + this->_trunc_sv(); + if (this->mWidth == 0) { + Assert(this->mLogicData >= 0 && this->mLogicData < 4, + "Logic data[%d] need in range [0,1,2,3]", this->mLogicData); + this->udata = this->mLogicData; + this->xdata = this->udata > 1 ? 1 : 0; + this->udata_is_valid = this->udata > 1 ? false : true; + } else { + // reset buff + this->ubuff[0] = 0; + this->ubuff[1] = 0; + this->xbuff[0] = 0; + this->xbuff[1] = 0; + auto range = std::min( + 2, (int)this->vecSize); // max xdata.length is 64 (2 vecData) + for (int i = 0; i < range; i++) { + this->ubuff[i] = this->pVecData[i].aval; + this->xbuff[i] = this->pVecData[i].bval; + } + this->udata = *(uint64_t *)this->ubuff; + this->xdata = *(uint64_t *)this->xbuff; + this->udata_is_valid = true; + for (int i = 0; i < this->vecSize; i++) { + if (this->pVecData[i].bval != 0) { + this->udata_is_valid = false; + break; + } + } + } +} +void XData::_zero_sv() +{ + for (int i = 0; i < this->vecSize; i++) { + this->pVecData[i].aval = 0; + this->pVecData[i].bval = 0; + } +} +void XData::_trunc_sv() +{ + if (this->mWidth == 0) return; + this->pVecData[this->vecSize - 1].aval &= this->zero_mask; + this->pVecData[this->vecSize - 1].bval &= this->zero_mask; +} +void XData::_local_to_sv() +{ + if (this->mWidth == 0) { + Assert(this->udata >= 0 && this->udata < 4, + "Logic data to write need in range [0,1,2,3]"); + this->mLogicData = this->udata; + } else { + // reset buff + *(uint64_t *)this->ubuff = this->udata; + auto range = std::min( + 2, (int)this->vecSize); // max xdata.length is 64 (2 vecData) + for (int i = 0; i < this->vecSize; i++) { + // assign lower 64 bits + if (i < 2) { + this->pVecData[i].aval = this->ubuff[i]; + // assign higher bits zero + } else { + this->pVecData[i].aval = 0; + } + this->pVecData[i].bval = 0; + } + } + this->_sv_to_local(); +} +void XData::_dpi_read() +{ + if (unlikely(this->mIOType == IOType::Input + && this->write_mode != WriteMode::Imme)) { + return; + } + this->_dpi_check(); + if (likely(this->bitRead)) this->bitRead(&this->mLogicData); + if (likely(this->vecRead)) this->vecRead(this->pVecData); +} +WriteMode XData::GetWriteMode() +{ + return this->write_mode; +} +bool XData::SetWriteMode(WriteMode mode) +{ + if (unlikely(!this->_check_writeable())) { + Assert(false, "XData(%s) is not writeable, cannot set write mode", + this->mName.c_str()); + } + this->write_mode = mode; + return true; +}; + +void XData::WriteDirect(){ + if (likely(this->bitWrite)) this->bitWrite(this->mLogicData); + if (likely(this->vecWrite)) this->vecWrite(this->pVecData); +} + +void XData::WriteOnRise() +{ + if (unlikely(this->mIOType == IOType::Output)) return; + if (unlikely(this->write_mode != WriteMode::Rise)) return; + if (this->_need_write()) { + if (likely(this->bitWrite)) this->bitWrite(this->mLogicData); + if (likely(this->vecWrite)) this->vecWrite(this->pVecData); + } +} + +void XData::WriteOnFall() +{ + if (unlikely(this->mIOType == IOType::Output)) return; + if (unlikely(this->write_mode != WriteMode::Fall)) return; + if (this->_need_write()) { + if (likely(this->bitWrite)) this->bitWrite(this->mLogicData); + if (likely(this->vecWrite)) this->vecWrite(this->pVecData); + } +} +void XData::_dpi_write() +{ + if (unlikely(this->mIOType == IOType::Output)) return; + if (unlikely(this->write_mode != WriteMode::Imme)) return; + this->_dpi_check(); + if (this->_need_write()) { + if (likely(this->bitWrite)) this->bitWrite(this->mLogicData); + if (likely(this->vecWrite)) this->vecWrite(this->pVecData); + } +} + +bool XData::_need_write() +{ + if (unlikely(!this->ignore_same_write)) return true; + if (unlikely(!this->last_is_write)) { + this->_update_last_write(); + return true; + }; + if (this->mWidth == 0) { + if (this->last_mLogicData != this->mLogicData) { + this->_update_last_write(); + return true; + } + return false; + } else { + if (memcmp(this->last_pVecData, this->pVecData, + this->vecSize * sizeof(xsvLogicVecVal)) != 0) { + this->_update_last_write(); + return true; + } + } + return false; +} + +void XData::_update_last_write() +{ + this->last_is_write = true; + this->last_mLogicData = this->mLogicData; + if (this->mWidth > 0) { + if (this->last_pVecData == nullptr) { + this->last_pVecData = + (xsvLogicVecVal *)calloc(this->vecSize, sizeof(xsvLogicVecVal)); + } + memcpy(this->last_pVecData, this->pVecData, + this->vecSize * sizeof(xsvLogicVecVal)); + } +} + +void XData::_update_shadow() +{ + if (unlikely(this->igore_callback)) return; + if (unlikely(!this->has_on_change_cbs)) return; + bool need_call = false; + bool validate = true; + if (this->mWidth == 0) { + need_call = (this->__mLogicData != this->mLogicData); + validate = this->mLogicData < 2; + } else { + for (int i = 0; i < this->vecSize; i++) { + if (this->__pVecData[i].aval != this->pVecData[i].aval) { + need_call = true; + } + if (this->__pVecData[i].bval != this->pVecData[i].bval) { + need_call = true; + } + if (unlikely(this->pVecData[i].bval)) { validate = false; } + this->__pVecData[i] = this->pVecData[i]; + } + } + this->__mLogicData = this->mLogicData; + this->igore_callback = true; + if (need_call) { + for (auto fc : this->call_back_on_change) { + fc.fc(validate, this, this->udata, fc.args); + } + } + this->igore_callback = false; +} +void XData::_dpi_check(){ + // TBD +}; + +bool XData::_check_writeable() const +{ + return !this->readonly_backend && this->mIOType != IOType::Output; +} + +XData::XData() : XData(0, IOType::InOut){}; +XData::XData(uint32_t width, IOType itype, std::string name) : + mWidth(width), mIOType(itype), pinbind_bit(&this->mLogicData), mName(name), value(*this) +{ + this->ReInit(width, itype, name); +} +void XData::ReInit(uint32_t width, IOType itype, std::string name) +{ + if (this->pVecData) { + for (int i = 0; i < this->mWidth; i++) { delete this->pinbind_vec[i]; } + free(this->pinbind_vec); + free(this->pVecData); + free(this->__pVecData); + this->pinbind_vec = nullptr; + this->pVecData = nullptr; + this->__pVecData = nullptr; + } + this->mWidth = width; + this->mIOType = itype; + this->mName = name; + this->backend_kind = XDataBackendKind::Unknown; + this->readonly_backend = false; + this->last_is_write = false; + this->last_mLogicData = 0; + if (this->last_pVecData) { + free(this->last_pVecData); + this->last_pVecData = nullptr; + } + + auto write_fc = [this]() { + this->_dpi_write(); + this->_sv_to_local(); + this->_update_shadow(); + }; + if (width >= 1) { + // svDPI Vec is 32 bits + this->vecSize = width / 32 + ((width % 32 != 0) ? 1 : 0); + this->pVecData = + (xsvLogicVecVal *)calloc(this->vecSize, sizeof(xsvLogicVecVal)); + this->__pVecData =// [shadow data][3 * tmp data] + (xsvLogicVecVal *)calloc(this->vecSize*4, sizeof(xsvLogicVecVal)); + this->pinbind_vec = (PinBind **)calloc(this->mWidth, sizeof(PinBind *)); + for (int i = 0; i < this->mWidth; i++) { + this->pinbind_vec[i] = new PinBind(this->pVecData, i); + this->pinbind_vec[i]->write_fc = write_fc; + } + this->zero_mask = + bit32_msk(width % 32); // eg: [bin] ,|1111,1000,0000,0000| ... + if (this->zero_mask == 0) { + this->zero_mask = -1; // set: 0xFFFFFFFF + } + } else { + this->pinbind_bit.write_fc = write_fc; + this->vecSize = 0; + } + this->mLogicData = 0; + this->udata_is_valid = true; +} +XData::XData(XData &t) : + mWidth(t.mWidth), + mIOType(t.mIOType), + pinbind_bit(&this->mLogicData), + mLogicData(t.mLogicData), + value(*this) +{ + auto write_fc = [this]() { + this->_dpi_write(); + this->_sv_to_local(); + this->_update_shadow(); + }; + t.update_read(); + // members + this->xdata = t.xdata; + this->udata = t.udata; + this->vecSize = t.vecSize; + this->zero_mask = t.zero_mask; + this->write_mode = t.write_mode; + this->sub_offset = t.sub_offset; + this->sub_pVecRef = t.sub_pVecRef; + this->sub_parent = t.sub_parent; + // pointers + if (this->vecSize > 0) { + this->pVecData = + (xsvLogicVecVal *)calloc(this->vecSize, sizeof(xsvLogicVecVal)); + this->__pVecData = // [shadow data][3 * tmp data] + (xsvLogicVecVal *)calloc(this->vecSize*4, sizeof(xsvLogicVecVal)); + this->pinbind_vec = (PinBind **)calloc(this->mWidth, sizeof(PinBind *)); + for (int i = 0; i < this->mWidth; i++) { + this->pinbind_vec[i] = new PinBind(this->pVecData, i); + this->pinbind_vec[i]->write_fc = write_fc; + } + } + for (int i = 0; i < this->vecSize; i++) { + this->pVecData[i] = t.pVecData[i]; + } + this->pinbind_bit.write_fc = write_fc; + this->_sv_to_local(); + if(this->sub_pVecRef != nullptr){ + Warn("You are copying a ref xdata. bind _sub_data_fake_dpirw ..."); + this->vecRead = [this](void*data){return this->_sub_data_fake_dpir(data);}; + if(this->mIOType != IOType::Output)this->vecWrite = [this](void*data){return this->_sub_data_fake_dpiw(data);}; + this->update_read(); + } +}; +XData::~XData() +{ + if (this->pVecData) { + for (int i = 0; i < this->mWidth; i++) { delete this->pinbind_vec[i]; } + free(this->pinbind_vec); + free(this->pVecData); + free(this->__pVecData); + this->pVecData = nullptr; + } + if (this->last_pVecData) { + free(this->last_pVecData); + this->last_pVecData = nullptr; + } +} + +XData* XData::SubDataRefRaw(uint32_t start, uint32_t width, std::string name){ + Assert(this->mWidth > 0, "Only svVec support SubDataRef, need mWidth > 0"); + Assert(start + width <= this->mWidth, "SubDataRef out of range (start + witdth=%d > mWidth=%d)", start + width, this->mWidth); + auto sub = new XData(width, this->mIOType, name); + if(sub->mIOType != IOType::Output)sub->SetWriteMode(WriteMode::Imme); + sub->sub_offset = start; + sub->sub_pVecRef = this->pVecData; + sub->sub_parent = this; + sub->vecRead = [sub](void*data){return sub->_sub_data_fake_dpir(data);}; + if(sub->mIOType != IOType::Output)sub->vecWrite = [sub](void*data){return sub->_sub_data_fake_dpiw(data);}; + sub->update_read(); + return sub; +} + +std::shared_ptr XData::SubDataRef(uint32_t start, uint32_t width, std::string name){ + std::shared_ptr sub(this->SubDataRefRaw(start, width, name)); + return sub; +} + +void XData::_sub_data_fake_dpirw(void *data, bool is_read){ + DebugC(false, "_sub_data_fake_dpirw: %s", is_read ? "Read": "Write"); + if (is_read && this->sub_parent != nullptr) { + this->sub_parent->update_read(); + } + // read data from ref + uint32_t start_p = this->sub_offset / 32; + uint32_t start_f = this->sub_offset % 32; + uint32_t end_p = (this->sub_offset + this->mWidth - 1) / 32; + uint32_t end_f = (this->sub_offset + this->mWidth - 1) % 32 + 1; + if (this->vecSize == 0 && this->mWidth == 0) { + if (is_read) { + auto aval = (this->sub_pVecRef[start_p].aval >> start_f) & 1; + auto bval = (this->sub_pVecRef[start_p].bval >> start_f) & 1; + this->mLogicData = bval * 2 + aval; + } else { + uint32_t mask = 1 << start_f; + uint32_t maskb = ~mask; + this->sub_pVecRef[start_p].aval = (maskb & this->sub_pVecRef[start_p].aval) | ((this->mLogicData & 1) << start_f); + this->sub_pVecRef[start_p].bval = (maskb & this->sub_pVecRef[start_p].bval) | (((this->mLogicData >> 1) & 1) << start_f); + if (this->sub_parent != nullptr) { + this->sub_parent->_sv_to_local(); + this->sub_parent->_dpi_write(); + this->sub_parent->_update_shadow(); + } + } + return; + } + xsvLogicVecVal * p = this->sub_pVecRef; + // copy/write data + // from/dist: |_|_|_|_|_|_| + // / / + // to/from: |_|_|_| + // [shadow data][mask][aval][bval] + int secs = end_p - start_p + 1; + int * temp_mask = (int *)this->__pVecData + this->vecSize * 2; + int * temp_aval = temp_mask + secs; + int * temp_bval = temp_aval + secs; + Assert(secs > 0, "Error! SubDataRef: secs <= 0"); + Assert(secs <= (this->vecSize + 1), "Error! SubDataRef: secs(%d) > vecSize(%d) + 1", secs, this->vecSize); + if(is_read) { + // Logic for reading data + for (int i = 0; i < secs; i++) { + temp_aval[i] = p[start_p + i].aval; + temp_bval[i] = p[start_p + i].bval; + } + if (start_f > 0) { + big_shift(temp_aval, secs, start_f); + big_shift(temp_bval, secs, start_f); + } + for (int i = 0; i < this->vecSize; i++){ + this->pVecData[i].aval = temp_aval[i]; + this->pVecData[i].bval = temp_bval[i]; + } + } else { + // Logic for writing data + for (int i = 0; i < this->vecSize; i++) { + temp_aval[i] = this->pVecData[i].aval; + temp_bval[i] = this->pVecData[i].bval; + } + if (start_f > 0) { + big_shift(temp_aval, secs, -start_f); + big_shift(temp_bval, secs, -start_f); + } + // Calculate bit mask + // Use a more precise method to calculate the mask + memset(temp_mask, 0, secs * sizeof(int)); + // Create mask for the first 32-bit block + uint32_t first_mask_bits = 32 - start_f; + if (this->mWidth < first_mask_bits) { + first_mask_bits = this->mWidth; + } + uint32_t first_mask = first_mask_bits == 32 + ? 0xFFFFFFFFu + : (uint32_t(1) << first_mask_bits) - 1; + temp_mask[0] = first_mask << start_f; + // Create mask for the middle blocks (if any) + for (int i = 1; i < secs - 1; i++) { + temp_mask[i] = 0xFFFFFFFF; // Set all bits to 1 + } + // Create mask for the last block (if different from the first block) + if (secs > 1) { + uint32_t last_bits = (this->sub_offset + this->mWidth) % 32; + if (last_bits == 0) { + // If exactly on the boundary, set all bits + temp_mask[secs - 1] = 0xFFFFFFFF; + } else { + // Otherwise, set only the required bits + temp_mask[secs - 1] = bit32_msk(last_bits); + } + } + // Apply the mask and update the data + for (int i = 0; i < secs; i++) { + p[start_p + i].aval = (p[start_p + i].aval & ~temp_mask[i]) | (temp_aval[i] & temp_mask[i]); + p[start_p + i].bval = (p[start_p + i].bval & ~temp_mask[i]) | (temp_bval[i] & temp_mask[i]); + } + if (this->sub_parent != nullptr) { + this->sub_parent->_sv_to_local(); + this->sub_parent->_dpi_write(); + this->sub_parent->_update_shadow(); + } + } + // Update local data + this->_sv_to_local(); + this->_update_shadow(); +} + +void XData::_sub_data_fake_dpir(void *data){ + return this->_sub_data_fake_dpirw(data, true); +} + +void XData::_sub_data_fake_dpiw(void *data){ + return this->_sub_data_fake_dpirw(data, false); +} + + +XData &XData::Set(XData &data) +{ + return this->operator=(data); +} + +XData &XData::Set(const char *data) +{ + return this->operator=(data); +} + +XData &XData::Set(std::string &data) +{ + return this->operator=(data); +} + +XData &XData::Set(int data) +{ + return this->operator=(data); +} + +XData &XData::Set(unsigned int data) +{ + return this->operator=(data); +} + +XData &XData::Set(int64_t data) +{ + return this->operator=(data); +} + +XData &XData::Set(uint64_t data) +{ + return this->operator=(data); +} + +namespace { +template +XData &imm_set_once(XData &self, T &&data) +{ + auto mode = self.GetWriteMode(); + self.AsImmWrite(); + self.Set(std::forward(data)); + self.SetWriteMode(mode); + return self; +} +} // namespace + +XData &XData::ImmSet(XData &data) +{ + return imm_set_once(*this, data); +} + +XData &XData::ImmSet(const char *data) +{ + return imm_set_once(*this, data); +} + +XData &XData::ImmSet(std::string &data) +{ + return imm_set_once(*this, data); +} + +XData &XData::ImmSet(int data) +{ + return imm_set_once(*this, data); +} + +XData &XData::ImmSet(unsigned int data) +{ + return imm_set_once(*this, data); +} + +XData &XData::ImmSet(int64_t data) +{ + return imm_set_once(*this, data); +} + +XData &XData::ImmSet(uint64_t data) +{ + return imm_set_once(*this, data); +} + +PinBind &XData::At(int index) +{ + return this->operator[](index); +} + +std::string XData::AsBinaryString() +{ + this->update_read(); + std::string ret; + if (this->mWidth == 0) { + switch (this->mLogicData) { + case 0: return "0"; break; + case 1: return "1"; break; + case 2: return "z"; break; + case 3: return "x"; break; + default:; + } + Assert(false, "Logic Data value error(%d), need range (0 -> 3)", + this->mLogicData); + } + for (int i = this->vecSize - 1; i >= 0; i--) { + for (int j = 31; j >= 0; j--) { + if(i*32 + j >= this->mWidth)continue; + auto b = (this->pVecData[i].bval & (1 << j)) >> j; + auto a = (this->pVecData[i].aval & (1 << j)) >> j; + auto v = b * 2 + a; + switch (v) + { + case 0: if(!ret.empty())ret += "0"; break; + case 1: ret += "1"; break; + case 2: ret += "z"; break; + case 3: ret += "x"; break; + default: Assert(false, "Logic Data value error(%d), need range (0 -> 3)", v); + } + } + } + if(ret.empty()){ + ret = "0"; + } + return ret; +} + +int64_t XData::AsInt64() +{ + return this->S(); +} + +int XData::AsInt32() +{ + return (int)this->S(); +} + +bool XData::Connect(XData &xdata) +{ + auto a_drive_b = [](XData &a, XData &b) -> bool { + if (a.mIOType == IOType::Input) { + Error("Master IO type cannot be IOType::Input"); + return false; + } + if (b.mIOType == IOType::Output) { + Error("Slave IO type cannot be IOType::Output"); + return false; + } + std::string desc = "connet_" + a.mName + "_to_" + b.mName; + a.OnChange( + [](bool valid, XData *x, u_int32_t val, void *args) { + if (valid) { + auto y = ((XData *)args); + y->mLogicData = x->mLogicData; + for (int i = 0; i < x->vecSize; i++) { + y->pVecData[i] = x->pVecData[i]; + } + y->_sv_to_local(); + y->_dpi_write(); + y->_update_shadow(); + } + }, + &b, desc); + return true; + }; + // same type + // if(this->mIOType == IOType::InOut || xdata.mIOType == IOType::InOut) + // Warn("connect InOut PINs, it may cause error!"); + if (this->mIOType == xdata.mIOType) { + if (this->mIOType == IOType::InOut) { + xdata = *this; + return a_drive_b(*this, xdata); + } + Error("Can not connect same IOType[%s] %s <-> %s", + this->mIOType == IOType::Input ? "IOType::Input" : + "IOType::Output", + this->mName.c_str(), xdata.mName.c_str()); + return false; + } + // diff type + switch (this->mIOType) { + case IOType::Input: + *this = xdata; + return a_drive_b(xdata, *this); + break; + case IOType::Output: xdata = *this; return a_drive_b(*this, xdata); + case IOType::InOut: + if (xdata.mIOType == IOType::Input) { + xdata = *this; + return a_drive_b(*this, xdata); + } else { + *this = xdata; + return a_drive_b(xdata, *this); + }; + break; + default: + Assert(false, "Connect %s -> %s fail!", this->mName.c_str(), + xdata.mName.c_str()); + } +} + +} // namespace xspcomm diff --git a/src/core/data_backends.cpp b/src/core/data_backends.cpp new file mode 100644 index 0000000..0c34c1b --- /dev/null +++ b/src/core/data_backends.cpp @@ -0,0 +1,280 @@ +#include "xspcomm/xdata.h" +#include "xspcomm/xexpr.h" + +#include +#include + +namespace xspcomm { + +namespace { +// Count == 0 uses a runtime loop; fixed counts are expanded at compile time. +template +struct VecMemoryIO { + static_assert(std::is_same_v || + std::is_same_v || + std::is_same_v); + + static T Load(const unsigned char* src) { + T value; + std::memcpy(&value, src, sizeof(T)); + return value; + } + + static void Store(unsigned char* dst, uint32_t value) { + const T native = static_cast(value); + std::memcpy(dst, &native, sizeof(T)); + } + + template + static void ReadFixed(xsvLogicVecVal* dst, const unsigned char* src, + std::index_sequence) { + ((dst[I].aval = static_cast(Load(src + I * sizeof(T)))), ...); + } + + template + static void WriteFixed(unsigned char* dst, const xsvLogicVecVal* src, + std::index_sequence) { + (Store(dst + I * sizeof(T), src[I].aval), ...); + } + + static void Read(xsvLogicVecVal* dst, const void* src, size_t count) { + const auto* bytes = static_cast(src); + const size_t words = sizeof(T) < sizeof(uint32_t) ? 1 : count; + if constexpr (Count != 0) { + // ReInit preserves callbacks, so the current width can differ. + if (likely(words == Count)) { + ReadFixed(dst, bytes, std::make_index_sequence{}); + return; + } + } + for (size_t i = 0; i < words; ++i) + dst[i].aval = static_cast(Load(bytes + i * sizeof(T))); + } + + static void Write(void* dst, const xsvLogicVecVal* src, size_t count) { + auto* bytes = static_cast(dst); + const size_t words = sizeof(T) < sizeof(uint32_t) ? 1 : count; + if constexpr (Count != 0) { + if (likely(words == Count)) { + WriteFixed(bytes, src, std::make_index_sequence{}); + return; + } + } + for (size_t i = 0; i < words; ++i) + Store(bytes + i * sizeof(T), src[i].aval); + } + + using VecCallback = xfunction; + static void Bind(uint64_t address, const uint32_t& count, + VecCallback& read, VecCallback& write) { + read = [address, &count](xsvLogicVecVal* dst) { + Read(dst, reinterpret_cast(address), count); + }; + write = [address, &count](const xsvLogicVecVal* src) { + Write(reinterpret_cast(address), src, count); + }; + } +}; +} // namespace + +void XData::BindDPIPtr(uint64_t read_ptr, uint64_t write_ptr) +{ + if (this->mWidth > 0) { + this->vecRead = (void (*)(void *))read_ptr; + this->vecWrite = (void (*)(const void *))write_ptr; + } else { + this->bitRead = (void (*)(void *))read_ptr; + this->bitWrite = (void (*)(const unsigned char))write_ptr; + } + this->backend_kind = XDataBackendKind::DPI; + this->readonly_backend = false; + this->update_read(); +} + +void XData::BindDPIRW(xfunction read, + xfunction write) +{ + Assert(this->mWidth > 0, "XData(name=%s).mWidth(%d) need > 0", + this->mName.c_str(), this->mWidth); + this->vecRead = read; + this->vecWrite = write; + this->backend_kind = XDataBackendKind::DPI; + this->readonly_backend = false; + this->update_read(); +} +void XData::BindDPIRW(xfunction read, + xfunction write) +{ + Assert(this->mWidth == 0, "XData(name=%s).mWidth(%d) need == 0", + this->mName.c_str(), this->mWidth); + this->bitRead = read; + this->bitWrite = write; + this->backend_kind = XDataBackendKind::DPI; + this->readonly_backend = false; + this->update_read(); +} +void XData::BindDPIRW(void (*read)(void *), void (*write)(const void *)) { + Assert(this->mWidth > 0, "XData(name=%s).mWidth(%d) need > 0", + this->mName.c_str(), this->mWidth); + this->vecRead = read; + this->vecWrite = write; + this->backend_kind = XDataBackendKind::DPI; + this->readonly_backend = false; + this->update_read(); +} +void XData::BindDPIRW(void (*read)(void *), void (*write)(const unsigned char)) { + Assert(this->mWidth == 0, "XData(name=%s).mWidth(%d) need == 0", + this->mName.c_str(), this->mWidth); + this->bitRead = read; + this->bitWrite = write; + this->backend_kind = XDataBackendKind::DPI; + this->readonly_backend = false; + this->update_read(); +} +// TODO: Define how native two-state reads handle an existing X/Z mask and how +// X/Z writes map to native storage. Preserve bval until that policy is settled; +// see docs/xdata_xclock_optimizations.md (BindNativeData X/Z semantics). +void XData::BindNativeData(uint64_t pdata){ + if (this->mWidth == 0){ + auto* native = reinterpret_cast(pdata); + this->bitRead = [native](xsvLogic* dst){ + *dst = *native; + }; + this->bitWrite = [native](xsvLogic value){ + *native = value; + }; + } else if (this->mWidth <= 8) { + VecMemoryIO::Bind(pdata, this->vecSize, this->vecRead, this->vecWrite); + } else if (this->mWidth <= 16) { + VecMemoryIO::Bind(pdata, this->vecSize, this->vecRead, this->vecWrite); + } else { +#define XCOMM_NATIVE_WORD_COUNTS(M) \ + M(1) M(2) M(3) M(4) M(5) M(6) M(7) M(8) \ + M(9) M(10) M(11) M(12) M(13) M(14) M(15) M(16) +#define XCOMM_BIND_NATIVE_CASE(N) \ + case N: \ + VecMemoryIO::Bind(pdata, this->vecSize, this->vecRead, this->vecWrite); \ + break; + + switch (this->vecSize) { + XCOMM_NATIVE_WORD_COUNTS(XCOMM_BIND_NATIVE_CASE) + default: + VecMemoryIO::Bind(pdata, this->vecSize, this->vecRead, this->vecWrite); + break; + } +#undef XCOMM_BIND_NATIVE_CASE +#undef XCOMM_NATIVE_WORD_COUNTS + } + this->backend_kind = XDataBackendKind::MemDirect; + this->readonly_backend = false; + this->update_read(); +} +void XData::BindMixFromUvs(uint64_t pdata) +{ + if (this->mWidth == 0) { // scalar and 1bit vector + this->bitRead = [pdata](void *d) { + *(xsvLogic *)d = *(uint8_t *)pdata; + }; + } else { + if (this->mWidth <= 8) { + this->vecRead = [this, pdata](void *d) { + ((xsvLogicVecVal *)d)->aval = *(uint8_t *)pdata; + // ((xsvLogicVecVal *)d)->bval = 0; + }; + } else if (this->mWidth <= 16) { + this->vecRead = [this, pdata](void *d) { + ((xsvLogicVecVal *)d)->aval = *(uint16_t *)pdata; + // ((xsvLogicVecVal *)d)->bval = 0; + }; + } else if (this->mWidth <= 32) { + this->vecRead = [this, pdata](void *d) { + ((xsvLogicVecVal *)d)->aval = *(uint32_t *)pdata; + // ((xsvLogicVecVal *)d)->bval = 0; + }; + } else if (this->mWidth <= 64) { + this->vecRead = [this, pdata](void *d) { + ((xsvLogicVecVal *)d)->aval = ((uint32_t *)pdata)[0]; + // ((xsvLogicVecVal *)d)->bval = 0; + ((xsvLogicVecVal *)d)[1].aval = ((uint32_t *)pdata)[1]; + // ((xsvLogicVecVal *)d)[1].bval = 0; + }; + } else { // wider + this->vecRead = [this, pdata](void *d) { + for (int i = 0; i < this->vecSize; i++) { + ((xsvLogicVecVal *)d)[i].aval = ((uint32_t *)pdata)[i]; + // ((xsvLogicVecVal *)d)[i].bval = 0; + } + }; + } + } +} +void XData::BindExpr(std::shared_ptr engine, int root_id){ + Assert(engine != nullptr, "BindExpr engine is null"); + if (this->mWidth == 0) { + this->bitRead = [engine, root_id](void *d){ + auto value = engine->Eval(root_id); + *(xsvLogic *)d = (xsvLogic)((value & 0x1) ? 1 : 0); + }; + this->bitWrite = nullptr; + this->vecRead = nullptr; + this->vecWrite = nullptr; + } else { + this->vecRead = [this, engine, root_id](void *d){ + auto value = engine->Eval(root_id); + auto *vec = (xsvLogicVecVal *)d; + for (uint32_t i = 0; i < this->vecSize; i++) { + vec[i].aval = 0; + vec[i].bval = 0; + } + for (uint32_t i = 0; i < std::min(2, this->vecSize); i++) { + vec[i].aval = (uint32_t)((value >> (i * 32)) & 0xffffffffULL); + } + }; + this->vecWrite = nullptr; + this->bitRead = nullptr; + this->bitWrite = nullptr; + } + this->backend_kind = XDataBackendKind::Expr; + this->readonly_backend = true; + this->mIOType = IOType::Output; + this->update_read(); +} +void XData::BindConst(uint64_t value){ + if (this->mWidth == 0) { + this->bitRead = [value](void *d){ + *(xsvLogic *)d = (xsvLogic)((value & 0x1) ? 1 : 0); + }; + this->bitWrite = nullptr; + this->vecRead = nullptr; + this->vecWrite = nullptr; + } else { + this->vecRead = [this, value](void *d){ + auto *vec = (xsvLogicVecVal *)d; + for (uint32_t i = 0; i < this->vecSize; i++) { + vec[i].aval = 0; + vec[i].bval = 0; + } + for (uint32_t i = 0; i < std::min(2, this->vecSize); i++) { + vec[i].aval = (uint32_t)((value >> (i * 32)) & 0xffffffffULL); + } + }; + this->vecWrite = nullptr; + this->bitRead = nullptr; + this->bitWrite = nullptr; + } + this->backend_kind = XDataBackendKind::Const; + this->readonly_backend = true; + this->mIOType = IOType::Output; + this->update_read(); +} +void XData::_TestBindDPIL() +{ + this->BindDPIRW(TEST_DPI_LR, TEST_DPI_LW); +} + +void XData::_TestBindDPIV() +{ + this->BindDPIRW(TEST_DPI_VR, TEST_DPI_VW); +} + +} // namespace xspcomm diff --git a/src/core/data_value.cpp b/src/core/data_value.cpp new file mode 100644 index 0000000..ddab569 --- /dev/null +++ b/src/core/data_value.cpp @@ -0,0 +1,537 @@ +#include "xspcomm/xdata.h" +#include "xspcomm/common/compare.h" + +namespace xspcomm { + +void XData::SetBits(u_int8_t *buffer, int count, u_int8_t *mask, int start) +{ + if (unlikely(!this->_check_writeable())) { + Assert(false, "XData(%s) is not writeable, cannot set bits", + this->mName.c_str()); + } + Assert(this->mWidth > 0, "Only svVec support SetBits"); + int index = start; + for (int i = 0; i < count; i++) { + auto vec_idx = index / 4; + auto vec_off = index % 4; + if (vec_idx >= this->vecSize) break; + uint8_t byte_val = buffer[i]; + if (mask) { // FIXME: need refine + u_int8_t old = + (u_int8_t)((this->pVecData[vec_idx].aval >> vec_off * 4) + & 0x000000FF); + byte_val = (old & (~mask[i])) | (buffer[i] & mask[i]); + } + bit32_chr(this->pVecData[vec_idx].aval, vec_off, byte_val); + bit32_chr(this->pVecData[vec_idx].bval, vec_off, 0); + index += 1; + } + this->_dpi_write(); + this->_sv_to_local(); + this->_update_shadow(); +} +void XData::SetBits(u_int32_t *buffer, u_int32_t count, u_int32_t *mask, + u_int32_t start) +{ + if (unlikely(!this->_check_writeable())) { + Assert(false, "XData(%s) is not writeable, cannot set bits", + this->mName.c_str()); + } + Assert(this->mWidth > 0, "only svVec support SetBits"); + auto range = start < this->vecSize ? std::min(count, this->vecSize - start) : 0; + for (int i = 0; i < range; i++) { + if (mask == nullptr) { + this->pVecData[i + start].aval = buffer[i]; + } else { + u_int32_t aval = this->pVecData[i + start].aval & (~mask[i]); + this->pVecData[i + start].aval = aval | (buffer[i] & mask[i]); + } + this->pVecData[i + start].bval = 0; + } + this->_dpi_write(); + this->_sv_to_local(); + this->_update_shadow(); +} +bool XData::GetBits(u_int32_t *buffer, u_int32_t count) +{ + Assert(this->mWidth > 0, "only svVec support GetBits"); + this->update_read(); + auto range = std::min(count, this->vecSize); + bool ret = true; + for (int i = 0; i < range; i++) { + buffer[i] = this->pVecData[i].aval; + if (this->pVecData[i].bval != 0) { ret = false; } + } + return ret; +} + +bool XData::GetBits(u_int8_t *buffer, u_int32_t count) +{ + Assert(this->mWidth > 0, "only svVec support GetBits"); + this->update_read(); + auto range = std::min(count, 4 * this->vecSize); + bool ret = true; + for (int i = 0; i < range; i++) { + buffer[i] = ((unsigned char *)&this->pVecData[i / 4].aval)[i % 4]; + if (this->pVecData[i / 4].bval != 0) { ret = false; } + } + return ret; +} + +void XData::SetVU8(std::vector &buffer) +{ + if (unlikely(!this->_check_writeable())) { + Assert(false, "XData(%s) is not writeable, cannot set bytes", + this->mName.c_str()); + } + Assert(this->mWidth > 0, "only svVec support SetVU8"); + int index = 0; + for (int i = 0; i < this->vecSize; i++) { + this->pVecData[i].aval = 0; + this->pVecData[i].bval = 0; + } + for (auto &d : buffer) { + auto vec_idx = index / 4; + auto vec_off = index % 4; + if (vec_idx >= this->vecSize) break; + ((unsigned char *)&this->pVecData[vec_idx].aval)[vec_off] = d; + ((unsigned char *)&this->pVecData[vec_idx].bval)[vec_off] = 0; + index += 1; + } + this->_dpi_write(); + this->_sv_to_local(); + this->_update_shadow(); +} + +std::vector XData::GetVU8() +{ + std::vector ret; + Assert(this->mWidth > 0, "only svVec support GetVU8"); + this->update_read(); + for (int i = 0; i < this->vecSize; i++) { + ret.push_back(((unsigned char *)&this->pVecData[i].aval)[0]); + ret.push_back(((unsigned char *)&this->pVecData[i].aval)[1]); + ret.push_back(((unsigned char *)&this->pVecData[i].aval)[2]); + ret.push_back(((unsigned char *)&this->pVecData[i].aval)[3]); + } + return ret; +} + +std::vector XData::GetBvalBytes() +{ + std::vector ret; + Assert(this->mWidth > 0, "only svVec support GetBvalBytes"); + this->update_read(); + for (int i = 0; i < this->vecSize; i++) { + ret.push_back(((unsigned char *)&this->pVecData[i].bval)[0]); + ret.push_back(((unsigned char *)&this->pVecData[i].bval)[1]); + ret.push_back(((unsigned char *)&this->pVecData[i].bval)[2]); + ret.push_back(((unsigned char *)&this->pVecData[i].bval)[3]); + } + return ret; +} + +void XData::OnChange(xfunction func, + void *args, std::string desc) +{ + bool init_shadow = !this->has_on_change_cbs; + if (init_shadow) { + this->update_read(); + } + XDataCallBack cb; + cb.args = args; + cb.fc = func; + cb.desc = desc; + this->call_back_on_change.push_back(cb); + this->has_on_change_cbs = true; + if (init_shadow) { + this->__mLogicData = this->mLogicData; + if (this->mWidth > 0 && this->__pVecData) { + memcpy(this->__pVecData, this->pVecData, + this->vecSize * sizeof(xsvLogicVecVal)); + } + } +} + +void XData::ClearOnChangeCbs(){ + this->call_back_on_change.clear(); + this->has_on_change_cbs = false; +} + +uint32_t XData::W() +{ + return this->mWidth; +} +uint64_t XData::U() +{ + this->update_read(); + return static_cast(this->udata); +} +uint64_t XData::XMask() +{ + this->update_read(); + return static_cast(this->xdata); +} +int64_t XData::S() +{ + this->update_read(); + // A full native word already has its sign bit in the correct position. + if (this->mWidth == 0 || this->mWidth >= 64) { + return static_cast(this->udata); + } + + auto value = this->udata; + auto sign_bit = ((uint64_t)1) << (this->mWidth - 1); + + if ((value & sign_bit) != 0){ + auto mask = (((uint64_t)1) << this->mWidth) - 1; + value |= ~mask; + } + return value; +} +bool XData::B() +{ + this->update_read(); + return static_cast(this->udata); +} +std::string XData::String() +{ + this->update_read(); + if (this->mWidth == 0) { + switch (mLogicData) { + case 0: return "0"; break; + case 1: return "1"; break; + case 2: return "Z"; break; + case 3: return "X"; break; + default:; + } + Assert(false, "Logic Data value error(%d), need range (0 -> 3)", + this->mLogicData); + } + // svLogicVecVal + std::string ret; + u_int8_t abuffer[4]; // 32 bit buffer + u_int8_t bbuffer[4]; // 32 bit buffer + for (int i = this->vecSize - 1; i >= 0; i--) { + *(u_int32_t *)abuffer = this->pVecData[i].aval; + *(u_int32_t *)bbuffer = this->pVecData[i].bval; + for (int j = 3; j >= 0; j--) { + if (i * 32 + j * 8 > this->mWidth) continue; + if (bbuffer[j] == 0) { + ret += sFmt("%02x", abuffer[j]); + } else { + ret += "??"; + } + } + } + return ret; +} + +bool XData::operator==(u_int64_t data) +{ + this->update_read(); + if (this->mWidth > 64) { + return compare::EqualWords(this->vecSize, 2, + [this](size_t i) { + const auto& word = this->pVecData[i]; + return static_cast(word.aval) | (static_cast(word.bval) << 32); + }, + [data](size_t i) { return static_cast(static_cast(data >> (i * 32))); }); + } + return this->udata == data && this->xdata == 0; +} + +bool XData::operator==(const std::string &str) +{ + return sLower(this->String()) == sLower(str); +} + +bool XData::operator==(std::string &str) +{ + return this->operator==((const std::string)str); +} + +bool XData::operator==(char *str) +{ + return this->operator==((const char *)str); +} + +bool XData::operator==(const char *str) +{ + return this->operator==(std::string(str)); +} + +XData &XData::operator=(u_int64_t data) +{ + if (unlikely(!this->_check_writeable())) { + Assert(false, "XData(%s) is not writeable, cannot assign", + this->mName.c_str()); + } + + this->udata = data; + this->xdata = 0; + this->update_write(); + return *this; +} + +XData &XData::operator=(XData &data) +{ + data.update_read(); + if (unlikely(!this->_check_writeable())) { + Assert(false, "XData(%s) is not writeable, cannot assign", + this->mName.c_str()); + } + Assert(this->mWidth == data.mWidth, + "Need left.mWidth(%d) == right.mWidth(%d)", this->mWidth, + data.mWidth); + // raw + for (int i = 0; i < this->vecSize; i++) { + this->pVecData[i] = data.pVecData[i]; + } + this->udata_is_valid = data.udata_is_valid; + this->mLogicData = data.mLogicData; + // update + this->_sv_to_local(); + this->_dpi_write(); + this->_update_shadow(); + return *this; +} +bool XData::Comp(XData &data, int opcode, int eq){ + // opcode 0: equal, 1: less, 2: greater + if(opcode == 0)return *this == data; + if(*this == data){ + if(eq){ + return true; + } + return false; + } + int order; + if(this->mWidth < 64 && data.mWidth < 64){ + order = compare::Order(this->udata, data.udata); + }else{ + Assert(this->mWidth == data.mWidth, "Need left.mWidth(%d) == right.mWidth(%d)", this->mWidth, data.mWidth); + order = compare::CompareWords(this->vecSize, + [this](size_t i) { return this->pVecData[i].aval; }, + [&data](size_t i) { return data.pVecData[i].aval; }); + } + return opcode == 1 ? order < 0 : order > 0; +} + +bool XData::operator==(XData &data) +{ + // update value + this->update_read(); + data.update_read(); + if (this->mWidth < 64 && data.mWidth < 64) { + return (this->udata == data.udata) && (this->xdata == data.xdata); + } + // logic compare + auto lgc_cmp_vec = [](XData *lgc, XData *vec) -> bool { + for (int i = 1; i < vec->vecSize; i++) { + if (vec->pVecData[i].aval != 0) { return false; } + } + if (vec->pVecData[0].aval > 1 | vec->pVecData[0].bval > 1) { + return false; + } + return (vec->pVecData->aval + 2 * vec->pVecData->bval) + == lgc->mLogicData; + }; + if (this->mWidth == 0) { + if (data.mWidth == 0) { return this->mLogicData == data.mLogicData; } + return lgc_cmp_vec(this, &data); + } else { + if (data.mWidth == 0) return lgc_cmp_vec(&data, this); + return compare::EqualWords(this->vecSize, data.vecSize, + [this](size_t i) { + const auto& word = this->pVecData[i]; + return static_cast(word.aval) | (static_cast(word.bval) << 32); + }, + [&data](size_t i) { + const auto& word = data.pVecData[i]; + return static_cast(word.aval) | (static_cast(word.bval) << 32); + }); + } +} + +XData &XData::operator=(const char *str) +{ + std::string raw_str = str; + return this->operator=(raw_str); +} +XData &XData::operator=(std::string &data) +{ + if (unlikely(!this->_check_writeable())) { + Assert(false, "XData(%s) is not writeable, cannot assign", + this->mName.c_str()); + } + if (this->mWidth == 0) { + if (sLower(data) == "z") return this->operator=(2); + if (sLower(data) == "x") return this->operator=(3); + Assert(false, "for string values, Logica Data only support set Z or X"); + } else { + if (sLower(data) == "z") { + for (int i = 0; i < this->vecSize; i++) { + this->pVecData[i].aval = 0; + this->pVecData[i].bval = 1; + } + return *this; + } + if (sLower(data) == "x") { + for (int i = 0; i < this->vecSize; i++) { + this->pVecData[i].aval = 1; + this->pVecData[i].bval = 1; + } + return *this; + } + } + + auto prefix = data.substr(0, 2); + Assert(data.length() > 2 + && contians(std::vector{"0b", "0x", "::"}, prefix), + "Input string needs start with: 0b (binary), 0x (hex) or :: (str)"); + this->_zero_sv(); + + auto txt = data.substr(2); + int tsz = txt.length(); + int index = 0; + if (prefix == "0b") { + txt = sLower(txt); + for (int i = 0; i < tsz; i++) { + if (txt[i] == '_') continue; + Assert(txt[i] == '0' || txt[i] == '1' || txt[i] == 'z' + || txt[i] == 'x', + "find no 0/1 (%c) value: %s(%d) at %d", txt[i], txt.c_str(), + tsz, i); + auto vec_idx = index / 32; + auto vec_off = index % 32; + if (vec_idx >= this->vecSize) break; + if (txt[i] == '1') { + bit32_set(this->pVecData[vec_idx].aval, vec_off); + } else if (txt[i] == 'z') { + bit32_set(this->pVecData[vec_idx].bval, vec_off); + } else if (txt[i] == 'x') { + bit32_set(this->pVecData[vec_idx].aval, vec_off); + bit32_set(this->pVecData[vec_idx].bval, vec_off); + } + index += 1; + } + } else if (prefix == "0x") { + txt = sLower(txt); + for (int i = tsz - 1; i >= 0; i--) { + if (txt[i] == '_') continue; + u_int32_t hex_val = (u_int32_t)txt[i] - 48; + if (hex_val > 9) { hex_val = (u_int32_t)txt[i] - 97 + 10; } + Assert(hex_val >= 0 && hex_val <= 15 || hex_val == 0x23 + || hex_val == 0x21, + "find no hex(%c: 0x%x) value: %s(%d) at %d", txt[i], hex_val, + txt.c_str(), tsz, i); + auto vec_idx = index / 8; + auto vec_off = index % 8; + if (vec_idx >= this->vecSize) break; + if (txt[i] == 'z') { + bit32_hex(this->pVecData[vec_idx].aval, vec_off, 0x0); + bit32_hex(this->pVecData[vec_idx].bval, vec_off, 0xf); + } else if (txt[i] == 'x') { + bit32_hex(this->pVecData[vec_idx].aval, vec_off, 0xf); + bit32_hex(this->pVecData[vec_idx].bval, vec_off, 0xf); + } else { + bit32_hex(this->pVecData[vec_idx].aval, vec_off, hex_val); + } + index += 1; + } + } else if (prefix == "::") { + for (int i = 0; i < tsz; i++) { + if (txt[i] == '_') continue; + u_int32_t chr_val = (u_int32_t)txt[i]; + auto vec_idx = index / 4; + auto vec_off = index % 4; + if (vec_idx >= this->vecSize) break; + bit32_chr(this->pVecData[vec_idx].aval, vec_off, chr_val); + index += 1; + } + } + this->_dpi_write(); + this->_sv_to_local(); + this->_update_shadow(); + return *this; +} + +void XData::ReadFresh(WriteMode m) +{ + if (this->mIOType != IOType::Output) { + if (this->write_mode != m && m != WriteMode::Imme) return; + } + this->_dpi_check(); + if (this->bitRead) this->bitRead(&this->mLogicData); + if (this->vecRead) this->vecRead(this->pVecData); + this->_sv_to_local(); + this->_update_shadow(); +} + +XData &XData::FlipIOType() +{ + if (this->mIOType == IOType::Input) { + this->mIOType = IOType::Output; + } else if (this->mIOType == IOType::Output) { + this->mIOType = IOType::Input; + } + return *this; +} + +XData &XData::AsBiIO() +{ + this->mIOType = IOType::InOut; + return *this; +} + +XData &XData::AsInIO() +{ + this->mIOType = IOType::Input; + return *this; +} + +XData &XData::AsOutIO() +{ + this->mIOType = IOType::Output; + return *this; +} + +XData &XData::Invert() +{ + if (unlikely(!this->_check_writeable())) { + Assert(false, "XData(%s) is not writeable, cannot invert", + this->mName.c_str()); + } + if (this->mWidth == 0) { + this->mLogicData = this->mLogicData == 0 ? 1 : 0; + } else { + for (int i = 0; i < this->vecSize; i++) { + this->pVecData[i].aval = ~this->pVecData[i].aval; + this->pVecData[i].bval = 0; + } + } + this->_dpi_write(); + this->_sv_to_local(); + this->_update_shadow(); + return *this; +} +XData::operator u_int64_t() +{ + this->update_read(); + return this->udata; +} +XData::operator std::string() +{ + return this->mName; +} +PinBind &XData::operator[](u_int32_t index) +{ + this->update_read(); + Assert(index < this->mWidth, "Index[%d] need range 0 t %d", index, + this->mWidth); + if (this->mWidth > 0) { return *this->pinbind_vec[index]; } + return this->pinbind_bit; +} + + +} // namespace xspcomm diff --git a/src/core/data_vpi.cpp b/src/core/data_vpi.cpp new file mode 100644 index 0000000..813c34b --- /dev/null +++ b/src/core/data_vpi.cpp @@ -0,0 +1,127 @@ +#include "xspcomm/xdata.h" + +namespace xspcomm { + +bool XData::BindVPI(vpiHandle obj, func_vpi_get get, + func_vpi_get_value get_value, func_vpi_put_value put_value, std::string name){ + if(this->vpi_obj_handle != nullptr){ + Warn("BindVPI: already bind, ignore bind"); + return false; + } + if(obj == nullptr){ + Warn("BindVPI: obj is nullptr, ignore bind"); + return false; + } + if(get == nullptr || get_value == nullptr || put_value == nullptr){ + Warn("BindVPI: get/get_value/put_value is nullptr, ignore bind"); + return false; + } + PLI_INT32 type = get(vpiType, obj); + if(type != vpiNet && type != vpiReg){ + Warn("BindVPI: obj type is not vpiNet or vpiReg, ignore bind"); + return false; + } + this->vpi_obj_handle = obj; + this->vpi_get = get; + this->vpi_get_value = get_value; + this->vpi_put_value = put_value; + // Get data width + PLI_INT32 bit_size = get(vpiSize, obj); + if(bit_size == 1){ + bit_size = 0; + } + PLI_INT32 direction = vpi_get(vpiDirection, obj); + bool writeable = (direction != vpiOutput); + auto mode = IOType::Input; + if (!writeable) { + mode = IOType::Output; + } + // Reinit self + this->ReInit(bit_size, mode, name); + // Define and bind fake DPIRW + if(bit_size == 0){ // As logic + auto fake_dpir = [this](void *data){ + t_vpi_value value; + value.format = vpiIntVal; + if(this->vpi_data_type == VPI_XDATA_SCALAR){ + value.format = vpiScalarVal; + this->vpi_get_value(this->vpi_obj_handle, &value); + *(xsvLogic *)data = (xsvLogic)value.value.scalar; + }else{ + this->vpi_get_value(this->vpi_obj_handle, &value); + *(xsvLogic *)data = (xsvLogic)value.value.integer; + } + }; + auto fake_dpiw = [this](unsigned char data){ + t_vpi_value value; + value.format = vpiIntVal; + value.value.integer = data; + if(this->vpi_data_type == VPI_XDATA_SCALAR){ + value.format = vpiScalarVal; + value.value.scalar = data; + } + this->vpi_put_value(this->vpi_obj_handle, &value, nullptr, this->vpi_obj_wflage); + }; + if(writeable){ + this->BindDPIRW(fake_dpir, fake_dpiw); + }else{ + this->BindDPIRW(fake_dpir, (void(*)(unsigned char))nullptr); + } + }else{ // As vec + auto fake_dpir = [this](void *data){ + t_vpi_value value; + if((this->W() <= 32 && this->vpi_data_type == VPI_XDATA_AUTOCHECK) || this->vpi_data_type == VPI_XDATA_INTEGER){ + value.format = vpiIntVal; + this->vpi_get_value(this->vpi_obj_handle, &value); + this->pVecData[0].aval = value.value.integer; + this->pVecData[0].bval = 0; + }else{ + value.format = vpiVectorVal; + value.value.vector = nullptr; + this->vpi_get_value(this->vpi_obj_handle, &value); + if(value.value.vector == nullptr){ + Error("VPI vector read returned nullptr for XData(%s)", this->mName.c_str()); + this->_zero_sv(); + return; + } + auto dst = (xsvLogicVecVal *)data; + for(uint32_t i = 0; i < this->vecSize; i++){ + dst[i].aval = value.value.vector[i].aval; + dst[i].bval = value.value.vector[i].bval; + } + } + }; + auto fake_dpiw = [this](void *data){ + t_vpi_value value; + value.format = vpiVectorVal; + value.value.vector = (s_vpi_vecval *)data; + if((this->W() <= 32 && this->vpi_data_type == VPI_XDATA_AUTOCHECK) || this->vpi_data_type == VPI_XDATA_INTEGER){ + value.format = vpiIntVal; + value.value.integer = this->pVecData[0].aval; + } + this->vpi_put_value(this->vpi_obj_handle, &value, nullptr, this->vpi_obj_wflage); + }; + if(writeable){ + this->BindDPIRW(fake_dpir, fake_dpiw); + }else{ + this->BindDPIRW(fake_dpir, (void(*)(void *))nullptr); + } + } + if(writeable){ + this->AsImmWrite(); + } + this->backend_kind = XDataBackendKind::VPI; + this->readonly_backend = !writeable; + return true; +} + +XData * XData::FromVPI(vpiHandle obj, func_vpi_get get, func_vpi_get_value get_value, func_vpi_put_value put_value, std::string name){ + auto xdata = new XData(0, IOType::InOut, name); + if(xdata->BindVPI(obj, get, get_value, put_value, name)){ + return xdata; + } + delete xdata; + return nullptr; +} + +} // namespace xspcomm diff --git a/src/core/pin.cpp b/src/core/pin.cpp new file mode 100644 index 0000000..7227763 --- /dev/null +++ b/src/core/pin.cpp @@ -0,0 +1,107 @@ +#include "xspcomm/xdata.h" + +namespace xspcomm { + +// class PinBind +PinBind::PinBind(xsvLogicVecVal *p, int index) +{ + this->pVec = p; + this->index = index / 32; + this->mask = 1 << index % 32; +} +PinBind::PinBind(xsvLogic *p) +{ + this->pLgc = p; + this->index = -1; +} + +PinBind &PinBind::Set(int v) +{ + return this->operator=(v); +}; + +int PinBind::AsInt32() +{ + return (int)*this; +} + +PinBind &PinBind::operator=(const u_int8_t &v) +{ + auto val = (u_int8_t)v; + Assert(val >= 0 && val <= 3, + "Set value[%d(to uint8)] must in rang [0,1,2,3] 01ZX", val); + if (this->pLgc) { + *this->pLgc = val; + } else if (this->pVec) { + switch (val) { + case 0: + bit32_zro(this->pVec[this->index].aval, this->mask); + bit32_zro(this->pVec[this->index].bval, this->mask); + break; + case 1: + bit32_one(this->pVec[this->index].aval, this->mask); + bit32_zro(this->pVec[this->index].bval, this->mask); + break; + case 2: + bit32_zro(this->pVec[this->index].aval, this->mask); + bit32_one(this->pVec[this->index].bval, this->mask); + break; + case 3: + bit32_one(this->pVec[this->index].aval, this->mask); + bit32_one(this->pVec[this->index].bval, this->mask); + break; + default: + Assert(false, "Set value[%d] must in rang [0,1,2,3] 01ZX", val); + break; + } + } else { + Assert(false, "Pin is not bind to any svLogic data"); + } + // write to dpi + if (this->write_fc) this->write_fc(); + return *this; +} + +std::string PinBind::AsString(){ + int v = this->AsInt32(); + switch (v) + { + case 0: + return "0"; + case 1: + return "1"; + case 2: + return "z"; + case 3: + return "x"; + } + Assert(false, "Error! PinBind::AsString() return invalid value[%d], need in [0,1,2,3]", v); +} + +PinBind &PinBind::operator=(const std::string &v) +{ + if (sLower(v) == "z") { return this->operator=(2); } + if (sLower(v) == "x") { return this->operator=(3); } + Assert(false, "Only support set Z or X"); + return *this; +} + +PinBind &PinBind::operator=(std::string &v) +{ + return this->operator=((const std::string &)v); +} + +PinBind::operator u_int8_t() +{ + if (this->pLgc) { + return static_cast(*this->pLgc); + } else if (this->pVec) { + uint8_t aval = bit32_val(this->pVec[this->index].aval, this->mask); + uint8_t bval = bit32_val(this->pVec[this->index].bval, this->mask); + return bval * 2 + aval; + } else { + Assert(false, "PinType error"); + } +} + +} // namespace xspcomm diff --git a/src/xport.cpp b/src/core/port.cpp similarity index 100% rename from src/xport.cpp rename to src/core/port.cpp diff --git a/src/xsignal_cfg.cpp b/src/core/signal_cfg.cpp similarity index 90% rename from src/xsignal_cfg.cpp rename to src/core/signal_cfg.cpp index fdc2feb..c8c35a1 100644 --- a/src/xsignal_cfg.cpp +++ b/src/core/signal_cfg.cpp @@ -1,5 +1,6 @@ #include "xspcomm/xsignal_cfg.h" #include "xspcomm/xexpr.h" +#include "fkyaml/node.hpp" namespace xspcomm { @@ -27,6 +28,15 @@ namespace xspcomm } // namespace + class XSignalCFG::Parser { + XSignalCFG &owner; + public: + explicit Parser(XSignalCFG &owner) : owner(owner) {} + bool _set_cfg_data(fkyaml::node &var, std::string prefix = ""); + bool _set_signal_meta(fkyaml::node &signal); + int _rec_set_cfg_data(fkyaml::node &var, std::string prefix); + }; + void XSignalCFG::_register_native_meta(const std::string &name, const s_xsignal_cfg &cfg){ s_xsignal_meta meta; meta.kind = "native"; @@ -61,6 +71,7 @@ namespace xspcomm this->init_error_msg = "cfg_data is empty (need a file path or yaml string)"; return; } + Parser parser(*this); fkyaml::node root; if (fileExists(this->cfg_data)){ std::ifstream ifs(this->cfg_data); @@ -77,7 +88,7 @@ namespace xspcomm auto vars = root["variables"]; if(!vars.is_sequence()){ if(vars.is_mapping()){ - int count = this->_rec_set_cfg_data(vars, ""); + int count = parser._rec_set_cfg_data(vars, ""); Debug("%d signals loaded (map mode)", count); } else { this->init_error_msg = "variables is not a list or map"; @@ -86,7 +97,7 @@ namespace xspcomm } else { int i = 0; for(auto var : vars){ - if(!this->_set_cfg_data(var))return; + if(!parser._set_cfg_data(var))return; i++; } Debug("%d signals loaded (list mode)", i); @@ -107,21 +118,21 @@ namespace xspcomm return; } for(auto signal : signals){ - if(!this->_set_signal_meta(signal)){ + if(!parser._set_signal_meta(signal)){ return; } } } } - bool XSignalCFG::_set_cfg_data(fkyaml::node &var, std::string prefix){ + bool XSignalCFG::Parser::_set_cfg_data(fkyaml::node &var, std::string prefix){ if(!var.is_mapping()){ - this->init_error_msg = "variables item is not a map"; + owner.init_error_msg = "variables item is not a map"; return false; } for(auto key: {"offset", "mem_bytes", "rtl_width"}){ if(!var.contains(key)){ - this->init_error_msg = "variables item not contains key: " + std::string(key); + owner.init_error_msg = "variables item not contains key: " + std::string(key); return false; } } @@ -137,17 +148,17 @@ namespace xspcomm if(var.contains("array_size"))cfg.array_size = var["array_size"].get_value(); if(var.contains("type"))cfg.type = var["type"].get_value(); Assert(cfg_key.empty() == false, "cfg_key is empty, check prefix and name"); - this->cfg_map[cfg_key] = cfg; + owner.cfg_map[cfg_key] = cfg; return true; } - bool XSignalCFG::_set_signal_meta(fkyaml::node &signal){ + bool XSignalCFG::Parser::_set_signal_meta(fkyaml::node &signal){ if(!signal.is_mapping()){ - this->init_error_msg = "signals item is not a map"; + owner.init_error_msg = "signals item is not a map"; return false; } if(!signal.contains("name") || !signal.contains("kind")){ - this->init_error_msg = "signals item missing name/kind"; + owner.init_error_msg = "signals item missing name/kind"; return false; } auto name = signal["name"].get_value(); @@ -163,7 +174,7 @@ namespace xspcomm if(signal.contains("deps")){ auto deps = signal["deps"]; if(!deps.is_sequence()){ - this->init_error_msg = "signals.deps is not a list"; + owner.init_error_msg = "signals.deps is not a list"; return false; } for(auto dep : deps){ @@ -184,7 +195,7 @@ namespace xspcomm } } else if(meta.kind == "const"){ meta.is_const = true; - meta.const_value = this->_parse_const_value(meta.value); + meta.const_value = owner._parse_const_value(meta.value); } else { meta.bindable = false; if(meta.deps.empty() && !meta.source.empty()){ @@ -192,18 +203,18 @@ namespace xspcomm } } - this->signal_meta_map[name] = meta; + owner.signal_meta_map[name] = meta; return true; } - int XSignalCFG::_rec_set_cfg_data(fkyaml::node &var, std::string prefix){ + int XSignalCFG::Parser::_rec_set_cfg_data(fkyaml::node &var, std::string prefix){ if(!var.is_mapping())return 0; int count = 0; for (auto& pair : var.map_items()) { if(var.contains("offset") && var.contains("mem_bytes") && var.contains("rtl_width")){ if(var["offset"].is_integer() && var["mem_bytes"].is_integer() && var["rtl_width"].is_integer()){ if(this->_set_cfg_data(var, prefix))return 1; - Error("set cfg data failed: %s", this->init_error_msg.c_str()); + Error("set cfg data failed: %s", owner.init_error_msg.c_str()); return 0; } } diff --git a/src/xutil.cpp b/src/core/util.cpp similarity index 100% rename from src/xutil.cpp rename to src/core/util.cpp diff --git a/src/coverage/engine.cpp b/src/coverage/engine.cpp new file mode 100644 index 0000000..94bf80f --- /dev/null +++ b/src/coverage/engine.cpp @@ -0,0 +1,246 @@ +#include "state.h" +#include "xspcomm/xexpr.h" + +#include +#include + +namespace xspcomm { + +namespace detail { + +CoverageState::CoverageState(uint32_t generation, ExprEngine &expr, const PatternView &source, + const std::vector &items, const std::vector &bins, + int gate, int abort, bool raise_illegal, size_t diagnostic_capacity, + bool overlap, size_t max_active, bool diagnostics) +{ + if (items.empty() || bins.empty() || diagnostic_capacity == 0) + throw std::invalid_argument("empty coverage definition or diagnostic capacity"); + if (max_active == 0 || (!overlap && max_active != 1)) + throw std::invalid_argument("invalid coverage max_active"); + if (overlap && !source.sequence && !source.fsm) + throw std::invalid_argument("coverage overlap requires Sequence or FSM"); + if (overlap && source.sequence != nullptr && + source.sequence->front().kind != XSequenceStepKind::Wait) + throw std::invalid_argument("overlapping Sequence must start with Wait"); + auto check = [&](int r) { + if (r != -1 && !expr.ValidRoot(r)) throw std::invalid_argument("invalid coverage expression"); + }; + check(gate); check(abort); + for (size_t i = 0; i < items.size(); ++i) { + check(items[i].gate); + if (items[i].dimensions.empty() != (items[i].signal != nullptr)) + throw std::invalid_argument("coverage point requires a signal; cross must not have one"); + for (auto d : items[i].dimensions) + if (d >= i || !items[d].dimensions.empty()) throw std::invalid_argument("invalid cross point"); + } + for (const auto &bin : bins) { + if (bin.item >= items.size() || bin.kind > 3) throw std::invalid_argument("invalid coverage bin"); + check(bin.root); + for (const auto &step : bin.steps) { + if (step.root < 0 || (step.kind != XSequenceStepKind::Wait && step.kind != XSequenceStepKind::Next)) + throw std::invalid_argument("coverage transition requires adjacent steps"); + check(step.root); + } + const auto &dims = items[bin.item].dimensions; + if (bin.dimensions.size() != dims.size()) throw std::invalid_argument("invalid cross tuple"); + for (size_t j = 0; j < dims.size(); ++j) { + auto d = bin.dimensions[j]; + if (d >= bins.size() || bins[d].kind != 0 || bins[d].item != dims[j]) + throw std::invalid_argument("invalid cross bin"); + } + } + this->items = items; this->bins = bins; this->gate = gate; this->abort = abort; + this->raise_illegal = raise_illegal; this->diagnostic_capacity = diagnostic_capacity; + this->overlap = overlap; this->max_active = max_active; + // Grow only with observed starts, never reserve arbitrary max_active up front. + if (diagnostics) snapshot.diagnostics.resize((1 + bins.size()) * CoverageState::DiagnosticCount); + snapshot.generation = generation; + snapshot.counters.resize(2 + items.size() * 5 + bins.size()); + delta.resize(snapshot.counters.size()); + matched.resize(bins.size()); normal_counts.resize(items.size()); + attempts.resize(bins.size()); + for (size_t i = 0; i < bins.size(); ++i) attempts[i].reserve(bins[i].steps.size()); + snapshot.illegal_bins.reserve(diagnostic_capacity); + snapshot.illegal_values.reserve(diagnostic_capacity); + snapshot.illegal_ticks.reserve(diagnostic_capacity); +} + +void CoverageState::SamplePattern(ExprEngine &expr, const PatternView &source, uint64_t tick) +{ + const auto completed = AdvanceAttempts(expr, source, source_attempts, 0, overlap, max_active); + for (size_t i = 0; i < completed; ++i) Sample(expr, tick); +} + +size_t CoverageState::AdvanceAttempts(ExprEngine &expr, const PatternView &program, + std::vector &attempts, size_t pattern, bool overlap, size_t max_active) +{ + const bool fsm = program.fsm != nullptr; + const bool had_active = !attempts.empty(); + auto advance = [&](PatternState &state) { + uint32_t terminal = 0; + return fsm ? AdvanceFsm(*program.fsm, state, expr, terminal) : AdvanceSequence(*program.sequence, state, expr); + }; + size_t completed = 0; + size_t kept = 0; + for (size_t i = 0; i < attempts.size(); ++i) { + auto &state = attempts[i]; + const bool done = advance(state); + const bool failed = fsm ? (!done && state.fsm_current_state == program.start_state) + : state.sequence_failed; + if (done) { ++completed; Bump(pattern, CoverageState::Completed); } + else if (failed) Bump(pattern, state.sequence_expired ? CoverageState::Expired : CoverageState::Failed); + if (!done && !failed) { + if (kept != i) attempts[kept] = state; + ++kept; + } + } + attempts.resize(kept); + if (overlap || !had_active) { + PatternState candidate; + candidate.fsm_current_state = program.start_state; + const bool done = advance(candidate); + const bool active = fsm ? candidate.fsm_current_state != program.start_state + : candidate.sequence_index || candidate.sequence_age || candidate.sequence_held; + if (done || active) { + if (attempts.size() >= max_active) { + snapshot.incomplete = true; + throw std::overflow_error("coverage active pattern capacity exhausted"); + } + Bump(pattern, CoverageState::Started); + Bump(pattern, CoverageState::PeakActive, attempts.size() + 1); + if (done) { ++completed; Bump(pattern, CoverageState::Completed); } + else attempts.push_back(candidate); + } + } + return completed; +} + +void CoverageState::ClearHistory(bool aborted) +{ + const auto reason = aborted ? CoverageState::Aborted : CoverageState::Cleared; + ClearAttempts(0, source_attempts, reason); + for (size_t b = 0; b < attempts.size(); ++b) ClearAttempts(b + 1, attempts[b], reason); +} + +void CoverageState::ResetCounters() +{ + if (snapshot.epoch == std::numeric_limits::max()) + throw std::overflow_error("coverage epoch overflow"); + std::fill(snapshot.counters.begin(), snapshot.counters.end(), 0); + std::fill(snapshot.diagnostics.begin(), snapshot.diagnostics.end(), 0); + snapshot.incomplete = false; + snapshot.illegal_bins.clear(); + snapshot.illegal_values.clear(); + snapshot.illegal_ticks.clear(); + ++snapshot.epoch; +} + +XCoverageSnapshot CoverageState::Snapshot(uint64_t tick, bool progress) const +{ + auto snapshot = this->snapshot; + snapshot.tick = tick; + if (progress) { + auto append = [&](size_t id, const std::vector &states) { + for (const auto &s : states) snapshot.progress.insert(snapshot.progress.end(), { + id, s.sequence_index, s.sequence_age, s.sequence_held, s.fsm_current_state}); + }; + append(0, source_attempts); + for (size_t b = 0; b < attempts.size(); ++b) append(b + 1, attempts[b]); + } + return snapshot; +} + +void CoverageState::Sample(ExprEngine &expr, uint64_t tick) +{ + std::fill(delta.begin(), delta.end(), 0); + std::fill(matched.begin(), matched.end(), false); + std::fill(normal_counts.begin(), normal_counts.end(), 0); + const size_t offset = 2 + items.size() * 5; + auto enabled = [&](int r) { return r < 0 || (expr.IsKnown(r) && expr.Eval(r)); }; + if (!enabled(gate)) { + delta[1] = 1; + for (size_t b = 0; b < attempts.size(); ++b) ClearAttempts(b + 1, attempts[b], CoverageState::Cleared); + } else { + delta[0] = 1; + for (size_t i = 0; i < items.size(); ++i) { + const auto &item = items[i]; + const size_t base = 2 + i * 5; + const bool gate = enabled(item.gate); + // U refreshes all native words; DataValid checks X/Z across the full width. + const bool known = !item.signal || (item.signal->U(), item.signal->DataValid()); + if (!gate || !known) { + delta[base + 1] = 1; + if (gate && !known) delta[base + 4] = 1; + for (size_t b = 0; b < bins.size(); ++b) if (bins[b].item == i) + ClearAttempts(b + 1, attempts[b], CoverageState::Cleared); + continue; + } + delta[base] = 1; + if (!item.dimensions.empty()) { + uint64_t combinations = 1, hits = 0; + for (auto d : item.dimensions) { + const auto n = normal_counts[d]; + if (n && combinations > std::numeric_limits::max() / n) + throw std::overflow_error("coverage cross product overflow"); + combinations *= n; + } + for (size_t b = 0; b < bins.size(); ++b) { + const auto &bin = bins[b]; + if (bin.item != i) continue; + bool match = true; + for (auto d : bin.dimensions) match = match && matched[d]; + if (!match) continue; + delta[offset + b] = 1; ++hits; + if (bin.kind == 1) ++delta[base + 2]; + } + delta[base + 3] = combinations ? combinations - hits : 1; + continue; + } + int priority = -1; + for (size_t b = 0; b < bins.size(); ++b) { + const auto &bin = bins[b]; + if (bin.item != i || bin.kind == 3) continue; + bool hit = false; + if (bin.steps.empty()) hit = enabled(bin.root); + else { + hit = AdvanceAttempts(expr, {&bin.steps}, attempts[b], + b + 1, bin.overlap, bin.steps.size()) != 0; + } + matched[b] = hit; + if (hit) priority = std::max(priority, static_cast(bin.kind)); + } + if (priority == 1) delta[base + 2] = 1; + bool any = false; + for (size_t b = 0; b < bins.size(); ++b) { + const auto &bin = bins[b]; + if (bin.item != i) continue; + const bool hit = priority < 0 ? bin.kind == 3 : matched[b] && static_cast(bin.kind) == priority; + matched[b] = hit && bin.kind == 0; + if (matched[b]) ++normal_counts[i]; + if (hit) { delta[offset + b] = 1; any = true; } + } + if (!any) delta[base + 3] = 1; + } + } + size_t illegal = 0; + for (size_t b = 0; b < bins.size(); ++b) if (bins[b].kind == 2 && delta[offset + b]) ++illegal; + if (snapshot.illegal_bins.size() + illegal > diagnostic_capacity) + throw std::overflow_error("coverage diagnostic capacity exhausted"); + for (size_t i = 0; i < delta.size(); ++i) + if (std::numeric_limits::max() - snapshot.counters[i] < delta[i]) + throw std::overflow_error("coverage counter overflow"); + for (size_t i = 0; i < delta.size(); ++i) snapshot.counters[i] += delta[i]; + snapshot.tick = tick; + for (size_t b = 0; b < bins.size(); ++b) if (bins[b].kind == 2 && delta[offset + b]) { + snapshot.illegal_bins.push_back(b); + snapshot.illegal_ticks.push_back(tick); + auto *signal = items[bins[b].item].signal; + snapshot.illegal_values.push_back(signal ? signal->String() : "0"); + } + if (illegal && raise_illegal) throw std::runtime_error("coverage illegal bin"); +} + + +} // namespace detail + +} // namespace xspcomm diff --git a/src/coverage/state.h b/src/coverage/state.h new file mode 100644 index 0000000..29bf324 --- /dev/null +++ b/src/coverage/state.h @@ -0,0 +1,76 @@ +#ifndef XSPCOMM_COVERAGE_STATE_H +#define XSPCOMM_COVERAGE_STATE_H + +#include "xspcomm/xcoverage.h" +#include "xspcomm/detail/pattern.h" +#include "xspcomm/xexpr.h" +#include +#include +#include + +namespace xspcomm::detail { + +// Coverage owns its sampling history and counters. No trigger types are needed. +class CoverageState { +public: + enum class SampleStatus { Ready, Skip, Aborted }; + CoverageState(uint32_t generation, ExprEngine &expr, const PatternView &source, + const std::vector &items, + const std::vector &bins, + int gate, int abort, bool raise_illegal, size_t diagnostic_capacity, + bool overlap, size_t max_active, bool diagnostics); + SampleStatus BeginSample(ExprEngine &expr, uint64_t tick) + { + if (sampled && evaluated_tick == tick) return SampleStatus::Skip; + sampled = true; + evaluated_tick = tick; + if (abort >= 0 && expr.IsKnown(abort) && expr.Eval(abort)) { + ClearHistory(true); + return SampleStatus::Aborted; + } + return SampleStatus::Ready; + } + void Sample(ExprEngine &expr, uint64_t tick); + void SamplePattern(ExprEngine &expr, const PatternView &source, uint64_t tick); + void ClearHistory(bool aborted = false); + void ResetCounters(); + XCoverageSnapshot Snapshot(uint64_t tick, bool progress) const; + +private: + size_t AdvanceAttempts(ExprEngine &expr, const PatternView &program, + std::vector &attempts, size_t pattern, + bool overlap, size_t max_active); + std::vector items; + std::vector bins; + std::vector> attempts; + std::vector source_attempts; + bool overlap = false; + size_t max_active = 1; + enum { Started, Completed, Failed, Expired, Aborted, Cleared, PeakActive, DiagnosticCount }; + void Bump(size_t pattern, size_t field, uint64_t amount = 1) { + if (snapshot.diagnostics.empty()) return; + auto &count = snapshot.diagnostics[pattern * DiagnosticCount + field]; + if (field == PeakActive) { count = std::max(count, amount); return; } + if (std::numeric_limits::max() - count < amount) { + snapshot.incomplete = true; + throw std::overflow_error("coverage diagnostic counter overflow"); + } + count += amount; + } + void ClearAttempts(size_t pattern, std::vector &states, size_t reason) { + Bump(pattern, reason, states.size()); + states.clear(); + } + std::vector delta, normal_counts; + std::vector matched; + XCoverageSnapshot snapshot; + int gate = -1, abort = -1; + bool raise_illegal = true; + size_t diagnostic_capacity = 1024; + bool sampled = false; + uint64_t evaluated_tick = 0; +}; + +} // namespace xspcomm::detail + +#endif diff --git a/src/xexpr_engine.cpp b/src/expr/engine.cpp similarity index 80% rename from src/xexpr_engine.cpp rename to src/expr/engine.cpp index d7ce7ea..9562b90 100644 --- a/src/xexpr_engine.cpp +++ b/src/expr/engine.cpp @@ -1,4 +1,6 @@ #include "xspcomm/xexpr.h" +#include "xspcomm/xsignal_cfg.h" +#include "xspcomm/common/compare.h" #include #include @@ -363,17 +365,7 @@ uint64_t ExprEngine::EvalIterative(int root){ }else{ uint64_t rv = pop_val(); uint64_t lv = f.lhs_val; - bool r = false; - switch(n.op){ - case ExprOp::EQ: r = (lv == rv); break; - case ExprOp::NE: r = (lv != rv); break; - case ExprOp::GT: r = (lv > rv); break; - case ExprOp::GE: r = (lv >= rv); break; - case ExprOp::LT: r = (lv < rv); break; - case ExprOp::LE: r = (lv <= rv); break; - default: break; - } - vals.push_back(r ? 1 : 0); + vals.push_back(compare::Compare(n.op, lv, rv) ? 1 : 0); stack.pop_back(); } break; @@ -529,41 +521,11 @@ bool ExprEngine::EvalCompareNode(const ExprNode &node){ != node.rhs_xdata->pVecData[i].aval) return false; return true; } - return CompareXData(node.lhs_xdata, node.rhs_xdata, node.op); + return compare::Compare(node.op, *node.lhs_xdata, *node.rhs_xdata); } uint64_t lv = EvalNode(node.lhs); uint64_t rv = EvalNode(node.rhs); - switch(node.op){ - case ExprOp::EQ: return lv == rv; - case ExprOp::NE: return lv != rv; - case ExprOp::GT: return lv > rv; - case ExprOp::GE: return lv >= rv; - case ExprOp::LT: return lv < rv; - case ExprOp::LE: return lv <= rv; - default: - break; - } - return false; -} - -bool ExprEngine::CompareXData(XData* lhs, XData* rhs, ExprOp op){ - switch(op){ - case ExprOp::EQ: - return (*lhs == *rhs); - case ExprOp::NE: - return !(*lhs == *rhs); - case ExprOp::GT: - return lhs->Comp(*rhs, 2, 0); - case ExprOp::GE: - return lhs->Comp(*rhs, 2, 1); - case ExprOp::LT: - return lhs->Comp(*rhs, 1, 0); - case ExprOp::LE: - return lhs->Comp(*rhs, 1, 1); - default: - break; - } - return false; + return compare::Compare(node.op, lv, rv); } XData* ExprEngine::MakeConstXData(uint32_t width, uint64_t value){ @@ -722,133 +684,4 @@ void ExprEngine::OptimizeShortCircuitOrder(int root){ } -void ComUseExprCheck::BindXClock(XClock *clk){ - this->clk_list.push_back(clk); -} - -int ComUseExprCheck::ExprNewConst(uint64_t v){ - return this->engine.NewConst(v); -} - -int ComUseExprCheck::ExprNewSignal(XData* sig){ - return this->engine.NewSignal(sig); -} - -int ComUseExprCheck::ExprNewUnary(int op, int child){ - return this->engine.NewUnary((ExprOp)op, child); -} - -int ComUseExprCheck::ExprNewBinary(int op, int lhs, int rhs){ - return this->engine.NewBinary((ExprOp)op, lhs, rhs); -} - -int ComUseExprCheck::ExprNewCompare(int op, int lhs, int rhs){ - return this->engine.NewCompare((ExprOp)op, lhs, rhs); -} - -int ComUseExprCheck::ExprNewCompareSigSig(int op, XData* lhs, XData* rhs){ - return this->engine.NewCompareSigSig((ExprOp)op, lhs, rhs); -} - -int ComUseExprCheck::ExprNewCompareSigConst(int op, XData* lhs, uint64_t rhs){ - return this->engine.NewCompareSigConst((ExprOp)op, lhs, rhs); -} - -int ComUseExprCheck::ExprNewCompareConstSig(int op, uint64_t lhs, XData* rhs){ - return this->engine.NewCompareConstSig((ExprOp)op, lhs, rhs); -} - -int ComUseExprCheck::ExprNewCompareSigConstBytes( - int op, XData* lhs, std::vector &rhs){ - return this->engine.NewCompareSigConstBytes((ExprOp)op, lhs, rhs); -} - -int ComUseExprCheck::ExprNewCompareConstBytesSig( - int op, std::vector &lhs, XData* rhs){ - return this->engine.NewCompareConstBytesSig((ExprOp)op, lhs, rhs); -} - -int ComUseExprCheck::CompileExpr(std::string expr, XSignalCFG* cfg){ - try{ - return this->engine.CompileExpr(expr, cfg); - }catch(const std::exception &e){ - Error("CompileExpr failed: %s", e.what()); - return -1; - } -} - -void ComUseExprCheck::SetExpr(std::string name, int root){ - auto it = this->expr_index.find(name); - if(it == this->expr_index.end()){ - ExprItem item; - item.name = name; - item.root = root; - this->expr_list.push_back(std::move(item)); - this->expr_index[name] = this->expr_list.size() - 1; - }else{ - this->expr_list[it->second].root = root; - } -} - -void ComUseExprCheck::RemoveExpr(std::string name){ - auto it = this->expr_index.find(name); - if(it == this->expr_index.end()) return; - size_t at = it->second; - size_t last = this->expr_list.size() - 1; - if(at != last){ - this->expr_list[at] = std::move(this->expr_list[last]); - this->expr_index[this->expr_list[at].name] = at; - } - this->expr_list.pop_back(); - this->expr_index.erase(it); -} - -std::map ComUseExprCheck::ListExpr(){ - std::map ret; - for(const auto &item : this->expr_list){ - ret[item.name] = (item.last_trigger_cycle == this->last_eval_cycle); - } - return ret; -} - -std::vector ComUseExprCheck::GetTriggeredExprKeys(){ - std::vector ret; - for(const auto &item : this->expr_list){ - if(item.last_trigger_cycle == this->last_eval_cycle){ - ret.push_back(item.name); - } - } - return ret; -} - -void ComUseExprCheck::ClearExpr(){ - this->expr_index.clear(); - this->expr_list.clear(); - this->last_eval_cycle = 0; - this->engine.Clear(); -} - -void ComUseExprCheck::Call(){ - if (likely(this->expr_list.empty())) { - return; - } - bool triggered = false; - this->engine.SetCycle(this->cycle); - this->last_eval_cycle = this->cycle; - for(auto &item : this->expr_list){ - if(item.root < 0) continue; - uint64_t v = this->engine.Eval(item.root); - if(v != 0){ - item.last_trigger_cycle = this->last_eval_cycle; - if(!triggered){ - for(auto &clk : this->clk_list){ - clk->Disable(); - } - triggered = true; - this->IncCbCount(); - } - } - } -} - } // namespace xspcomm diff --git a/src/xexpr_parser.cpp b/src/expr/parser.cpp similarity index 99% rename from src/xexpr_parser.cpp rename to src/expr/parser.cpp index a4ead9a..14d6c89 100644 --- a/src/xexpr_parser.cpp +++ b/src/expr/parser.cpp @@ -1,4 +1,5 @@ #include "xspcomm/xexpr.h" +#include "xspcomm/xsignal_cfg.h" #include #include diff --git a/src/pattern.cpp b/src/pattern.cpp new file mode 100644 index 0000000..cfafe42 --- /dev/null +++ b/src/pattern.cpp @@ -0,0 +1,78 @@ +#include "xspcomm/detail/pattern.h" +#include "xspcomm/xexpr.h" + +#include +#include + +namespace xspcomm { + +namespace detail { + +bool AdvanceSequence(const std::vector &steps, PatternState &watcher, ExprEngine &expr) +{ + const auto &step = steps[watcher.sequence_index]; + bool complete = false; + watcher.sequence_failed = watcher.sequence_expired = false; + switch (step.kind) { + case XSequenceStepKind::Wait: + complete = expr.IsKnown(step.root) && + expr.Eval(step.root) != 0; + break; + case XSequenceStepKind::Next: + complete = expr.IsKnown(step.root) && expr.Eval(step.root) != 0; + if (!complete) { + watcher.sequence_index = watcher.sequence_age = watcher.sequence_held = 0; + watcher.sequence_failed = true; + } + break; + case XSequenceStepKind::Within: + watcher.sequence_age += 1; + if (watcher.sequence_age > step.maximum) { + watcher.sequence_index = 0; + watcher.sequence_age = 0; + watcher.sequence_held = 0; + watcher.sequence_failed = watcher.sequence_expired = true; + return false; + } + complete = watcher.sequence_age >= step.minimum && + expr.IsKnown(step.root) && + expr.Eval(step.root) != 0; + break; + case XSequenceStepKind::Hold: + if (expr.IsKnown(step.root) && + expr.Eval(step.root) != 0) { + watcher.sequence_held += 1; + } else { + watcher.sequence_held = 0; + } + complete = watcher.sequence_held >= step.cycles; + break; + } + if (!complete) return false; + watcher.sequence_index += 1; + watcher.sequence_age = 0; + watcher.sequence_held = 0; + if (watcher.sequence_index == steps.size()) { + watcher.sequence_index = 0; + return true; + } + return false; +} + +bool AdvanceFsm(const std::vector &transitions, PatternState &state, ExprEngine &expr, uint32_t &terminal) +{ + for (const auto &transition : transitions) { + if (transition.from_state != state.fsm_current_state) continue; + if (transition.root >= 0 && (!expr.IsKnown(transition.root) || + expr.Eval(transition.root) == 0)) continue; + if (transition.trigger) { terminal = transition.terminal_id; return true; } + state.fsm_current_state = transition.next_state; + break; + } + return false; +} + + +} // namespace detail + +} // namespace xspcomm diff --git a/src/trigger/coverage.cpp b/src/trigger/coverage.cpp new file mode 100644 index 0000000..098d709 --- /dev/null +++ b/src/trigger/coverage.cpp @@ -0,0 +1,48 @@ +#include "xspcomm/xtrigger.h" +#include "xspcomm/xexpr.h" +#include "../coverage/state.h" + +#include +#include + +namespace xspcomm { + +void XTriggerEngine::CheckCoverageHandle(XRegistrationHandle handle) const +{ + if (!handle.IsValid() || handle.slot >= watchers.size() || + !watchers[handle.slot].occupied || watchers[handle.slot].generation != handle.generation || + !watchers[handle.slot].coverage) throw std::invalid_argument("stale coverage handle"); +} + +void XTriggerEngine::AttachCoverage(XRegistrationHandle handle, + const std::vector &items, const std::vector &bins, + int gate, int abort, bool raise_illegal, size_t diagnostic_capacity, + bool overlap, size_t max_active, bool diagnostics) +{ + if (!handle.IsValid() || handle.slot >= watchers.size() || + !watchers[handle.slot].occupied || watchers[handle.slot].generation != handle.generation || + watchers[handle.slot].coverage) throw std::invalid_argument("invalid coverage registration"); + auto &watcher = watchers[handle.slot]; + watcher.coverage = std::make_shared( + handle.generation, *expr_engine, CoverageSource(watcher), items, bins, + gate, abort, raise_illegal, diagnostic_capacity, overlap, max_active, diagnostics); +} + +void XTriggerEngine::ResetCoverage(XRegistrationHandle handle, bool counters) +{ + CheckCoverageHandle(handle); + auto &watcher = watchers[handle.slot]; + watcher.ResetPattern(); + watcher.last_condition = false; + watcher.coverage->ClearHistory(); + if (counters) watcher.coverage->ResetCounters(); +} + +XCoverageSnapshot XTriggerEngine::CoverageSnapshot(XRegistrationHandle handle, bool progress) const +{ + CheckCoverageHandle(handle); + const auto &watcher = watchers[handle.slot]; + return watcher.coverage->Snapshot(clock->GetHalfTick(), progress); +} + +} // namespace xspcomm diff --git a/src/xtrigger.cpp b/src/trigger/engine.cpp similarity index 52% rename from src/xtrigger.cpp rename to src/trigger/engine.cpp index a68b21f..26ce6c4 100644 --- a/src/xtrigger.cpp +++ b/src/trigger/engine.cpp @@ -1,5 +1,6 @@ #include "xspcomm/xtrigger.h" #include "xspcomm/xexpr.h" +#include "../coverage/state.h" #include #include @@ -7,38 +8,6 @@ namespace xspcomm { -struct XTriggerEngine::CoverageState { - std::vector items; - std::vector bins; - std::vector> attempts; - std::vector source_attempts; - bool overlap = false; - size_t max_active = 1; - enum { Started, Completed, Failed, Expired, Aborted, Cleared, PeakActive, DiagnosticCount }; - void Bump(size_t pattern, size_t field, uint64_t amount = 1) { - if (snapshot.diagnostics.empty()) return; - auto &count = snapshot.diagnostics[pattern * DiagnosticCount + field]; - if (field == PeakActive) { count = std::max(count, amount); return; } - if (std::numeric_limits::max() - count < amount) { - snapshot.incomplete = true; - throw std::overflow_error("coverage diagnostic counter overflow"); - } - count += amount; - } - void ClearAttempts(size_t pattern, std::vector &states, size_t reason) { - Bump(pattern, reason, states.size()); - states.clear(); - } - std::vector delta, normal_counts; - std::vector matched; - XCoverageSnapshot snapshot; - int gate = -1, abort = -1; - bool raise_illegal = true; - size_t diagnostic_capacity = 1024; - bool sampled = false; - uint64_t evaluated_tick = 0; -}; - XTriggerEngine::XTriggerEngine(XClock *clock, size_t capacity) : clock(clock), capacity(capacity), expr_engine(std::make_unique()) { @@ -385,10 +354,8 @@ void XTriggerEngine::AppendHit( uint64_t event_id, uint64_t x_mask) { if (watcher.coverage) { - SampleCoverage(watcher); - watcher.remaining = watcher.initial_count; - watcher.sequence_index = watcher.sequence_age = watcher.sequence_held = 0; - watcher.fsm_current_state = watcher.fsm_start_state; + watcher.coverage->Sample(*expr_engine, clock->GetHalfTick()); + watcher.ResetPattern(); return; } if (event_id == 0) event_id = next_event_id++; @@ -407,122 +374,34 @@ void XTriggerEngine::AppendHit( watcher.armed = false; } -bool XTriggerEngine::AdvanceSequence(const std::vector &steps, MatchState &watcher) +bool XTriggerEngine::HasArmedPhase(XPhase phase) const { - const auto &step = steps[watcher.sequence_index]; - bool complete = false; - watcher.sequence_failed = watcher.sequence_expired = false; - switch (step.kind) { - case XSequenceStepKind::Wait: - complete = expr_engine->IsKnown(step.root) && - expr_engine->Eval(step.root) != 0; - break; - case XSequenceStepKind::Next: - complete = expr_engine->IsKnown(step.root) && expr_engine->Eval(step.root) != 0; - if (!complete) { - watcher.sequence_index = watcher.sequence_age = watcher.sequence_held = 0; - watcher.sequence_failed = true; - } - break; - case XSequenceStepKind::Within: - watcher.sequence_age += 1; - if (watcher.sequence_age > step.maximum) { - watcher.sequence_index = 0; - watcher.sequence_age = 0; - watcher.sequence_held = 0; - watcher.sequence_failed = watcher.sequence_expired = true; - return false; - } - complete = watcher.sequence_age >= step.minimum && - expr_engine->IsKnown(step.root) && - expr_engine->Eval(step.root) != 0; - break; - case XSequenceStepKind::Hold: - if (expr_engine->IsKnown(step.root) && - expr_engine->Eval(step.root) != 0) { - watcher.sequence_held += 1; - } else { - watcher.sequence_held = 0; + for (const auto &watcher : watchers) { + if (watcher.occupied && watcher.armed && watcher.phase == phase) { + return true; } - complete = watcher.sequence_held >= step.cycles; - break; - } - if (!complete) return false; - watcher.sequence_index += 1; - watcher.sequence_age = 0; - watcher.sequence_held = 0; - if (watcher.sequence_index == steps.size()) { - watcher.sequence_index = 0; - return true; } return false; } -bool XTriggerEngine::AdvanceFsm(const Watcher &program, MatchState &state, uint32_t &terminal) +// Keep the sampling adapter visible to the phase loop so it can inline. +inline bool XTriggerEngine::EvaluateCoverage(Watcher &watcher) { - for (const auto &transition : program.fsm_transitions) { - if (transition.from_state != state.fsm_current_state) continue; - if (transition.root >= 0 && (!expr_engine->IsKnown(transition.root) || - expr_engine->Eval(transition.root) == 0)) continue; - if (transition.trigger) { terminal = transition.terminal_id; return true; } - state.fsm_current_state = transition.next_state; + auto &coverage = *watcher.coverage; + switch (coverage.BeginSample(*expr_engine, clock->GetHalfTick())) { + case detail::CoverageState::SampleStatus::Skip: + return true; + case detail::CoverageState::SampleStatus::Aborted: + watcher.ResetPattern(); + watcher.last_condition = false; + return true; + case detail::CoverageState::SampleStatus::Ready: break; } - return false; -} - -size_t XTriggerEngine::AdvanceCoverageAttempts(Watcher &watcher, - const std::vector &steps, std::vector &attempts, - size_t pattern, bool overlap, size_t max_active) -{ - auto &c = *watcher.coverage; - const bool fsm = pattern == 0 && watcher.kind == WatcherKind::Fsm; - const bool had_active = !attempts.empty(); - auto advance = [&](MatchState &state) { - uint32_t terminal = 0; - return fsm ? AdvanceFsm(watcher, state, terminal) : AdvanceSequence(steps, state); - }; - size_t completed = 0; - size_t kept = 0; - for (size_t i = 0; i < attempts.size(); ++i) { - auto &state = attempts[i]; - const bool done = advance(state); - const bool failed = fsm ? (!done && state.fsm_current_state == watcher.fsm_start_state) - : state.sequence_failed; - if (done) { ++completed; c.Bump(pattern, CoverageState::Completed); } - else if (failed) c.Bump(pattern, state.sequence_expired ? CoverageState::Expired : CoverageState::Failed); - if (!done && !failed) { - if (kept != i) attempts[kept] = state; - ++kept; - } - } - attempts.resize(kept); - if (overlap || !had_active) { - MatchState candidate; - candidate.fsm_current_state = watcher.fsm_start_state; - const bool done = advance(candidate); - const bool active = fsm ? candidate.fsm_current_state != watcher.fsm_start_state - : candidate.sequence_index || candidate.sequence_age || candidate.sequence_held; - if (done || active) { - if (attempts.size() >= max_active) { - c.snapshot.incomplete = true; - throw std::overflow_error("coverage active pattern capacity exhausted"); - } - c.Bump(pattern, CoverageState::Started); - c.Bump(pattern, CoverageState::PeakActive, attempts.size() + 1); - if (done) { ++completed; c.Bump(pattern, CoverageState::Completed); } - else attempts.push_back(candidate); - } - } - return completed; -} - -bool XTriggerEngine::HasArmedPhase(XPhase phase) const -{ - for (const auto &watcher : watchers) { - if (watcher.occupied && watcher.armed && watcher.phase == phase) { - return true; - } + const auto source = CoverageSource(watcher); + if (source.sequence || source.fsm) { + coverage.SamplePattern(*expr_engine, source, clock->GetHalfTick()); + return true; } return false; } @@ -538,22 +417,7 @@ void XTriggerEngine::EvaluatePhase(XPhase phase) watcher.phase != phase) { continue; } - if (watcher.coverage) { - auto &c = *watcher.coverage; - if (c.sampled && c.evaluated_tick == clock->GetHalfTick()) continue; - c.sampled = true; - c.evaluated_tick = clock->GetHalfTick(); - if (c.abort >= 0 && expr_engine->IsKnown(c.abort) && expr_engine->Eval(c.abort)) { - ClearCoverageHistory(watcher, true); - continue; - } - if (watcher.kind == WatcherKind::Sequence || watcher.kind == WatcherKind::Fsm) { - const auto completed = AdvanceCoverageAttempts(watcher, watcher.sequence_steps, - c.source_attempts, 0, c.overlap, c.max_active); - for (size_t i = 0; i < completed; ++i) SampleCoverage(watcher); - continue; - } - } + if (watcher.coverage && EvaluateCoverage(watcher)) continue; switch (watcher.kind) { case WatcherKind::Edge: { if (edge_event_id == 0) edge_event_id = next_event_id++; @@ -635,13 +499,13 @@ void XTriggerEngine::EvaluatePhase(XPhase phase) break; } case WatcherKind::Sequence: - if (AdvanceSequence(watcher.sequence_steps, watcher)) { + if (detail::AdvanceSequence(watcher.sequence_steps, watcher, *expr_engine)) { AppendHit(slot, watcher, XHitKind::Fsm, 1); } break; case WatcherKind::Fsm: { uint32_t terminal = 0; - if (AdvanceFsm(watcher, watcher, terminal)) AppendHit(slot, watcher, XHitKind::Fsm, terminal); + if (detail::AdvanceFsm(watcher.fsm_transitions, watcher, *expr_engine, terminal)) AppendHit(slot, watcher, XHitKind::Fsm, terminal); break; } } @@ -723,211 +587,6 @@ XRunResult XTriggerEngine::SamplePhase(XPhase phase) } -XTriggerEngine::Watcher &XTriggerEngine::CoverageWatcher(XRegistrationHandle handle) -{ - if (!handle.IsValid() || handle.slot >= watchers.size() || - !watchers[handle.slot].occupied || watchers[handle.slot].generation != handle.generation || - !watchers[handle.slot].coverage) throw std::invalid_argument("stale coverage handle"); - return watchers[handle.slot]; -} - -void XTriggerEngine::AttachCoverage(XRegistrationHandle handle, - const std::vector &items, const std::vector &bins, - int gate, int abort, bool raise_illegal, size_t diagnostic_capacity, - bool overlap, size_t max_active, bool diagnostics) -{ - if (!handle.IsValid() || handle.slot >= watchers.size() || - !watchers[handle.slot].occupied || watchers[handle.slot].generation != handle.generation || - watchers[handle.slot].coverage) throw std::invalid_argument("invalid coverage registration"); - if (items.empty() || bins.empty() || diagnostic_capacity == 0) - throw std::invalid_argument("empty coverage definition or diagnostic capacity"); - const auto &program = watchers[handle.slot]; - if (max_active == 0 || (!overlap && max_active != 1)) - throw std::invalid_argument("invalid coverage max_active"); - if (overlap && program.kind != WatcherKind::Sequence && program.kind != WatcherKind::Fsm) - throw std::invalid_argument("coverage overlap requires Sequence or FSM"); - if (overlap && program.kind == WatcherKind::Sequence && - program.sequence_steps.front().kind != XSequenceStepKind::Wait) - throw std::invalid_argument("overlapping Sequence must start with Wait"); - auto check = [&](int r) { - if (r != -1 && !expr_engine->ValidRoot(r)) throw std::invalid_argument("invalid coverage expression"); - }; - check(gate); check(abort); - for (size_t i = 0; i < items.size(); ++i) { - check(items[i].gate); - if (items[i].dimensions.empty() != (items[i].signal != nullptr)) - throw std::invalid_argument("coverage point requires a signal; cross must not have one"); - for (auto d : items[i].dimensions) - if (d >= i || !items[d].dimensions.empty()) throw std::invalid_argument("invalid cross point"); - } - for (const auto &bin : bins) { - if (bin.item >= items.size() || bin.kind > 3) throw std::invalid_argument("invalid coverage bin"); - check(bin.root); - for (const auto &step : bin.steps) { - if (step.root < 0 || (step.kind != XSequenceStepKind::Wait && step.kind != XSequenceStepKind::Next)) - throw std::invalid_argument("coverage transition requires adjacent steps"); - check(step.root); - } - const auto &dims = items[bin.item].dimensions; - if (bin.dimensions.size() != dims.size()) throw std::invalid_argument("invalid cross tuple"); - for (size_t j = 0; j < dims.size(); ++j) { - auto d = bin.dimensions[j]; - if (d >= bins.size() || bins[d].kind != 0 || bins[d].item != dims[j]) - throw std::invalid_argument("invalid cross bin"); - } - } - auto c = std::make_shared(); - c->items = items; c->bins = bins; c->gate = gate; c->abort = abort; - c->raise_illegal = raise_illegal; c->diagnostic_capacity = diagnostic_capacity; - c->overlap = overlap; c->max_active = max_active; - // Grow only with observed starts, never reserve arbitrary max_active up front. - if (diagnostics) c->snapshot.diagnostics.resize((1 + bins.size()) * CoverageState::DiagnosticCount); - c->snapshot.generation = handle.generation; - c->snapshot.counters.resize(2 + items.size() * 5 + bins.size()); - c->delta.resize(c->snapshot.counters.size()); - c->matched.resize(bins.size()); c->normal_counts.resize(items.size()); - c->attempts.resize(bins.size()); - for (size_t i = 0; i < bins.size(); ++i) c->attempts[i].reserve(bins[i].steps.size()); - c->snapshot.illegal_bins.reserve(diagnostic_capacity); - c->snapshot.illegal_values.reserve(diagnostic_capacity); - c->snapshot.illegal_ticks.reserve(diagnostic_capacity); - watchers[handle.slot].coverage = std::move(c); -} - -void XTriggerEngine::ClearCoverageHistory(Watcher &watcher, bool aborted) -{ - watcher.sequence_index = watcher.sequence_age = watcher.sequence_held = 0; - watcher.fsm_current_state = watcher.fsm_start_state; - watcher.last_condition = false; - watcher.remaining = watcher.initial_count; - auto &c = *watcher.coverage; - const auto reason = aborted ? CoverageState::Aborted : CoverageState::Cleared; - c.ClearAttempts(0, c.source_attempts, reason); - for (size_t b = 0; b < c.attempts.size(); ++b) c.ClearAttempts(b + 1, c.attempts[b], reason); -} - -void XTriggerEngine::ResetCoverage(XRegistrationHandle handle, bool counters) -{ - auto &w = CoverageWatcher(handle); - ClearCoverageHistory(w); - if (counters) { - auto &s = w.coverage->snapshot; - if (s.epoch == std::numeric_limits::max()) throw std::overflow_error("coverage epoch overflow"); - std::fill(s.counters.begin(), s.counters.end(), 0); - std::fill(s.diagnostics.begin(), s.diagnostics.end(), 0); - s.incomplete = false; - s.illegal_bins.clear(); s.illegal_values.clear(); s.illegal_ticks.clear(); ++s.epoch; - } -} - -XCoverageSnapshot XTriggerEngine::CoverageSnapshot(XRegistrationHandle handle, bool progress) const -{ - auto &c = *const_cast(this)->CoverageWatcher(handle).coverage; - auto snapshot = c.snapshot; - snapshot.tick = clock->GetHalfTick(); - if (progress) { - auto append = [&](size_t id, const std::vector &states) { - for (const auto &s : states) snapshot.progress.insert(snapshot.progress.end(), { - id, s.sequence_index, s.sequence_age, s.sequence_held, s.fsm_current_state}); - }; - append(0, c.source_attempts); - for (size_t b = 0; b < c.attempts.size(); ++b) append(b + 1, c.attempts[b]); - } - return snapshot; -} - -void XTriggerEngine::SampleCoverage(Watcher &watcher) -{ - auto &c = *watcher.coverage; - auto &delta = c.delta; - std::fill(delta.begin(), delta.end(), 0); - std::fill(c.matched.begin(), c.matched.end(), false); - std::fill(c.normal_counts.begin(), c.normal_counts.end(), 0); - const size_t offset = 2 + c.items.size() * 5; - auto enabled = [&](int r) { return r < 0 || (expr_engine->IsKnown(r) && expr_engine->Eval(r)); }; - if (!enabled(c.gate)) { - delta[1] = 1; - for (size_t b = 0; b < c.attempts.size(); ++b) c.ClearAttempts(b + 1, c.attempts[b], CoverageState::Cleared); - } else { - delta[0] = 1; - for (size_t i = 0; i < c.items.size(); ++i) { - const auto &item = c.items[i]; - const size_t base = 2 + i * 5; - const bool gate = enabled(item.gate); - // U refreshes all native words; DataValid checks X/Z across the full width. - const bool known = !item.signal || (item.signal->U(), item.signal->DataValid()); - if (!gate || !known) { - delta[base + 1] = 1; - if (gate && !known) delta[base + 4] = 1; - for (size_t b = 0; b < c.bins.size(); ++b) if (c.bins[b].item == i) - c.ClearAttempts(b + 1, c.attempts[b], CoverageState::Cleared); - continue; - } - delta[base] = 1; - if (!item.dimensions.empty()) { - uint64_t combinations = 1, hits = 0; - for (auto d : item.dimensions) { - const auto n = c.normal_counts[d]; - if (n && combinations > std::numeric_limits::max() / n) - throw std::overflow_error("coverage cross product overflow"); - combinations *= n; - } - for (size_t b = 0; b < c.bins.size(); ++b) { - const auto &bin = c.bins[b]; - if (bin.item != i) continue; - bool match = true; - for (auto d : bin.dimensions) match = match && c.matched[d]; - if (!match) continue; - delta[offset + b] = 1; ++hits; - if (bin.kind == 1) ++delta[base + 2]; - } - delta[base + 3] = combinations ? combinations - hits : 1; - continue; - } - int priority = -1; - for (size_t b = 0; b < c.bins.size(); ++b) { - const auto &bin = c.bins[b]; - if (bin.item != i || bin.kind == 3) continue; - bool hit = false; - if (bin.steps.empty()) hit = enabled(bin.root); - else { - hit = AdvanceCoverageAttempts(watcher, bin.steps, c.attempts[b], - b + 1, bin.overlap, bin.steps.size()) != 0; - } - c.matched[b] = hit; - if (hit) priority = std::max(priority, static_cast(bin.kind)); - } - if (priority == 1) delta[base + 2] = 1; - bool any = false; - for (size_t b = 0; b < c.bins.size(); ++b) { - const auto &bin = c.bins[b]; - if (bin.item != i) continue; - const bool hit = priority < 0 ? bin.kind == 3 : c.matched[b] && static_cast(bin.kind) == priority; - c.matched[b] = hit && bin.kind == 0; - if (c.matched[b]) ++c.normal_counts[i]; - if (hit) { delta[offset + b] = 1; any = true; } - } - if (!any) delta[base + 3] = 1; - } - } - size_t illegal = 0; - for (size_t b = 0; b < c.bins.size(); ++b) if (c.bins[b].kind == 2 && delta[offset + b]) ++illegal; - if (c.snapshot.illegal_bins.size() + illegal > c.diagnostic_capacity) - throw std::overflow_error("coverage diagnostic capacity exhausted"); - for (size_t i = 0; i < delta.size(); ++i) - if (std::numeric_limits::max() - c.snapshot.counters[i] < delta[i]) - throw std::overflow_error("coverage counter overflow"); - for (size_t i = 0; i < delta.size(); ++i) c.snapshot.counters[i] += delta[i]; - c.snapshot.tick = clock->GetHalfTick(); - for (size_t b = 0; b < c.bins.size(); ++b) if (c.bins[b].kind == 2 && delta[offset + b]) { - c.snapshot.illegal_bins.push_back(b); - c.snapshot.illegal_ticks.push_back(clock->GetHalfTick()); - auto *signal = c.items[c.bins[b].item].signal; - c.snapshot.illegal_values.push_back(signal ? signal->String() : "0"); - } - if (illegal && c.raise_illegal) throw std::runtime_error("coverage illegal bin"); -} - size_t XTriggerEngine::ActiveCount() const { size_t count = 0; diff --git a/src/xcomuse_range.cpp b/src/xcomuse_range.cpp deleted file mode 100644 index 95b3f16..0000000 --- a/src/xcomuse_range.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "xspcomm/xcomuse/range.h" -#include "xspcomm/xdata.h" - -#include -#include - -namespace xspcomm { - -namespace { -template struct UIntBytes; - -template -struct UIntWord { - static uint64_t Load(const unsigned char* data) { - T value; - std::memcpy(&value, data, sizeof(value)); - return value; - } -}; - -template -struct UIntParts { - static uint64_t Load(const unsigned char* data) { - return UIntBytes::Load(data) | - (UIntBytes::Load(data + LowBytes) << (LowBytes * 8)); - } -}; - -// Fixed byte widths use native integers or consecutive low/high parts. -#define XSPCOMM_UINT_BYTES(Bytes, ...) \ - template <> struct UIntBytes : __VA_ARGS__ {}; -XSPCOMM_UINT_BYTES(1, UIntWord) -XSPCOMM_UINT_BYTES(2, UIntWord) -XSPCOMM_UINT_BYTES(3, UIntParts<2, 1>) -XSPCOMM_UINT_BYTES(4, UIntWord) -XSPCOMM_UINT_BYTES(5, UIntParts<4, 1>) -XSPCOMM_UINT_BYTES(6, UIntParts<4, 2>) -XSPCOMM_UINT_BYTES(7, UIntParts<6, 1>) -XSPCOMM_UINT_BYTES(8, UIntWord) -#undef XSPCOMM_UINT_BYTES - -template -bool range_compare(size_t words, int range, ReadA a, ReadB b) { - const uint32_t limit = static_cast(range >= 0 ? range : -static_cast(range)); - uint64_t borrow = 0; - for (size_t i = 0; i < words; ++i) { - const uint64_t lhs = range >= 0 ? b(i) : a(i); - const uint64_t rhs = (range >= 0 ? a(i) : b(i)) + borrow; - const uint32_t difference = static_cast(lhs - rhs); - borrow = lhs < rhs; - // The tolerance fits in the low word; all higher difference words must be zero. - if (difference > (i == 0 ? limit : 0U)) return false; - } - return borrow == 0; -} -} - -template -bool ComUseRangeCheck::Compare(uint64_t a, uint64_t b, uint64_t self) { - const auto* check = reinterpret_cast(self); - const auto target = UIntBytes::Load(reinterpret_cast(a)); - const auto center = UIntBytes::Load(reinterpret_cast(b)); - return cmp(target, center, check->range); -} - -const ComUseRangeCheck::ArrayCompare ComUseRangeCheck::comparisons[] = { - Compare<1>, Compare<2>, Compare<3>, Compare<4>, - Compare<5>, Compare<6>, Compare<7>, Compare<8>, -}; - -bool ComUseRangeCheck::ArrayWideCmp(uint64_t a, uint64_t b, uint64_t self) { - const auto* check = reinterpret_cast(self); - const size_t bytes = check->bytes; - auto word = [bytes](uint64_t address, size_t i) { - const size_t offset = i * 4; - const size_t count = std::min(sizeof(uint32_t), bytes - offset); - const auto* data = reinterpret_cast(address) + offset; - if (count == sizeof(uint32_t)) return UIntBytes<4>::Load(data); - switch (count) { - case 1: return UIntBytes<1>::Load(data); - case 2: return UIntBytes<2>::Load(data); - default: return UIntBytes<3>::Load(data); - } - }; - return range_compare((bytes + 3) / 4, check->range, - [=](size_t i) { return word(a, i); }, - [=](size_t i) { return word(b, i); }); -} - -bool ComUseRangeCheck::XDataCmp(XData* a, XData* b, uint64_t self) { - // Refresh each signal once; subsequent word reads use its existing cache. - const uint64_t av = a->U(); - const uint64_t bv = b->U(); - if (!a->DataValid() || !b->DataValid()) return false; - const int range = reinterpret_cast(self)->range; - if (a->W() <= 64 && b->W() <= 64) return cmp(av, bv, range); - const size_t awords = (static_cast(a->W()) + 31) / 32; - const size_t bwords = (static_cast(b->W()) + 31) / 32; - return range_compare(std::max(awords, bwords), range, - [=](size_t i) { - if (i == 0) return static_cast(av); - return i < awords ? a->pVecData[i].aval : 0U; - }, - [=](size_t i) { - if (i == 0) return static_cast(bv); - return i < bwords ? b->pVecData[i].aval : 0U; - }); -} - -} // namespace xspcomm diff --git a/src/xdata.cpp b/src/xdata.cpp deleted file mode 100644 index 29e3b37..0000000 --- a/src/xdata.cpp +++ /dev/null @@ -1,1742 +0,0 @@ - -#include "xspcomm/xdata.h" -#include "xspcomm/xexpr.h" -#include "xspcomm/xutil.h" -#include -#include - -namespace xspcomm { - -namespace { -// Count == 0 uses a runtime loop; fixed counts are expanded at compile time. -template -struct VecMemoryIO { - static_assert(std::is_same_v || - std::is_same_v || - std::is_same_v); - - static T Load(const unsigned char* src) { - T value; - std::memcpy(&value, src, sizeof(T)); - return value; - } - - static void Store(unsigned char* dst, uint32_t value) { - const T native = static_cast(value); - std::memcpy(dst, &native, sizeof(T)); - } - - template - static void ReadFixed(xsvLogicVecVal* dst, const unsigned char* src, - std::index_sequence) { - ((dst[I].aval = static_cast(Load(src + I * sizeof(T)))), ...); - } - - template - static void WriteFixed(unsigned char* dst, const xsvLogicVecVal* src, - std::index_sequence) { - (Store(dst + I * sizeof(T), src[I].aval), ...); - } - - static void Read(xsvLogicVecVal* dst, const void* src, size_t count) { - const auto* bytes = static_cast(src); - const size_t words = sizeof(T) < sizeof(uint32_t) ? 1 : count; - if constexpr (Count != 0) { - // ReInit preserves callbacks, so the current width can differ. - if (likely(words == Count)) { - ReadFixed(dst, bytes, std::make_index_sequence{}); - return; - } - } - for (size_t i = 0; i < words; ++i) - dst[i].aval = static_cast(Load(bytes + i * sizeof(T))); - } - - static void Write(void* dst, const xsvLogicVecVal* src, size_t count) { - auto* bytes = static_cast(dst); - const size_t words = sizeof(T) < sizeof(uint32_t) ? 1 : count; - if constexpr (Count != 0) { - if (likely(words == Count)) { - WriteFixed(bytes, src, std::make_index_sequence{}); - return; - } - } - for (size_t i = 0; i < words; ++i) - Store(bytes + i * sizeof(T), src[i].aval); - } - - using VecCallback = xfunction; - static void Bind(uint64_t address, const uint32_t& count, - VecCallback& read, VecCallback& write) { - read = [address, &count](xsvLogicVecVal* dst) { - Read(dst, reinterpret_cast(address), count); - }; - write = [address, &count](const xsvLogicVecVal* src) { - Write(reinterpret_cast(address), src, count); - }; - } -}; -} // namespace - -const IOType XData::In; -const IOType XData::Out; -const IOType XData::InOut; -const WriteMode XData::Imme; -const WriteMode XData::Rise; -const WriteMode XData::Fall; - -void TEST_DPI_LR(void *v) -{ - Debug("test DPI Logic Read"); -} -void TEST_DPI_LW(unsigned char v) -{ - Debug("test DPI Logic Write"); -} -void TEST_DPI_VR(void *v) -{ - Debug("test DPI Vec Read"); -} -void TEST_DPI_VW(void *v) -{ - Debug("test DPI Vec Write"); -} - -// class PinBind -PinBind::PinBind(xsvLogicVecVal *p, int index) -{ - this->pVec = p; - this->index = index / 32; - this->mask = 1 << index % 32; -} -PinBind::PinBind(xsvLogic *p) -{ - this->pLgc = p; - this->index = -1; -} - -PinBind &PinBind::Set(int v) -{ - return this->operator=(v); -}; - -int PinBind::AsInt32() -{ - return (int)*this; -} - -PinBind &PinBind::operator=(const u_int8_t &v) -{ - auto val = (u_int8_t)v; - Assert(val >= 0 && val <= 3, - "Set value[%d(to uint8)] must in rang [0,1,2,3] 01ZX", val); - if (this->pLgc) { - *this->pLgc = val; - } else if (this->pVec) { - switch (val) { - case 0: - bit32_zro(this->pVec[this->index].aval, this->mask); - bit32_zro(this->pVec[this->index].bval, this->mask); - break; - case 1: - bit32_one(this->pVec[this->index].aval, this->mask); - bit32_zro(this->pVec[this->index].bval, this->mask); - break; - case 2: - bit32_zro(this->pVec[this->index].aval, this->mask); - bit32_one(this->pVec[this->index].bval, this->mask); - break; - case 3: - bit32_one(this->pVec[this->index].aval, this->mask); - bit32_one(this->pVec[this->index].bval, this->mask); - break; - default: - Assert(false, "Set value[%d] must in rang [0,1,2,3] 01ZX", val); - break; - } - } else { - Assert(false, "Pin is not bind to any svLogic data"); - } - // write to dpi - if (this->write_fc) this->write_fc(); - return *this; -} - -std::string PinBind::AsString(){ - int v = this->AsInt32(); - switch (v) - { - case 0: - return "0"; - case 1: - return "1"; - case 2: - return "z"; - case 3: - return "x"; - } - Assert(false, "Error! PinBind::AsString() return invalid value[%d], need in [0,1,2,3]", v); -} - -PinBind &PinBind::operator=(const std::string &v) -{ - if (sLower(v) == "z") { return this->operator=(2); } - if (sLower(v) == "x") { return this->operator=(3); } - Assert(false, "Only support set Z or X"); - return *this; -} - -PinBind &PinBind::operator=(std::string &v) -{ - return this->operator=((const std::string &)v); -} - -PinBind::operator u_int8_t() -{ - if (this->pLgc) { - return static_cast(*this->pLgc); - } else if (this->pVec) { - uint8_t aval = bit32_val(this->pVec[this->index].aval, this->mask); - uint8_t bval = bit32_val(this->pVec[this->index].bval, this->mask); - return bval * 2 + aval; - } else { - Assert(false, "PinType error"); - } -} - -// class XData:: -void XData::update_read() -{ - this->_dpi_read(); - this->_sv_to_local(); - this->_update_shadow(); -} -void XData::update_write() -{ - this->_local_to_sv(); - this->_dpi_write(); - this->_update_shadow(); -} -bool XData::DataValid() -{ - return this->udata_is_valid; -} -void XData::_sv_to_local() -{ - this->_trunc_sv(); - if (this->mWidth == 0) { - Assert(this->mLogicData >= 0 && this->mLogicData < 4, - "Logic data[%d] need in range [0,1,2,3]", this->mLogicData); - this->udata = this->mLogicData; - this->xdata = this->udata > 1 ? 1 : 0; - this->udata_is_valid = this->udata > 1 ? false : true; - } else { - // reset buff - this->ubuff[0] = 0; - this->ubuff[1] = 0; - this->xbuff[0] = 0; - this->xbuff[1] = 0; - auto range = std::min( - 2, (int)this->vecSize); // max xdata.length is 64 (2 vecData) - for (int i = 0; i < range; i++) { - this->ubuff[i] = this->pVecData[i].aval; - this->xbuff[i] = this->pVecData[i].bval; - } - this->udata = *(uint64_t *)this->ubuff; - this->xdata = *(uint64_t *)this->xbuff; - this->udata_is_valid = true; - for (int i = 0; i < this->vecSize; i++) { - if (this->pVecData[i].bval != 0) { - this->udata_is_valid = false; - break; - } - } - } -} -void XData::_zero_sv() -{ - for (int i = 0; i < this->vecSize; i++) { - this->pVecData[i].aval = 0; - this->pVecData[i].bval = 0; - } -} -void XData::_trunc_sv() -{ - if (this->mWidth == 0) return; - this->pVecData[this->vecSize - 1].aval &= this->zero_mask; - this->pVecData[this->vecSize - 1].bval &= this->zero_mask; -} -void XData::_local_to_sv() -{ - if (this->mWidth == 0) { - Assert(this->udata >= 0 && this->udata < 4, - "Logic data to write need in range [0,1,2,3]"); - this->mLogicData = this->udata; - } else { - // reset buff - *(uint64_t *)this->ubuff = this->udata; - auto range = std::min( - 2, (int)this->vecSize); // max xdata.length is 64 (2 vecData) - for (int i = 0; i < this->vecSize; i++) { - // assign lower 64 bits - if (i < 2) { - this->pVecData[i].aval = this->ubuff[i]; - // assign higher bits zero - } else { - this->pVecData[i].aval = 0; - } - this->pVecData[i].bval = 0; - } - } - this->_sv_to_local(); -} -void XData::_dpi_read() -{ - if (unlikely(this->mIOType == IOType::Input - && this->write_mode != WriteMode::Imme)) { - return; - } - this->_dpi_check(); - if (likely(this->bitRead)) this->bitRead(&this->mLogicData); - if (likely(this->vecRead)) this->vecRead(this->pVecData); -} -WriteMode XData::GetWriteMode() -{ - return this->write_mode; -} -bool XData::SetWriteMode(WriteMode mode) -{ - if (unlikely(!this->_check_writeable())) { - Assert(false, "XData(%s) is not writeable, cannot set write mode", - this->mName.c_str()); - } - this->write_mode = mode; - return true; -}; - -void XData::WriteDirect(){ - if (likely(this->bitWrite)) this->bitWrite(this->mLogicData); - if (likely(this->vecWrite)) this->vecWrite(this->pVecData); -} - -void XData::WriteOnRise() -{ - if (unlikely(this->mIOType == IOType::Output)) return; - if (unlikely(this->write_mode != WriteMode::Rise)) return; - if (this->_need_write()) { - if (likely(this->bitWrite)) this->bitWrite(this->mLogicData); - if (likely(this->vecWrite)) this->vecWrite(this->pVecData); - } -} - -void XData::WriteOnFall() -{ - if (unlikely(this->mIOType == IOType::Output)) return; - if (unlikely(this->write_mode != WriteMode::Fall)) return; - if (this->_need_write()) { - if (likely(this->bitWrite)) this->bitWrite(this->mLogicData); - if (likely(this->vecWrite)) this->vecWrite(this->pVecData); - } -} -void XData::_dpi_write() -{ - if (unlikely(this->mIOType == IOType::Output)) return; - if (unlikely(this->write_mode != WriteMode::Imme)) return; - this->_dpi_check(); - if (this->_need_write()) { - if (likely(this->bitWrite)) this->bitWrite(this->mLogicData); - if (likely(this->vecWrite)) this->vecWrite(this->pVecData); - } -} - -bool XData::_need_write() -{ - if (unlikely(!this->ignore_same_write)) return true; - if (unlikely(!this->last_is_write)) { - this->_update_last_write(); - return true; - }; - if (this->mWidth == 0) { - if (this->last_mLogicData != this->mLogicData) { - this->_update_last_write(); - return true; - } - return false; - } else { - if (memcmp(this->last_pVecData, this->pVecData, - this->vecSize * sizeof(xsvLogicVecVal)) != 0) { - this->_update_last_write(); - return true; - } - } - return false; -} - -void XData::_update_last_write() -{ - this->last_is_write = true; - this->last_mLogicData = this->mLogicData; - if (this->mWidth > 0) { - if (this->last_pVecData == nullptr) { - this->last_pVecData = - (xsvLogicVecVal *)calloc(this->vecSize, sizeof(xsvLogicVecVal)); - } - memcpy(this->last_pVecData, this->pVecData, - this->vecSize * sizeof(xsvLogicVecVal)); - } -} - -void XData::_update_shadow() -{ - if (unlikely(this->igore_callback)) return; - if (unlikely(!this->has_on_change_cbs)) return; - bool need_call = false; - bool validate = true; - if (this->mWidth == 0) { - need_call = (this->__mLogicData != this->mLogicData); - validate = this->mLogicData < 2; - } else { - for (int i = 0; i < this->vecSize; i++) { - if (this->__pVecData[i].aval != this->pVecData[i].aval) { - need_call = true; - } - if (this->__pVecData[i].bval != this->pVecData[i].bval) { - need_call = true; - } - if (unlikely(this->pVecData[i].bval)) { validate = false; } - this->__pVecData[i] = this->pVecData[i]; - } - } - this->__mLogicData = this->mLogicData; - this->igore_callback = true; - if (need_call) { - for (auto fc : this->call_back_on_change) { - fc.fc(validate, this, this->udata, fc.args); - } - } - this->igore_callback = false; -} -void XData::_dpi_check(){ - // TBD -}; - -bool XData::_check_writeable() const -{ - return !this->readonly_backend && this->mIOType != IOType::Output; -} - -XData::XData() : XData(0, IOType::InOut){}; -XData::XData(uint32_t width, IOType itype, std::string name) : - mWidth(width), mIOType(itype), pinbind_bit(&this->mLogicData), mName(name), value(*this) -{ - this->ReInit(width, itype, name); -} -void XData::ReInit(uint32_t width, IOType itype, std::string name) -{ - if (this->pVecData) { - for (int i = 0; i < this->mWidth; i++) { delete this->pinbind_vec[i]; } - free(this->pinbind_vec); - free(this->pVecData); - free(this->__pVecData); - this->pinbind_vec = nullptr; - this->pVecData = nullptr; - this->__pVecData = nullptr; - } - this->mWidth = width; - this->mIOType = itype; - this->mName = name; - this->backend_kind = XDataBackendKind::Unknown; - this->readonly_backend = false; - this->last_is_write = false; - this->last_mLogicData = 0; - if (this->last_pVecData) { - free(this->last_pVecData); - this->last_pVecData = nullptr; - } - - auto write_fc = [this]() { - this->_dpi_write(); - this->_sv_to_local(); - this->_update_shadow(); - }; - if (width >= 1) { - // svDPI Vec is 32 bits - this->vecSize = width / 32 + ((width % 32 != 0) ? 1 : 0); - this->pVecData = - (xsvLogicVecVal *)calloc(this->vecSize, sizeof(xsvLogicVecVal)); - this->__pVecData =// [shadow data][3 * tmp data] - (xsvLogicVecVal *)calloc(this->vecSize*4, sizeof(xsvLogicVecVal)); - this->pinbind_vec = (PinBind **)calloc(this->mWidth, sizeof(PinBind *)); - for (int i = 0; i < this->mWidth; i++) { - this->pinbind_vec[i] = new PinBind(this->pVecData, i); - this->pinbind_vec[i]->write_fc = write_fc; - } - this->zero_mask = - bit32_msk(width % 32); // eg: [bin] ,|1111,1000,0000,0000| ... - if (this->zero_mask == 0) { - this->zero_mask = -1; // set: 0xFFFFFFFF - } - } else { - this->pinbind_bit.write_fc = write_fc; - this->vecSize = 0; - } - this->mLogicData = 0; - this->udata_is_valid = true; -} -XData::XData(XData &t) : - mWidth(t.mWidth), - mIOType(t.mIOType), - pinbind_bit(&this->mLogicData), - mLogicData(t.mLogicData), - value(*this) -{ - auto write_fc = [this]() { - this->_dpi_write(); - this->_sv_to_local(); - this->_update_shadow(); - }; - t.update_read(); - // members - this->xdata = t.xdata; - this->udata = t.udata; - this->vecSize = t.vecSize; - this->zero_mask = t.zero_mask; - this->write_mode = t.write_mode; - this->sub_offset = t.sub_offset; - this->sub_pVecRef = t.sub_pVecRef; - this->sub_parent = t.sub_parent; - // pointers - if (this->vecSize > 0) { - this->pVecData = - (xsvLogicVecVal *)calloc(this->vecSize, sizeof(xsvLogicVecVal)); - this->__pVecData = // [shadow data][3 * tmp data] - (xsvLogicVecVal *)calloc(this->vecSize*4, sizeof(xsvLogicVecVal)); - this->pinbind_vec = (PinBind **)calloc(this->mWidth, sizeof(PinBind *)); - for (int i = 0; i < this->mWidth; i++) { - this->pinbind_vec[i] = new PinBind(this->pVecData, i); - this->pinbind_vec[i]->write_fc = write_fc; - } - } - for (int i = 0; i < this->vecSize; i++) { - this->pVecData[i] = t.pVecData[i]; - } - this->pinbind_bit.write_fc = write_fc; - this->_sv_to_local(); - if(this->sub_pVecRef != nullptr){ - Warn("You are copying a ref xdata. bind _sub_data_fake_dpirw ..."); - this->vecRead = [this](void*data){return this->_sub_data_fake_dpir(data);}; - if(this->mIOType != IOType::Output)this->vecWrite = [this](void*data){return this->_sub_data_fake_dpiw(data);}; - this->update_read(); - } -}; -XData::~XData() -{ - if (this->pVecData) { - for (int i = 0; i < this->mWidth; i++) { delete this->pinbind_vec[i]; } - free(this->pinbind_vec); - free(this->pVecData); - free(this->__pVecData); - this->pVecData = nullptr; - } - if (this->last_pVecData) { - free(this->last_pVecData); - this->last_pVecData = nullptr; - } -} - -XData* XData::SubDataRefRaw(uint32_t start, uint32_t width, std::string name){ - Assert(this->mWidth > 0, "Only svVec support SubDataRef, need mWidth > 0"); - Assert(start + width <= this->mWidth, "SubDataRef out of range (start + witdth=%d > mWidth=%d)", start + width, this->mWidth); - auto sub = new XData(width, this->mIOType, name); - if(sub->mIOType != IOType::Output)sub->SetWriteMode(WriteMode::Imme); - sub->sub_offset = start; - sub->sub_pVecRef = this->pVecData; - sub->sub_parent = this; - sub->vecRead = [sub](void*data){return sub->_sub_data_fake_dpir(data);}; - if(sub->mIOType != IOType::Output)sub->vecWrite = [sub](void*data){return sub->_sub_data_fake_dpiw(data);}; - sub->update_read(); - return sub; -} - -std::shared_ptr XData::SubDataRef(uint32_t start, uint32_t width, std::string name){ - std::shared_ptr sub(this->SubDataRefRaw(start, width, name)); - return sub; -} - -void XData::_sub_data_fake_dpirw(void *data, bool is_read){ - DebugC(false, "_sub_data_fake_dpirw: %s", is_read ? "Read": "Write"); - if (is_read && this->sub_parent != nullptr) { - this->sub_parent->update_read(); - } - // read data from ref - uint32_t start_p = this->sub_offset / 32; - uint32_t start_f = this->sub_offset % 32; - uint32_t end_p = (this->sub_offset + this->mWidth - 1) / 32; - uint32_t end_f = (this->sub_offset + this->mWidth - 1) % 32 + 1; - if (this->vecSize == 0 && this->mWidth == 0) { - if (is_read) { - auto aval = (this->sub_pVecRef[start_p].aval >> start_f) & 1; - auto bval = (this->sub_pVecRef[start_p].bval >> start_f) & 1; - this->mLogicData = bval * 2 + aval; - } else { - uint32_t mask = 1 << start_f; - uint32_t maskb = ~mask; - this->sub_pVecRef[start_p].aval = (maskb & this->sub_pVecRef[start_p].aval) | ((this->mLogicData & 1) << start_f); - this->sub_pVecRef[start_p].bval = (maskb & this->sub_pVecRef[start_p].bval) | (((this->mLogicData >> 1) & 1) << start_f); - if (this->sub_parent != nullptr) { - this->sub_parent->_sv_to_local(); - this->sub_parent->_dpi_write(); - this->sub_parent->_update_shadow(); - } - } - return; - } - xsvLogicVecVal * p = this->sub_pVecRef; - // copy/write data - // from/dist: |_|_|_|_|_|_| - // / / - // to/from: |_|_|_| - // [shadow data][mask][aval][bval] - int secs = end_p - start_p + 1; - int * temp_mask = (int *)this->__pVecData + this->vecSize * 2; - int * temp_aval = temp_mask + secs; - int * temp_bval = temp_aval + secs; - Assert(secs > 0, "Error! SubDataRef: secs <= 0"); - Assert(secs <= (this->vecSize + 1), "Error! SubDataRef: secs(%d) > vecSize(%d) + 1", secs, this->vecSize); - if(is_read) { - // Logic for reading data - for (int i = 0; i < secs; i++) { - temp_aval[i] = p[start_p + i].aval; - temp_bval[i] = p[start_p + i].bval; - } - if (start_f > 0) { - big_shift(temp_aval, secs, start_f); - big_shift(temp_bval, secs, start_f); - } - for (int i = 0; i < this->vecSize; i++){ - this->pVecData[i].aval = temp_aval[i]; - this->pVecData[i].bval = temp_bval[i]; - } - } else { - // Logic for writing data - for (int i = 0; i < this->vecSize; i++) { - temp_aval[i] = this->pVecData[i].aval; - temp_bval[i] = this->pVecData[i].bval; - } - if (start_f > 0) { - big_shift(temp_aval, secs, -start_f); - big_shift(temp_bval, secs, -start_f); - } - // Calculate bit mask - // Use a more precise method to calculate the mask - memset(temp_mask, 0, secs * sizeof(int)); - // Create mask for the first 32-bit block - uint32_t first_mask_bits = 32 - start_f; - if (this->mWidth < first_mask_bits) { - first_mask_bits = this->mWidth; - } - uint32_t first_mask = first_mask_bits == 32 - ? 0xFFFFFFFFu - : (uint32_t(1) << first_mask_bits) - 1; - temp_mask[0] = first_mask << start_f; - // Create mask for the middle blocks (if any) - for (int i = 1; i < secs - 1; i++) { - temp_mask[i] = 0xFFFFFFFF; // Set all bits to 1 - } - // Create mask for the last block (if different from the first block) - if (secs > 1) { - uint32_t last_bits = (this->sub_offset + this->mWidth) % 32; - if (last_bits == 0) { - // If exactly on the boundary, set all bits - temp_mask[secs - 1] = 0xFFFFFFFF; - } else { - // Otherwise, set only the required bits - temp_mask[secs - 1] = bit32_msk(last_bits); - } - } - // Apply the mask and update the data - for (int i = 0; i < secs; i++) { - p[start_p + i].aval = (p[start_p + i].aval & ~temp_mask[i]) | (temp_aval[i] & temp_mask[i]); - p[start_p + i].bval = (p[start_p + i].bval & ~temp_mask[i]) | (temp_bval[i] & temp_mask[i]); - } - if (this->sub_parent != nullptr) { - this->sub_parent->_sv_to_local(); - this->sub_parent->_dpi_write(); - this->sub_parent->_update_shadow(); - } - } - // Update local data - this->_sv_to_local(); - this->_update_shadow(); -} - -void XData::_sub_data_fake_dpir(void *data){ - return this->_sub_data_fake_dpirw(data, true); -} - -void XData::_sub_data_fake_dpiw(void *data){ - return this->_sub_data_fake_dpirw(data, false); -} - -void XData::BindDPIPtr(uint64_t read_ptr, uint64_t write_ptr) -{ - if (this->mWidth > 0) { - this->vecRead = (void (*)(void *))read_ptr; - this->vecWrite = (void (*)(const void *))write_ptr; - } else { - this->bitRead = (void (*)(void *))read_ptr; - this->bitWrite = (void (*)(const unsigned char))write_ptr; - } - this->backend_kind = XDataBackendKind::DPI; - this->readonly_backend = false; - this->update_read(); -} - -void XData::BindDPIRW(xfunction read, - xfunction write) -{ - Assert(this->mWidth > 0, "XData(name=%s).mWidth(%d) need > 0", - this->mName.c_str(), this->mWidth); - this->vecRead = read; - this->vecWrite = write; - this->backend_kind = XDataBackendKind::DPI; - this->readonly_backend = false; - this->update_read(); -} -void XData::BindDPIRW(xfunction read, - xfunction write) -{ - Assert(this->mWidth == 0, "XData(name=%s).mWidth(%d) need == 0", - this->mName.c_str(), this->mWidth); - this->bitRead = read; - this->bitWrite = write; - this->backend_kind = XDataBackendKind::DPI; - this->readonly_backend = false; - this->update_read(); -} -void XData::BindDPIRW(void (*read)(void *), void (*write)(const void *)) { - Assert(this->mWidth > 0, "XData(name=%s).mWidth(%d) need > 0", - this->mName.c_str(), this->mWidth); - this->vecRead = read; - this->vecWrite = write; - this->backend_kind = XDataBackendKind::DPI; - this->readonly_backend = false; - this->update_read(); -} -void XData::BindDPIRW(void (*read)(void *), void (*write)(const unsigned char)) { - Assert(this->mWidth == 0, "XData(name=%s).mWidth(%d) need == 0", - this->mName.c_str(), this->mWidth); - this->bitRead = read; - this->bitWrite = write; - this->backend_kind = XDataBackendKind::DPI; - this->readonly_backend = false; - this->update_read(); -} -// TODO: Define how native two-state reads handle an existing X/Z mask and how -// X/Z writes map to native storage. Preserve bval until that policy is settled; -// see docs/xdata_xclock_optimizations.md (BindNativeData X/Z semantics). -void XData::BindNativeData(uint64_t pdata){ - if (this->mWidth == 0){ - auto* native = reinterpret_cast(pdata); - this->bitRead = [native](xsvLogic* dst){ - *dst = *native; - }; - this->bitWrite = [native](xsvLogic value){ - *native = value; - }; - } else if (this->mWidth <= 8) { - VecMemoryIO::Bind(pdata, this->vecSize, this->vecRead, this->vecWrite); - } else if (this->mWidth <= 16) { - VecMemoryIO::Bind(pdata, this->vecSize, this->vecRead, this->vecWrite); - } else { -#define XCOMM_NATIVE_WORD_COUNTS(M) \ - M(1) M(2) M(3) M(4) M(5) M(6) M(7) M(8) \ - M(9) M(10) M(11) M(12) M(13) M(14) M(15) M(16) -#define XCOMM_BIND_NATIVE_CASE(N) \ - case N: \ - VecMemoryIO::Bind(pdata, this->vecSize, this->vecRead, this->vecWrite); \ - break; - - switch (this->vecSize) { - XCOMM_NATIVE_WORD_COUNTS(XCOMM_BIND_NATIVE_CASE) - default: - VecMemoryIO::Bind(pdata, this->vecSize, this->vecRead, this->vecWrite); - break; - } -#undef XCOMM_BIND_NATIVE_CASE -#undef XCOMM_NATIVE_WORD_COUNTS - } - this->backend_kind = XDataBackendKind::MemDirect; - this->readonly_backend = false; - this->update_read(); -} -void XData::BindMixFromUvs(uint64_t pdata) -{ - if (this->mWidth == 0) { // scalar and 1bit vector - this->bitRead = [pdata](void *d) { - *(xsvLogic *)d = *(uint8_t *)pdata; - }; - } else { - if (this->mWidth <= 8) { - this->vecRead = [this, pdata](void *d) { - ((xsvLogicVecVal *)d)->aval = *(uint8_t *)pdata; - // ((xsvLogicVecVal *)d)->bval = 0; - }; - } else if (this->mWidth <= 16) { - this->vecRead = [this, pdata](void *d) { - ((xsvLogicVecVal *)d)->aval = *(uint16_t *)pdata; - // ((xsvLogicVecVal *)d)->bval = 0; - }; - } else if (this->mWidth <= 32) { - this->vecRead = [this, pdata](void *d) { - ((xsvLogicVecVal *)d)->aval = *(uint32_t *)pdata; - // ((xsvLogicVecVal *)d)->bval = 0; - }; - } else if (this->mWidth <= 64) { - this->vecRead = [this, pdata](void *d) { - ((xsvLogicVecVal *)d)->aval = ((uint32_t *)pdata)[0]; - // ((xsvLogicVecVal *)d)->bval = 0; - ((xsvLogicVecVal *)d)[1].aval = ((uint32_t *)pdata)[1]; - // ((xsvLogicVecVal *)d)[1].bval = 0; - }; - } else { // wider - this->vecRead = [this, pdata](void *d) { - for (int i = 0; i < this->vecSize; i++) { - ((xsvLogicVecVal *)d)[i].aval = ((uint32_t *)pdata)[i]; - // ((xsvLogicVecVal *)d)[i].bval = 0; - } - }; - } - } -} -void XData::BindExpr(std::shared_ptr engine, int root_id){ - Assert(engine != nullptr, "BindExpr engine is null"); - if (this->mWidth == 0) { - this->bitRead = [engine, root_id](void *d){ - auto value = engine->Eval(root_id); - *(xsvLogic *)d = (xsvLogic)((value & 0x1) ? 1 : 0); - }; - this->bitWrite = nullptr; - this->vecRead = nullptr; - this->vecWrite = nullptr; - } else { - this->vecRead = [this, engine, root_id](void *d){ - auto value = engine->Eval(root_id); - auto *vec = (xsvLogicVecVal *)d; - for (uint32_t i = 0; i < this->vecSize; i++) { - vec[i].aval = 0; - vec[i].bval = 0; - } - for (uint32_t i = 0; i < std::min(2, this->vecSize); i++) { - vec[i].aval = (uint32_t)((value >> (i * 32)) & 0xffffffffULL); - } - }; - this->vecWrite = nullptr; - this->bitRead = nullptr; - this->bitWrite = nullptr; - } - this->backend_kind = XDataBackendKind::Expr; - this->readonly_backend = true; - this->mIOType = IOType::Output; - this->update_read(); -} -void XData::BindConst(uint64_t value){ - if (this->mWidth == 0) { - this->bitRead = [value](void *d){ - *(xsvLogic *)d = (xsvLogic)((value & 0x1) ? 1 : 0); - }; - this->bitWrite = nullptr; - this->vecRead = nullptr; - this->vecWrite = nullptr; - } else { - this->vecRead = [this, value](void *d){ - auto *vec = (xsvLogicVecVal *)d; - for (uint32_t i = 0; i < this->vecSize; i++) { - vec[i].aval = 0; - vec[i].bval = 0; - } - for (uint32_t i = 0; i < std::min(2, this->vecSize); i++) { - vec[i].aval = (uint32_t)((value >> (i * 32)) & 0xffffffffULL); - } - }; - this->vecWrite = nullptr; - this->bitRead = nullptr; - this->bitWrite = nullptr; - } - this->backend_kind = XDataBackendKind::Const; - this->readonly_backend = true; - this->mIOType = IOType::Output; - this->update_read(); -} -void XData::SetBits(u_int8_t *buffer, int count, u_int8_t *mask, int start) -{ - if (unlikely(!this->_check_writeable())) { - Assert(false, "XData(%s) is not writeable, cannot set bits", - this->mName.c_str()); - } - Assert(this->mWidth > 0, "Only svVec support SetBits"); - int index = start; - for (int i = 0; i < count; i++) { - auto vec_idx = index / 4; - auto vec_off = index % 4; - if (vec_idx >= this->vecSize) break; - uint8_t byte_val = buffer[i]; - if (mask) { // FIXME: need refine - u_int8_t old = - (u_int8_t)((this->pVecData[vec_idx].aval >> vec_off * 4) - & 0x000000FF); - byte_val = (old & (~mask[i])) | (buffer[i] & mask[i]); - } - bit32_chr(this->pVecData[vec_idx].aval, vec_off, byte_val); - bit32_chr(this->pVecData[vec_idx].bval, vec_off, 0); - index += 1; - } - this->_dpi_write(); - this->_sv_to_local(); - this->_update_shadow(); -} -void XData::SetBits(u_int32_t *buffer, u_int32_t count, u_int32_t *mask, - u_int32_t start) -{ - if (unlikely(!this->_check_writeable())) { - Assert(false, "XData(%s) is not writeable, cannot set bits", - this->mName.c_str()); - } - Assert(this->mWidth > 0, "only svVec support SetBits"); - auto range = start < this->vecSize ? std::min(count, this->vecSize - start) : 0; - for (int i = 0; i < range; i++) { - if (mask == nullptr) { - this->pVecData[i + start].aval = buffer[i]; - } else { - u_int32_t aval = this->pVecData[i + start].aval & (~mask[i]); - this->pVecData[i + start].aval = aval | (buffer[i] & mask[i]); - } - this->pVecData[i + start].bval = 0; - } - this->_dpi_write(); - this->_sv_to_local(); - this->_update_shadow(); -} -bool XData::GetBits(u_int32_t *buffer, u_int32_t count) -{ - Assert(this->mWidth > 0, "only svVec support GetBits"); - this->update_read(); - auto range = std::min(count, this->vecSize); - bool ret = true; - for (int i = 0; i < range; i++) { - buffer[i] = this->pVecData[i].aval; - if (this->pVecData[i].bval != 0) { ret = false; } - } - return ret; -} - -bool XData::GetBits(u_int8_t *buffer, u_int32_t count) -{ - Assert(this->mWidth > 0, "only svVec support GetBits"); - this->update_read(); - auto range = std::min(count, 4 * this->vecSize); - bool ret = true; - for (int i = 0; i < range; i++) { - buffer[i] = ((unsigned char *)&this->pVecData[i / 4].aval)[i % 4]; - if (this->pVecData[i / 4].bval != 0) { ret = false; } - } - return ret; -} - -void XData::SetVU8(std::vector &buffer) -{ - if (unlikely(!this->_check_writeable())) { - Assert(false, "XData(%s) is not writeable, cannot set bytes", - this->mName.c_str()); - } - Assert(this->mWidth > 0, "only svVec support SetVU8"); - int index = 0; - for (int i = 0; i < this->vecSize; i++) { - this->pVecData[i].aval = 0; - this->pVecData[i].bval = 0; - } - for (auto &d : buffer) { - auto vec_idx = index / 4; - auto vec_off = index % 4; - if (vec_idx >= this->vecSize) break; - ((unsigned char *)&this->pVecData[vec_idx].aval)[vec_off] = d; - ((unsigned char *)&this->pVecData[vec_idx].bval)[vec_off] = 0; - index += 1; - } - this->_dpi_write(); - this->_sv_to_local(); - this->_update_shadow(); -} - -std::vector XData::GetVU8() -{ - std::vector ret; - Assert(this->mWidth > 0, "only svVec support GetVU8"); - this->update_read(); - for (int i = 0; i < this->vecSize; i++) { - ret.push_back(((unsigned char *)&this->pVecData[i].aval)[0]); - ret.push_back(((unsigned char *)&this->pVecData[i].aval)[1]); - ret.push_back(((unsigned char *)&this->pVecData[i].aval)[2]); - ret.push_back(((unsigned char *)&this->pVecData[i].aval)[3]); - } - return ret; -} - -std::vector XData::GetBvalBytes() -{ - std::vector ret; - Assert(this->mWidth > 0, "only svVec support GetBvalBytes"); - this->update_read(); - for (int i = 0; i < this->vecSize; i++) { - ret.push_back(((unsigned char *)&this->pVecData[i].bval)[0]); - ret.push_back(((unsigned char *)&this->pVecData[i].bval)[1]); - ret.push_back(((unsigned char *)&this->pVecData[i].bval)[2]); - ret.push_back(((unsigned char *)&this->pVecData[i].bval)[3]); - } - return ret; -} - -void XData::OnChange(xfunction func, - void *args, std::string desc) -{ - bool init_shadow = !this->has_on_change_cbs; - if (init_shadow) { - this->update_read(); - } - XDataCallBack cb; - cb.args = args; - cb.fc = func; - cb.desc = desc; - this->call_back_on_change.push_back(cb); - this->has_on_change_cbs = true; - if (init_shadow) { - this->__mLogicData = this->mLogicData; - if (this->mWidth > 0 && this->__pVecData) { - memcpy(this->__pVecData, this->pVecData, - this->vecSize * sizeof(xsvLogicVecVal)); - } - } -} - -void XData::ClearOnChangeCbs(){ - this->call_back_on_change.clear(); - this->has_on_change_cbs = false; -} - -uint32_t XData::W() -{ - return this->mWidth; -} -uint64_t XData::U() -{ - this->update_read(); - return static_cast(this->udata); -} -uint64_t XData::XMask() -{ - this->update_read(); - return static_cast(this->xdata); -} -int64_t XData::S() -{ - this->update_read(); - // A full native word already has its sign bit in the correct position. - if (this->mWidth == 0 || this->mWidth >= 64) { - return static_cast(this->udata); - } - - auto value = this->udata; - auto sign_bit = ((uint64_t)1) << (this->mWidth - 1); - - if ((value & sign_bit) != 0){ - auto mask = (((uint64_t)1) << this->mWidth) - 1; - value |= ~mask; - } - return value; -} -bool XData::B() -{ - this->update_read(); - return static_cast(this->udata); -} -std::string XData::String() -{ - this->update_read(); - if (this->mWidth == 0) { - switch (mLogicData) { - case 0: return "0"; break; - case 1: return "1"; break; - case 2: return "Z"; break; - case 3: return "X"; break; - default:; - } - Assert(false, "Logic Data value error(%d), need range (0 -> 3)", - this->mLogicData); - } - // svLogicVecVal - std::string ret; - u_int8_t abuffer[4]; // 32 bit buffer - u_int8_t bbuffer[4]; // 32 bit buffer - for (int i = this->vecSize - 1; i >= 0; i--) { - *(u_int32_t *)abuffer = this->pVecData[i].aval; - *(u_int32_t *)bbuffer = this->pVecData[i].bval; - for (int j = 3; j >= 0; j--) { - if (i * 32 + j * 8 > this->mWidth) continue; - if (bbuffer[j] == 0) { - ret += sFmt("%02x", abuffer[j]); - } else { - ret += "??"; - } - } - } - return ret; -} - -bool XData::operator==(u_int64_t data) -{ - this->update_read(); - if (this->mWidth > 64) { - for (int i = 2; i < this->vecSize; i++) { - if (this->pVecData[i].aval != 0 || this->pVecData[i].bval != 0) { - return false; - } - } - } - return this->udata == data && this->xdata == 0; -} - -bool XData::operator==(const std::string &str) -{ - return sLower(this->String()) == sLower(str); -} - -bool XData::operator==(std::string &str) -{ - return this->operator==((const std::string)str); -} - -bool XData::operator==(char *str) -{ - return this->operator==((const char *)str); -} - -bool XData::operator==(const char *str) -{ - return this->operator==(std::string(str)); -} - -XData &XData::operator=(u_int64_t data) -{ - if (unlikely(!this->_check_writeable())) { - Assert(false, "XData(%s) is not writeable, cannot assign", - this->mName.c_str()); - } - - this->udata = data; - this->xdata = 0; - this->update_write(); - return *this; -} - -XData &XData::operator=(XData &data) -{ - data.update_read(); - if (unlikely(!this->_check_writeable())) { - Assert(false, "XData(%s) is not writeable, cannot assign", - this->mName.c_str()); - } - Assert(this->mWidth == data.mWidth, - "Need left.mWidth(%d) == right.mWidth(%d)", this->mWidth, - data.mWidth); - // raw - for (int i = 0; i < this->vecSize; i++) { - this->pVecData[i] = data.pVecData[i]; - } - this->udata_is_valid = data.udata_is_valid; - this->mLogicData = data.mLogicData; - // update - this->_sv_to_local(); - this->_dpi_write(); - this->_update_shadow(); - return *this; -} -bool XData::Comp(XData &data, int opcode, int eq){ - // opcode 0: equal, 1: less, 2: greater - if(opcode == 0)return *this == data; - if(*this == data){ - if(eq){ - return true; - } - return false; - } - if(this->mWidth < 64 && data.mWidth < 64){ - if(opcode == 1)return this->udata < data.udata; - return this->udata > data.udata; - } - Assert(this->mWidth == data.mWidth, "Need left.mWidth(%d) == right.mWidth(%d)", this->mWidth, data.mWidth); - // Unsigned multi-word ordering is determined by the most significant - // differing word, not the first differing low word. - for(int i = this->vecSize - 1; i >= 0; i--){ - if(opcode == 1){ - if(this->pVecData[i].aval < data.pVecData[i].aval)return true; - if(this->pVecData[i].aval > data.pVecData[i].aval)return false; - }else{ - if(this->pVecData[i].aval > data.pVecData[i].aval)return true; - if(this->pVecData[i].aval < data.pVecData[i].aval)return false; - } - } - return false; -} -bool XData::operator==(XData &data) -{ - // update value - this->update_read(); - data.update_read(); - if (this->mWidth < 64 && data.mWidth < 64) { - return (this->udata == data.udata) && (this->xdata == data.xdata); - } - // logic compare - auto lgc_cmp_vec = [](XData *lgc, XData *vec) -> bool { - for (int i = 1; i < vec->vecSize; i++) { - if (vec->pVecData[i].aval != 0) { return false; } - } - if (vec->pVecData[0].aval > 1 | vec->pVecData[0].bval > 1) { - return false; - } - return (vec->pVecData->aval + 2 * vec->pVecData->bval) - == lgc->mLogicData; - }; - if (this->mWidth == 0) { - if (data.mWidth == 0) { return this->mLogicData == data.mLogicData; } - return lgc_cmp_vec(this, &data); - } else { - if (data.mWidth == 0) return lgc_cmp_vec(&data, this); - // vec compare - XData *ldata = (this->vecSize > data.vecSize) ? this : &data; - uint32_t idx_vec = std::min(this->vecSize, data.vecSize); - for (int i = idx_vec; i < ldata->vecSize; i++) { - if (ldata->pVecData[i].aval != 0 || ldata->pVecData[i].bval != 0) { - return false; - } - } - for (int i = 0; i < idx_vec; i++) { - if ((this->pVecData[i].aval != data.pVecData[i].aval) - || (this->pVecData[i].bval != data.pVecData[i].bval)) { - return false; - } - } - return true; - } -} - -XData &XData::operator=(const char *str) -{ - std::string raw_str = str; - return this->operator=(raw_str); -} -XData &XData::operator=(std::string &data) -{ - if (unlikely(!this->_check_writeable())) { - Assert(false, "XData(%s) is not writeable, cannot assign", - this->mName.c_str()); - } - if (this->mWidth == 0) { - if (sLower(data) == "z") return this->operator=(2); - if (sLower(data) == "x") return this->operator=(3); - Assert(false, "for string values, Logica Data only support set Z or X"); - } else { - if (sLower(data) == "z") { - for (int i = 0; i < this->vecSize; i++) { - this->pVecData[i].aval = 0; - this->pVecData[i].bval = 1; - } - return *this; - } - if (sLower(data) == "x") { - for (int i = 0; i < this->vecSize; i++) { - this->pVecData[i].aval = 1; - this->pVecData[i].bval = 1; - } - return *this; - } - } - - auto prefix = data.substr(0, 2); - Assert(data.length() > 2 - && contians(std::vector{"0b", "0x", "::"}, prefix), - "Input string needs start with: 0b (binary), 0x (hex) or :: (str)"); - this->_zero_sv(); - - auto txt = data.substr(2); - int tsz = txt.length(); - int index = 0; - if (prefix == "0b") { - txt = sLower(txt); - for (int i = 0; i < tsz; i++) { - if (txt[i] == '_') continue; - Assert(txt[i] == '0' || txt[i] == '1' || txt[i] == 'z' - || txt[i] == 'x', - "find no 0/1 (%c) value: %s(%d) at %d", txt[i], txt.c_str(), - tsz, i); - auto vec_idx = index / 32; - auto vec_off = index % 32; - if (vec_idx >= this->vecSize) break; - if (txt[i] == '1') { - bit32_set(this->pVecData[vec_idx].aval, vec_off); - } else if (txt[i] == 'z') { - bit32_set(this->pVecData[vec_idx].bval, vec_off); - } else if (txt[i] == 'x') { - bit32_set(this->pVecData[vec_idx].aval, vec_off); - bit32_set(this->pVecData[vec_idx].bval, vec_off); - } - index += 1; - } - } else if (prefix == "0x") { - txt = sLower(txt); - for (int i = tsz - 1; i >= 0; i--) { - if (txt[i] == '_') continue; - u_int32_t hex_val = (u_int32_t)txt[i] - 48; - if (hex_val > 9) { hex_val = (u_int32_t)txt[i] - 97 + 10; } - Assert(hex_val >= 0 && hex_val <= 15 || hex_val == 0x23 - || hex_val == 0x21, - "find no hex(%c: 0x%x) value: %s(%d) at %d", txt[i], hex_val, - txt.c_str(), tsz, i); - auto vec_idx = index / 8; - auto vec_off = index % 8; - if (vec_idx >= this->vecSize) break; - if (txt[i] == 'z') { - bit32_hex(this->pVecData[vec_idx].aval, vec_off, 0x0); - bit32_hex(this->pVecData[vec_idx].bval, vec_off, 0xf); - } else if (txt[i] == 'x') { - bit32_hex(this->pVecData[vec_idx].aval, vec_off, 0xf); - bit32_hex(this->pVecData[vec_idx].bval, vec_off, 0xf); - } else { - bit32_hex(this->pVecData[vec_idx].aval, vec_off, hex_val); - } - index += 1; - } - } else if (prefix == "::") { - for (int i = 0; i < tsz; i++) { - if (txt[i] == '_') continue; - u_int32_t chr_val = (u_int32_t)txt[i]; - auto vec_idx = index / 4; - auto vec_off = index % 4; - if (vec_idx >= this->vecSize) break; - bit32_chr(this->pVecData[vec_idx].aval, vec_off, chr_val); - index += 1; - } - } - this->_dpi_write(); - this->_sv_to_local(); - this->_update_shadow(); - return *this; -} - -void XData::ReadFresh(WriteMode m) -{ - if (this->mIOType != IOType::Output) { - if (this->write_mode != m && m != WriteMode::Imme) return; - } - this->_dpi_check(); - if (this->bitRead) this->bitRead(&this->mLogicData); - if (this->vecRead) this->vecRead(this->pVecData); - this->_sv_to_local(); - this->_update_shadow(); -} - -XData &XData::FlipIOType() -{ - if (this->mIOType == IOType::Input) { - this->mIOType = IOType::Output; - } else if (this->mIOType == IOType::Output) { - this->mIOType = IOType::Input; - } - return *this; -} - -XData &XData::AsBiIO() -{ - this->mIOType = IOType::InOut; - return *this; -} - -XData &XData::AsInIO() -{ - this->mIOType = IOType::Input; - return *this; -} - -XData &XData::AsOutIO() -{ - this->mIOType = IOType::Output; - return *this; -} - -XData &XData::Invert() -{ - if (unlikely(!this->_check_writeable())) { - Assert(false, "XData(%s) is not writeable, cannot invert", - this->mName.c_str()); - } - if (this->mWidth == 0) { - this->mLogicData = this->mLogicData == 0 ? 1 : 0; - } else { - for (int i = 0; i < this->vecSize; i++) { - this->pVecData[i].aval = ~this->pVecData[i].aval; - this->pVecData[i].bval = 0; - } - } - this->_dpi_write(); - this->_sv_to_local(); - this->_update_shadow(); - return *this; -} -XData::operator u_int64_t() -{ - this->update_read(); - return this->udata; -} -XData::operator std::string() -{ - return this->mName; -} -PinBind &XData::operator[](u_int32_t index) -{ - this->update_read(); - Assert(index < this->mWidth, "Index[%d] need range 0 t %d", index, - this->mWidth); - if (this->mWidth > 0) { return *this->pinbind_vec[index]; } - return this->pinbind_bit; -} - -void XData::_TestBindDPIL() -{ - this->BindDPIRW(TEST_DPI_LR, TEST_DPI_LW); -} - -void XData::_TestBindDPIV() -{ - this->BindDPIRW(TEST_DPI_VR, TEST_DPI_VW); -} - -XData &XData::Set(XData &data) -{ - return this->operator=(data); -} - -XData &XData::Set(const char *data) -{ - return this->operator=(data); -} - -XData &XData::Set(std::string &data) -{ - return this->operator=(data); -} - -XData &XData::Set(int data) -{ - return this->operator=(data); -} - -XData &XData::Set(unsigned int data) -{ - return this->operator=(data); -} - -XData &XData::Set(int64_t data) -{ - return this->operator=(data); -} - -XData &XData::Set(uint64_t data) -{ - return this->operator=(data); -} - -namespace { -template -XData &imm_set_once(XData &self, T &&data) -{ - auto mode = self.GetWriteMode(); - self.AsImmWrite(); - self.Set(std::forward(data)); - self.SetWriteMode(mode); - return self; -} -} // namespace - -XData &XData::ImmSet(XData &data) -{ - return imm_set_once(*this, data); -} - -XData &XData::ImmSet(const char *data) -{ - return imm_set_once(*this, data); -} - -XData &XData::ImmSet(std::string &data) -{ - return imm_set_once(*this, data); -} - -XData &XData::ImmSet(int data) -{ - return imm_set_once(*this, data); -} - -XData &XData::ImmSet(unsigned int data) -{ - return imm_set_once(*this, data); -} - -XData &XData::ImmSet(int64_t data) -{ - return imm_set_once(*this, data); -} - -XData &XData::ImmSet(uint64_t data) -{ - return imm_set_once(*this, data); -} - -PinBind &XData::At(int index) -{ - return this->operator[](index); -} - -std::string XData::AsBinaryString() -{ - this->update_read(); - std::string ret; - if (this->mWidth == 0) { - switch (this->mLogicData) { - case 0: return "0"; break; - case 1: return "1"; break; - case 2: return "z"; break; - case 3: return "x"; break; - default:; - } - Assert(false, "Logic Data value error(%d), need range (0 -> 3)", - this->mLogicData); - } - for (int i = this->vecSize - 1; i >= 0; i--) { - for (int j = 31; j >= 0; j--) { - if(i*32 + j >= this->mWidth)continue; - auto b = (this->pVecData[i].bval & (1 << j)) >> j; - auto a = (this->pVecData[i].aval & (1 << j)) >> j; - auto v = b * 2 + a; - switch (v) - { - case 0: if(!ret.empty())ret += "0"; break; - case 1: ret += "1"; break; - case 2: ret += "z"; break; - case 3: ret += "x"; break; - default: Assert(false, "Logic Data value error(%d), need range (0 -> 3)", v); - } - } - } - if(ret.empty()){ - ret = "0"; - } - return ret; -} - -int64_t XData::AsInt64() -{ - return this->S(); -} - -int XData::AsInt32() -{ - return (int)this->S(); -} - -bool XData::Connect(XData &xdata) -{ - auto a_drive_b = [](XData &a, XData &b) -> bool { - if (a.mIOType == IOType::Input) { - Error("Master IO type cannot be IOType::Input"); - return false; - } - if (b.mIOType == IOType::Output) { - Error("Slave IO type cannot be IOType::Output"); - return false; - } - std::string desc = "connet_" + a.mName + "_to_" + b.mName; - a.OnChange( - [](bool valid, XData *x, u_int32_t val, void *args) { - if (valid) { - auto y = ((XData *)args); - y->mLogicData = x->mLogicData; - for (int i = 0; i < x->vecSize; i++) { - y->pVecData[i] = x->pVecData[i]; - } - y->_sv_to_local(); - y->_dpi_write(); - y->_update_shadow(); - } - }, - &b, desc); - return true; - }; - // same type - // if(this->mIOType == IOType::InOut || xdata.mIOType == IOType::InOut) - // Warn("connect InOut PINs, it may cause error!"); - if (this->mIOType == xdata.mIOType) { - if (this->mIOType == IOType::InOut) { - xdata = *this; - return a_drive_b(*this, xdata); - } - Error("Can not connect same IOType[%s] %s <-> %s", - this->mIOType == IOType::Input ? "IOType::Input" : - "IOType::Output", - this->mName.c_str(), xdata.mName.c_str()); - return false; - } - // diff type - switch (this->mIOType) { - case IOType::Input: - *this = xdata; - return a_drive_b(xdata, *this); - break; - case IOType::Output: xdata = *this; return a_drive_b(*this, xdata); - case IOType::InOut: - if (xdata.mIOType == IOType::Input) { - xdata = *this; - return a_drive_b(*this, xdata); - } else { - *this = xdata; - return a_drive_b(xdata, *this); - }; - break; - default: - Assert(false, "Connect %s -> %s fail!", this->mName.c_str(), - xdata.mName.c_str()); - } -} - -bool XData::BindVPI(vpiHandle obj, func_vpi_get get, - func_vpi_get_value get_value, func_vpi_put_value put_value, std::string name){ - if(this->vpi_obj_handle != nullptr){ - Warn("BindVPI: already bind, ignore bind"); - return false; - } - if(obj == nullptr){ - Warn("BindVPI: obj is nullptr, ignore bind"); - return false; - } - if(get == nullptr || get_value == nullptr || put_value == nullptr){ - Warn("BindVPI: get/get_value/put_value is nullptr, ignore bind"); - return false; - } - PLI_INT32 type = get(vpiType, obj); - if(type != vpiNet && type != vpiReg){ - Warn("BindVPI: obj type is not vpiNet or vpiReg, ignore bind"); - return false; - } - this->vpi_obj_handle = obj; - this->vpi_get = get; - this->vpi_get_value = get_value; - this->vpi_put_value = put_value; - // Get data width - PLI_INT32 bit_size = get(vpiSize, obj); - if(bit_size == 1){ - bit_size = 0; - } - PLI_INT32 direction = vpi_get(vpiDirection, obj); - bool writeable = (direction != vpiOutput); - auto mode = IOType::Input; - if (!writeable) { - mode = IOType::Output; - } - // Reinit self - this->ReInit(bit_size, mode, name); - // Define and bind fake DPIRW - if(bit_size == 0){ // As logic - auto fake_dpir = [this](void *data){ - t_vpi_value value; - value.format = vpiIntVal; - if(this->vpi_data_type == VPI_XDATA_SCALAR){ - value.format = vpiScalarVal; - this->vpi_get_value(this->vpi_obj_handle, &value); - *(xsvLogic *)data = (xsvLogic)value.value.scalar; - }else{ - this->vpi_get_value(this->vpi_obj_handle, &value); - *(xsvLogic *)data = (xsvLogic)value.value.integer; - } - }; - auto fake_dpiw = [this](unsigned char data){ - t_vpi_value value; - value.format = vpiIntVal; - value.value.integer = data; - if(this->vpi_data_type == VPI_XDATA_SCALAR){ - value.format = vpiScalarVal; - value.value.scalar = data; - } - this->vpi_put_value(this->vpi_obj_handle, &value, nullptr, this->vpi_obj_wflage); - }; - if(writeable){ - this->BindDPIRW(fake_dpir, fake_dpiw); - }else{ - this->BindDPIRW(fake_dpir, (void(*)(unsigned char))nullptr); - } - }else{ // As vec - auto fake_dpir = [this](void *data){ - t_vpi_value value; - if((this->W() <= 32 && this->vpi_data_type == VPI_XDATA_AUTOCHECK) || this->vpi_data_type == VPI_XDATA_INTEGER){ - value.format = vpiIntVal; - this->vpi_get_value(this->vpi_obj_handle, &value); - this->pVecData[0].aval = value.value.integer; - this->pVecData[0].bval = 0; - }else{ - value.format = vpiVectorVal; - value.value.vector = nullptr; - this->vpi_get_value(this->vpi_obj_handle, &value); - if(value.value.vector == nullptr){ - Error("VPI vector read returned nullptr for XData(%s)", this->mName.c_str()); - this->_zero_sv(); - return; - } - auto dst = (xsvLogicVecVal *)data; - for(uint32_t i = 0; i < this->vecSize; i++){ - dst[i].aval = value.value.vector[i].aval; - dst[i].bval = value.value.vector[i].bval; - } - } - }; - auto fake_dpiw = [this](void *data){ - t_vpi_value value; - value.format = vpiVectorVal; - value.value.vector = (s_vpi_vecval *)data; - if((this->W() <= 32 && this->vpi_data_type == VPI_XDATA_AUTOCHECK) || this->vpi_data_type == VPI_XDATA_INTEGER){ - value.format = vpiIntVal; - value.value.integer = this->pVecData[0].aval; - } - this->vpi_put_value(this->vpi_obj_handle, &value, nullptr, this->vpi_obj_wflage); - }; - if(writeable){ - this->BindDPIRW(fake_dpir, fake_dpiw); - }else{ - this->BindDPIRW(fake_dpir, (void(*)(void *))nullptr); - } - } - if(writeable){ - this->AsImmWrite(); - } - this->backend_kind = XDataBackendKind::VPI; - this->readonly_backend = !writeable; - return true; -} - -XData * XData::FromVPI(vpiHandle obj, func_vpi_get get, func_vpi_get_value get_value, func_vpi_put_value put_value, std::string name){ - auto xdata = new XData(0, IOType::InOut, name); - if(xdata->BindVPI(obj, get, get_value, put_value, name)){ - return xdata; - } - delete xdata; - return nullptr; -} - -} // namespace xspcomm diff --git a/swig/CMakeLists.txt b/swig/CMakeLists.txt index 2602666..6805148 100644 --- a/swig/CMakeLists.txt +++ b/swig/CMakeLists.txt @@ -72,7 +72,13 @@ function(string_contains str substr result) endif() endfunction(string_contains) -string(TOLOWER "$ENV{BUILD_XSPCOMM_SWIG}" swig_target) +set(XSPCOMM_SWIG_LANGUAGES "$ENV{BUILD_XSPCOMM_SWIG}" CACHE STRING + "Comma-separated SWIG binding languages") +if(NOT "$ENV{BUILD_XSPCOMM_SWIG}" STREQUAL "") + set(XSPCOMM_SWIG_LANGUAGES "$ENV{BUILD_XSPCOMM_SWIG}" CACHE STRING + "Comma-separated SWIG binding languages" FORCE) +endif() +string(TOLOWER "${XSPCOMM_SWIG_LANGUAGES}" swig_target) if (SKBUILD AND "${swig_target}" STREQUAL "") set(swig_target "python") endif () diff --git a/swig/javascript/CMakeLists.txt b/swig/javascript/CMakeLists.txt index 0200835..6a641d4 100644 --- a/swig/javascript/CMakeLists.txt +++ b/swig/javascript/CMakeLists.txt @@ -4,13 +4,10 @@ setup_swig_javascript_env() set_property(SOURCE javascript.i PROPERTY CPLUSPLUS ON) add_compile_options(-fexceptions -sMODULARIZE=1 -sEXPORT_NAME=xspcomm) -swig_add_library(jsxspcomm LANGUAGE javascript SOURCES -javascript.i -../../src/xclock.cpp -../../src/xport.cpp -../../src/xdata.cpp -../../src/xcoroutine.cpp -../../src/xutil.cpp) +include(${PROJECT_SOURCE_DIR}/cmake/Sources.cmake) +swig_add_library(jsxspcomm LANGUAGE javascript SOURCES + javascript.i ${XSPCOMM_SOURCES}) +target_include_directories(jsxspcomm PRIVATE ${PROJECT_SOURCE_DIR}/third_party) # set buld output directory set(XCOMM_LIB_DIR ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/../javascript/xspcomm) diff --git a/swig/python/CMakeLists.txt b/swig/python/CMakeLists.txt index 9ece1eb..c277aa2 100644 --- a/swig/python/CMakeLists.txt +++ b/swig/python/CMakeLists.txt @@ -46,6 +46,8 @@ else () target_link_libraries(pyxspcomm PRIVATE xspcomm Python3::Module) endif () +target_include_directories(pyxspcomm PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + # set buld output directory set(XCOMM_LIB_DIR ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/../python/xspcomm) set(STAGE_NATIVE_LIBRARY_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/../StageSharedLibrary.cmake) diff --git a/swig/python/python.i b/swig/python/python.i index 3c454ee..2828b57 100644 --- a/swig/python/python.i +++ b/swig/python/python.i @@ -18,9 +18,9 @@ } %{ - #include "xspcomm/thirdcall.h" + #include "thirdcall.h" %} -%include "xspcomm/thirdcall.h" +%include "thirdcall.h" %include ../xcomm.i %constant void (*DPI_TEST_LR)(void *v) = xspcomm::TEST_DPI_LR; diff --git a/swig/python/thirdcall.cpp b/swig/python/thirdcall.cpp index 3a46f65..3cd611a 100644 --- a/swig/python/thirdcall.cpp +++ b/swig/python/thirdcall.cpp @@ -1,4 +1,4 @@ -#include "xspcomm/thirdcall.h" +#include "thirdcall.h" #include "stdlib.h" #include "stdio.h" #include diff --git a/include/xspcomm/thirdcall.h b/swig/python/thirdcall.h similarity index 100% rename from include/xspcomm/thirdcall.h rename to swig/python/thirdcall.h diff --git a/swig/xcomm.i b/swig/xcomm.i index e44df0b..0079ffc 100644 --- a/swig/xcomm.i +++ b/swig/xcomm.i @@ -45,7 +45,7 @@ %include std_string.i %include std_map.i %include std_vector.i -#if !defined(SWIGGO) && !defined(SWIGLUA) +#if !defined(SWIGGO) && !defined(SWIGLUA) && !defined(SWIGJAVASCRIPT) %include std_shared_ptr.i %shared_ptr(xspcomm::XData) #endif @@ -66,9 +66,10 @@ namespace std { %include "xspcomm/xcomuse/callback.h" %include "xspcomm/xcomuse/condition.h" %include "xspcomm/xcomuse/range.h" -%include "xspcomm/xcomuse/utils.h" +%include "xspcomm/common/memory.h" %include "xspcomm/xexpr.h" -%include "xspcomm/xfsm.h" +%include "xspcomm/xcomuse/expr.h" +%include "xspcomm/xcomuse/fsm.h" %include "xspcomm/xcomuse.h" %include exception.i %exception { @@ -80,6 +81,8 @@ namespace std { SWIG_exception(SWIG_RuntimeError, error.what()); } } +%include "xspcomm/xpattern.h" +%include "xspcomm/xcoverage.h" %include "xspcomm/xtrigger.h" %exception; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 56ee974..4b5edf3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,31 +1,16 @@ -include_directories(../include) -enable_testing() -if(NOT "${XSPCOMM_WASM_LIB}" STREQUAL "TRUE") -add_executable(test_xdata test_xdata.cpp) -target_link_libraries(test_xdata ${PROJECT_NAME}) -add_test(NAME test_xdata COMMAND test_xdata) - -add_executable(test_xclock test_xclock.cpp) -target_link_libraries(test_xclock ${PROJECT_NAME}) -add_test(NAME test_xclock COMMAND test_xclock) - -add_executable(test_xtrigger test_xtrigger.cpp) -target_link_libraries(test_xtrigger ${PROJECT_NAME}) -add_test(NAME test_xtrigger COMMAND test_xtrigger) - -add_executable(test_xcomuse_base test_xcomuse_base.cpp) -target_link_libraries(test_xcomuse_base ${PROJECT_NAME}) -add_test(NAME test_xcomuse_base COMMAND test_xcomuse_base) - -add_executable(test_xexpr test_xexpr.cpp) -target_link_libraries(test_xexpr ${PROJECT_NAME}) -add_test(NAME test_xexpr COMMAND test_xexpr) - -add_executable(test_xvpi test_xvpi.cpp) -target_link_libraries(test_xvpi ${PROJECT_NAME}) -add_test(NAME test_xvpi COMMAND test_xvpi) - -add_executable(test_xfsm test_xfsm.cpp) -target_link_libraries(test_xfsm ${PROJECT_NAME}) -add_test(NAME test_xfsm COMMAND test_xfsm) +if(NOT XSPCOMM_WASM_LIB) + foreach(component IN ITEMS xdata xclock xtrigger xcoverage xcomuse xexpr xvpi xfsm) + add_executable(test_${component} unit/test_${component}.cpp) + target_include_directories(test_${component} PRIVATE + ${PROJECT_SOURCE_DIR}/third_party/catch2 + ${PROJECT_SOURCE_DIR}/src) + target_link_libraries(test_${component} PRIVATE ${PROJECT_NAME}) + add_test(NAME test_${component} COMMAND test_${component}) + endforeach() + add_executable(test_coverage_core unit/test_coverage_core.cpp) + target_include_directories(test_coverage_core PRIVATE + ${PROJECT_SOURCE_DIR}/third_party/catch2 ${PROJECT_SOURCE_DIR}/src) + target_link_libraries(test_coverage_core PRIVATE ${PROJECT_NAME}) + add_test(NAME test_coverage_core COMMAND test_coverage_core) + target_sources(test_xdata PRIVATE unit/xdata_cases.cpp) endif() diff --git a/tests/test_golang.go b/tests/bindings/golang/test_golang.go similarity index 100% rename from tests/test_golang.go rename to tests/bindings/golang/test_golang.go diff --git a/tests/test_java.java b/tests/bindings/java/test_java.java similarity index 100% rename from tests/test_java.java rename to tests/bindings/java/test_java.java diff --git a/tests/test_lua.lua b/tests/bindings/lua/test_lua.lua similarity index 100% rename from tests/test_lua.lua rename to tests/bindings/lua/test_lua.lua diff --git a/tests/test_pythirdcall.py b/tests/bindings/python/test_pythirdcall.py similarity index 100% rename from tests/test_pythirdcall.py rename to tests/bindings/python/test_pythirdcall.py diff --git a/tests/test_python.py b/tests/bindings/python/test_python.py similarity index 96% rename from tests/test_python.py rename to tests/bindings/python/test_python.py index 596c3f3..1dd9528 100644 --- a/tests/test_python.py +++ b/tests/bindings/python/test_python.py @@ -1,3 +1,5 @@ +from pathlib import Path + import xspcomm as xsp from xspcomm import * @@ -144,8 +146,8 @@ def test_xdata(): clk.Step(3) # test xsignal_cfg.h - cfg = XSignalCFG("tests/test_signal_cfg.yaml") - cfg2 = XSignalCFG("tests/test_signal_cfg_map.yaml") + cfg = XSignalCFG(str(Path(__file__).resolve().parents[2] / "fixtures/signal_cfg.yaml")) + cfg2 = XSignalCFG(str(Path(__file__).resolve().parents[2] / "fixtures/signal_cfg_map.yaml")) assert cfg.String() == cfg2.String(), "%s != %s" % (cfg.String().strip(), cfg2.String().strip()) # test cstring diff --git a/tests/test_python_exception.py b/tests/bindings/python/test_python_exception.py similarity index 100% rename from tests/test_python_exception.py rename to tests/bindings/python/test_python_exception.py diff --git a/tests/test_scala.scala b/tests/bindings/scala/test_scala.scala similarity index 100% rename from tests/test_scala.scala rename to tests/bindings/scala/test_scala.scala diff --git a/tests/test_signal_cfg.yaml b/tests/fixtures/signal_cfg.yaml similarity index 100% rename from tests/test_signal_cfg.yaml rename to tests/fixtures/signal_cfg.yaml diff --git a/tests/test_signal_cfg_map.yaml b/tests/fixtures/signal_cfg_map.yaml similarity index 100% rename from tests/test_signal_cfg_map.yaml rename to tests/fixtures/signal_cfg_map.yaml diff --git a/tests/integration/tlm/Makefile b/tests/integration/tlm/Makefile new file mode 100644 index 0000000..4c2c8ff --- /dev/null +++ b/tests/integration/tlm/Makefile @@ -0,0 +1,10 @@ +PYTHON_INCLUDE:=$(shell bash -c "python3-config --includes|awk '{print "$$"1}'|sed 's/-I//g'") + +all: + PYTHON_INCLUDE=$(PYTHON_INCLUDE) XSP_COMM_INCLUDE=../../../build/include XSP_COMM_TLM=../../../integrations/tlm make -f Makefile.vcs all + +clean: + PYTHON_INCLUDE=$(PYTHON_INCLUDE) XSP_COMM_INCLUDE=../../../build/include XSP_COMM_TLM=../../../integrations/tlm make -f Makefile.vcs clean + +run: + PYTHON_INCLUDE=$(PYTHON_INCLUDE) XSP_COMM_INCLUDE=../../../build/include XSP_COMM_TLM=../../../integrations/tlm make -f Makefile.vcs run diff --git a/tests/tlm/Makefile.vcs b/tests/integration/tlm/Makefile.vcs similarity index 91% rename from tests/tlm/Makefile.vcs rename to tests/integration/tlm/Makefile.vcs index 5c04e56..4dbc5cb 100644 --- a/tests/tlm/Makefile.vcs +++ b/tests/integration/tlm/Makefile.vcs @@ -1,3 +1,5 @@ +XSP_COMM_TLM ?= $(XSP_COMM_INCLUDE)/../share/xspcomm/tlm + ifndef UVMC_HOME $(error UVMC_HOME is not set) @@ -47,8 +49,8 @@ all: $(MAKE) -f Makefile.vcs clean comp run comp: - swig -D'MODULE_NAME="tlm_pbsb"' -python -c++ -DUSE_VCS -I${XSP_COMM_INCLUDE} -o tlmps.cpp ${XSP_COMM_INCLUDE}/xspcomm/python_tlm_pbsb.i - $(SYSCAN) -full64 -cflags -DUSE_VCS -cflags -I${PYTHON_INCLUDE} -cflags -I${XSP_COMM_INCLUDE} ${XSP_COMM_INCLUDE}/xspcomm/tlm_pbsb.cpp tlmps.cpp + swig -D'MODULE_NAME="tlm_pbsb"' -python -c++ -DUSE_VCS -I${XSP_COMM_INCLUDE} -o tlmps.cpp ${XSP_COMM_TLM}/python.i + $(SYSCAN) -full64 -cflags -DUSE_VCS -cflags -I${PYTHON_INCLUDE} -cflags -I${XSP_COMM_INCLUDE} ${XSP_COMM_TLM}/pubsub.cpp tlmps.cpp $(VLOGAN) -full64 +incdir+common tlm.sv +define+UVM_OBJECT_MUST_HAVE_CONSTRUCTOR $(VCS_ELAB) -e VcsMain -full64 sv_main ${VCS_HOME}/linux64/lib/vcs_tls.o -slave mv simv _tlm_pbsb.so diff --git a/tests/tlm/consumer.sv b/tests/integration/tlm/consumer.sv similarity index 100% rename from tests/tlm/consumer.sv rename to tests/integration/tlm/consumer.sv diff --git a/tests/tlm/producer.sv b/tests/integration/tlm/producer.sv similarity index 100% rename from tests/tlm/producer.sv rename to tests/integration/tlm/producer.sv diff --git a/tests/tlm/test.py b/tests/integration/tlm/test.py similarity index 100% rename from tests/tlm/test.py rename to tests/integration/tlm/test.py diff --git a/tests/tlm/tlm.sv b/tests/integration/tlm/tlm.sv similarity index 100% rename from tests/tlm/tlm.sv rename to tests/integration/tlm/tlm.sv diff --git a/tests/tlm/Makefile b/tests/tlm/Makefile deleted file mode 100644 index 6f932f7..0000000 --- a/tests/tlm/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -PYTHON_INCLUDE:=$(shell bash -c "python3-config --includes|awk '{print "$$"1}'|sed 's/-I//g'") - -all: - PYTHON_INCLUDE=$(PYTHON_INCLUDE) XSP_COMM_INCLUDE=../../include make -f Makefile.vcs all - -clean: - PYTHON_INCLUDE=$(PYTHON_INCLUDE) XSP_COMM_INCLUDE=../../include make -f Makefile.vcs clean - -run: - PYTHON_INCLUDE=$(PYTHON_INCLUDE) XSP_COMM_INCLUDE=../../include make -f Makefile.vcs run diff --git a/tests/unit/test_coverage_core.cpp b/tests/unit/test_coverage_core.cpp new file mode 100644 index 0000000..c194240 --- /dev/null +++ b/tests/unit/test_coverage_core.cpp @@ -0,0 +1,80 @@ +#define CATCH_CONFIG_MAIN +#include "catch.hpp" + +#include "coverage/state.h" +#include "xspcomm/xexpr.h" + +using namespace xspcomm; +using detail::CoverageState; + +TEST_CASE("Coverage samples independently of the trigger engine", "[coverage]") { + ExprEngine expr; + XData value(8, XData::InOut), gate(1, XData::InOut), abort(1, XData::InOut); + value = 7; + gate = 1; + abort = 0; + XCoverageItem item; + item.signal = &value; + XCoverageBin bin; + bin.root = expr.NewConst(1); + CoverageState coverage(9, expr, {}, {item}, {bin}, + expr.NewSignal(&gate), expr.NewSignal(&abort), true, 32, false, 1, true); + + REQUIRE(coverage.BeginSample(expr, 1) == CoverageState::SampleStatus::Ready); + coverage.Sample(expr, 1); + REQUIRE(coverage.BeginSample(expr, 1) == CoverageState::SampleStatus::Skip); + REQUIRE(coverage.Snapshot(1, false).counters.back() == 1); + + gate = 0; + REQUIRE(coverage.BeginSample(expr, 2) == CoverageState::SampleStatus::Ready); + coverage.Sample(expr, 2); + REQUIRE(coverage.Snapshot(2, false).counters[1] == 1); + REQUIRE(coverage.Snapshot(2, false).counters.back() == 1); + + abort = 1; + REQUIRE(coverage.BeginSample(expr, 3) == CoverageState::SampleStatus::Aborted); + REQUIRE(coverage.Snapshot(3, false).counters.back() == 1); + coverage.ResetCounters(); + const auto reset = coverage.Snapshot(3, false); + REQUIRE(reset.generation == 9); + REQUIRE(reset.epoch == 1); + REQUIRE(reset.counters.back() == 0); +} + +TEST_CASE("Coverage abort clears overlapping pattern history", "[coverage][pattern]") { + ExprEngine expr; + XData start(1, XData::InOut), done(1, XData::InOut); + XData abort(1, XData::InOut), value(8, XData::InOut); + start = 1; + done = 0; + abort = 0; + const std::vector steps = { + {XSequenceStepKind::Wait, expr.NewSignal(&start)}, + {XSequenceStepKind::Within, expr.NewSignal(&done), 1, 3}}; + const detail::PatternView source{&steps}; + XCoverageItem item; + item.signal = &value; + XCoverageBin bin; + bin.root = expr.NewConst(1); + CoverageState coverage(1, expr, source, {item}, {bin}, + -1, expr.NewSignal(&abort), true, 32, true, 2, true); + + for (uint64_t tick = 1; tick <= 2; ++tick) { + expr.SetCycle(tick); + REQUIRE(coverage.BeginSample(expr, tick) == CoverageState::SampleStatus::Ready); + coverage.SamplePattern(expr, source, tick); + } + REQUIRE(coverage.Snapshot(2, true).progress.size() == 10); + abort = 1; + REQUIRE(coverage.BeginSample(expr, 3) == CoverageState::SampleStatus::Aborted); + const auto aborted = coverage.Snapshot(3, true); + REQUIRE(aborted.progress.empty()); + REQUIRE(aborted.diagnostics[4] == 2); + + start = 0; + done = 1; + abort = 0; + REQUIRE(coverage.BeginSample(expr, 4) == CoverageState::SampleStatus::Ready); + coverage.SamplePattern(expr, source, 4); + REQUIRE(coverage.Snapshot(4, true).counters[0] == 0); +} diff --git a/tests/test_xclock.cpp b/tests/unit/test_xclock.cpp similarity index 100% rename from tests/test_xclock.cpp rename to tests/unit/test_xclock.cpp diff --git a/tests/test_xcomuse_base.cpp b/tests/unit/test_xcomuse.cpp similarity index 78% rename from tests/test_xcomuse_base.cpp rename to tests/unit/test_xcomuse.cpp index 21a4f4b..70b7fe9 100644 --- a/tests/test_xcomuse_base.cpp +++ b/tests/unit/test_xcomuse.cpp @@ -4,7 +4,11 @@ #include "xspcomm/xcomuse.h" #include "xspcomm/xclock.h" #include "xspcomm/xdata.h" +#include +#include #include +#include +#include using namespace xspcomm; @@ -304,3 +308,65 @@ TEST_CASE("ComUseEcho smoke", "[xcomuse_base]") { ComUseEcho echo(valid.CSelf(), data.CSelf(), false, "%c", 0); echo.Call(); } + + +TEST_CASE("Pointer conditions order signed values at every buffer width", "[xcomuse_base][compare]") { + for (int bytes : {1, 2, 3, 4, 5, 7, 8, 9, 16, 33, 65}) { + auto lhs = std::make_unique(bytes + 1); + auto rhs = std::make_unique(bytes + 1); + auto store = [bytes](unsigned char* data, int value) { + std::fill_n(data, bytes, value < 0 ? 0xff : 0); + const uint64_t bits = static_cast(static_cast(value)); + std::memcpy(data, &bits, std::min(static_cast(bytes), sizeof(bits))); + }; + ComUseCondCheck checker; + for (int a : {-128, -2, -1, 0, 1, 127}) { + for (int b : {-128, -2, -1, 0, 1, 127}) { + store(lhs.get() + 1, a); + store(rhs.get() + 1, b); + const std::pair cases[] = { + {ComUseCondCmp::EQ, a == b}, {ComUseCondCmp::NE, a != b}, + {ComUseCondCmp::GT, a > b}, {ComUseCondCmp::GE, a >= b}, + {ComUseCondCmp::LT, a < b}, {ComUseCondCmp::LE, a <= b}, + }; + for (auto [operation, expected] : cases) { + INFO("bytes=" << bytes << ", a=" << a << ", b=" << b << ", op=" << static_cast(operation)); + checker.SetCondition("compare", reinterpret_cast(lhs.get() + 1), + reinterpret_cast(rhs.get() + 1), operation, bytes); + checker.Call(); + REQUIRE(checker.ListCondition().at("compare") == expected); + } + } + } + } +} + +TEST_CASE("XData equality preserves zero extension and four-state masks", "[xcomuse_base][compare]") { + for (unsigned width : {64U, 65U, 127U, 129U, 513U}) { + XData wide(width, XData::InOut), peer(width, XData::InOut), narrow(8, XData::InOut); + wide.Set(0x5a); peer.Set(0x5a); narrow.Set(0x5a); + REQUIRE((wide == peer)); + REQUIRE((wide == narrow)); + REQUIRE((narrow == wide)); + REQUIRE((wide == uint64_t(0x5a))); + wide[width - 1] = 1; + REQUIRE_FALSE((wide == peer)); + REQUIRE_FALSE((wide == narrow)); + REQUIRE_FALSE((wide == uint64_t(0x5a))); + wide[width - 1] = "x"; + peer[width - 1] = "x"; + REQUIRE((wide == peer)); + REQUIRE_FALSE((wide == narrow)); + REQUIRE_FALSE((wide == uint64_t(0x5a))); + ComUseCondCheck checker; + checker.SetCondition("eq", &wide, &peer, ComUseCondCmp::EQ); + checker.SetCondition("ne", &wide, &peer, ComUseCondCmp::NE); + checker.Call(); + REQUIRE(checker.ListCondition().at("eq")); + REQUIRE_FALSE(checker.ListCondition().at("ne")); + peer[width - 1] = "z"; + checker.Call(); + REQUIRE_FALSE(checker.ListCondition().at("eq")); + REQUIRE(checker.ListCondition().at("ne")); + } +} diff --git a/tests/unit/test_xcoverage.cpp b/tests/unit/test_xcoverage.cpp new file mode 100644 index 0000000..9b1aae7 --- /dev/null +++ b/tests/unit/test_xcoverage.cpp @@ -0,0 +1,164 @@ +#define CATCH_CONFIG_MAIN +#include "catch.hpp" + +#include "xspcomm/xtrigger.h" +#include "xspcomm/xexpr.h" + +using namespace xspcomm; + +TEST_CASE("Coverage counts persist without returning ordinary hits", "[xtrigger][coverage]") { + XClock clock([](bool) { return 0; }); + XTriggerEngine engine(clock, 4); + XData value(8, XData::InOut); + value = 1; + const int source = engine.ExprNewSignal(&value); + const int one = engine.ExprNewCompare(static_cast(ExprOp::EQ), source, engine.ExprNewConst(1)); + XCoverageItem item; + item.signal = &value; + XCoverageBin bin; + bin.root = one; + auto handle = engine.ArmEdge(XPhase::RisingStable); + engine.AttachCoverage(handle, {item}, {bin}); + auto limit = engine.ArmClockCycles(10); + auto run = engine.RunUntil(100); + REQUIRE(run.advanced_ticks == 20); + REQUIRE(run.hits.size() == 1); + REQUIRE(run.hits.front().slot == limit.slot); + const auto first = engine.CoverageSnapshot(handle); + REQUIRE(first.counters[0] == 10); + REQUIRE(first.counters.back() == 10); + REQUIRE(engine.CoverageSnapshot(handle).counters == first.counters); + REQUIRE_THROWS(engine.ClearExecutionState()); + engine.ResetCoverage(handle, false); + REQUIRE(engine.CoverageSnapshot(handle).counters == first.counters); + engine.ResetCoverage(handle, true); + REQUIRE(engine.CoverageSnapshot(handle).epoch == 1); + REQUIRE(engine.CoverageSnapshot(handle).counters[0] == 0); + REQUIRE(engine.Disarm(handle)); + REQUIRE_THROWS(engine.CoverageSnapshot(handle)); + REQUIRE(engine.Disarm(limit)); + engine.ClearExecutionState(); + REQUIRE(engine.ActiveCount() == 0); +} + +TEST_CASE("Coverage transitions overlap and have isolated registration history", "[xtrigger][coverage]") { + XClock clock([](bool) { return 0; }); + XTriggerEngine engine(clock); + XData value(8, XData::InOut); + value = 1; + const int source = engine.ExprNewSignal(&value); + const int one = engine.ExprNewCompare(static_cast(ExprOp::EQ), source, engine.ExprNewConst(1)); + XCoverageItem item; + item.signal = &value; + XCoverageBin bin; + bin.steps = {{XSequenceStepKind::Wait, one}, {XSequenceStepKind::Next, one}}; + auto h = engine.ArmEdge(XPhase::RisingStable); + engine.AttachCoverage(h, {item}, {bin}); + REQUIRE(engine.RunUntil(6).hits.empty()); + REQUIRE(engine.CoverageSnapshot(h).counters.back() == 2); + engine.ResetCoverage(h, false); + REQUIRE(engine.RunUntil(2).hits.empty()); + REQUIRE(engine.CoverageSnapshot(h).counters.back() == 2); + REQUIRE(engine.Disarm(h)); + auto next = engine.ArmEdge(XPhase::RisingStable); + engine.AttachCoverage(next, {item}, {bin}); + engine.RunUntil(2); + REQUIRE(engine.CoverageSnapshot(next).counters.back() == 0); + REQUIRE(engine.Disarm(next)); +} + +TEST_CASE("Coverage rejects malformed descriptors without changing the watcher", "[xtrigger][coverage]") { + XClock clock([](bool) { return 0; }); + XTriggerEngine engine(clock); + auto h = engine.ArmEdge(XPhase::RisingStable); + XCoverageItem item; + XCoverageBin bin; + XData value(8, XData::InOut); + item.signal = &value; + bin.root = 100000; + REQUIRE_THROWS(engine.AttachCoverage(h, {item}, {bin})); + REQUIRE(engine.RunUntil(2).hits.size() == 1); + REQUIRE(engine.Disarm(h)); +} + + +TEST_CASE("Coverage snapshots preserve full-width illegal values", "[xtrigger][coverage]") { + XClock clock([](bool) { return 0; }); + XTriggerEngine engine(clock); + XData value(128, XData::InOut); + value = "0x80000000000000000000000000000007"; + auto bytes = value.GetBytes(); + XCoverageItem item; + item.signal = &value; + XCoverageBin bin; + bin.kind = 2; + bin.root = engine.ExprNewCompareSigConstBytes(static_cast(ExprOp::EQ), &value, bytes); + auto handle = engine.ArmEdge(XPhase::RisingStable); + engine.AttachCoverage(handle, {item}, {bin}, -1, -1, false); + engine.RunUntil(2); + value = 0; + const auto snapshot = engine.CoverageSnapshot(handle); + REQUIRE(snapshot.illegal_values == std::vector{"80000000000000000000000000000007"}); + REQUIRE(snapshot.illegal_ticks == std::vector{2}); + REQUIRE(snapshot.counters.back() == 1); + engine.ResetCoverage(handle, true); + REQUIRE(engine.CoverageSnapshot(handle).illegal_values.empty()); + REQUIRE(engine.Disarm(handle)); + REQUIRE_THROWS(engine.CoverageSnapshot(handle)); + engine.ClearExecutionState(); + REQUIRE(engine.ActiveCount() == 0); +} + + +TEST_CASE("Coverage overlapping sequences count simultaneous completions", "[xtrigger][coverage]") { + XClock clock([](bool) { return 0; }); + XTriggerEngine engine(clock); + XData start(1, XData::InOut), done(1, XData::InOut), value(8, XData::InOut); + const int start_root = engine.ExprNewSignal(&start), done_root = engine.ExprNewSignal(&done); + auto h = engine.ArmSequence({{XSequenceStepKind::Wait, start_root}, + {XSequenceStepKind::Within, done_root, 1, 3}}); + XCoverageItem item; item.signal = &value; + XCoverageBin bin; bin.root = engine.ExprNewConst(1); + engine.AttachCoverage(h, {item}, {bin}, -1, -1, true, 1024, true, 2, true); + start = 1; + REQUIRE(engine.RunUntil(4).hits.empty()); + auto pending = engine.CoverageSnapshot(h, true); + REQUIRE(pending.diagnostics[0] == 2); + REQUIRE(pending.diagnostics[6] == 2); + REQUIRE(pending.progress.size() == 10); + REQUIRE(engine.CoverageSnapshot(h).progress.empty()); + start = 0; done = 1; + REQUIRE(engine.RunUntil(2).hits.empty()); + auto completed = engine.CoverageSnapshot(h, true); + REQUIRE(completed.counters[0] == 2); + REQUIRE(completed.counters.back() == 2); + REQUIRE(completed.diagnostics[1] == 2); + REQUIRE(completed.progress.empty()); + engine.ResetCoverage(h, true); + REQUIRE(engine.CoverageSnapshot(h).diagnostics[0] == 0); + REQUIRE(engine.Disarm(h)); + engine.ClearExecutionState(); +} + +TEST_CASE("Coverage overlap capacity failure preserves pending state for cleanup", "[xtrigger][coverage]") { + XClock clock([](bool) { return 0; }); + XTriggerEngine engine(clock); + XData start(1, XData::InOut), done(1, XData::InOut), value(8, XData::InOut); + auto h = engine.ArmSequence({{XSequenceStepKind::Wait, engine.ExprNewSignal(&start)}, + {XSequenceStepKind::Wait, engine.ExprNewSignal(&done)}}); + XCoverageItem item; item.signal = &value; + XCoverageBin bin; bin.root = engine.ExprNewConst(1); + REQUIRE_THROWS(engine.AttachCoverage(h, {item}, {bin}, -1, -1, true, 1024, true, 0, true)); + engine.AttachCoverage(h, {item}, {bin}, -1, -1, true, 1024, true, 1, false); + start = 1; + REQUIRE_THROWS(engine.RunUntil(4)); + auto snapshot = engine.CoverageSnapshot(h, true); + REQUIRE(snapshot.incomplete); + REQUIRE(snapshot.counters[0] == 0); + REQUIRE(snapshot.diagnostics.empty()); + REQUIRE(snapshot.progress.size() == 5); + REQUIRE(engine.Disarm(h)); + REQUIRE_THROWS(engine.CoverageSnapshot(h, true)); + engine.ClearExecutionState(); + REQUIRE(engine.ActiveCount() == 0); +} diff --git a/tests/test_xdata.cpp b/tests/unit/test_xdata.cpp similarity index 98% rename from tests/test_xdata.cpp rename to tests/unit/test_xdata.cpp index 661840d..2e7cb56 100644 --- a/tests/test_xdata.cpp +++ b/tests/unit/test_xdata.cpp @@ -1,5 +1,5 @@ #include "xspcomm/xcomm.h" -#include "xspcomm/xinstance.h" +namespace xspcomm { int test_xdata(); } #include #include #include @@ -198,6 +198,6 @@ int main(int argsc, const char **argsv) test_native_reinit(); test_signed_values_and_slices(); test_bit_buffer_boundaries(); - test_xdata(); + Assert(test_xdata() == 0, "XData legacy cases failed"); return 0; } diff --git a/tests/test_xexpr.cpp b/tests/unit/test_xexpr.cpp similarity index 99% rename from tests/test_xexpr.cpp rename to tests/unit/test_xexpr.cpp index cca7de6..f21b3af 100644 --- a/tests/test_xexpr.cpp +++ b/tests/unit/test_xexpr.cpp @@ -2,6 +2,7 @@ #include "catch.hpp" #include "xspcomm/xexpr.h" +#include "xspcomm/xcomuse/expr.h" #include "xspcomm/xdata.h" #include "xspcomm/xsignal_cfg.h" diff --git a/tests/test_xfsm.cpp b/tests/unit/test_xfsm.cpp similarity index 98% rename from tests/test_xfsm.cpp rename to tests/unit/test_xfsm.cpp index b47336f..5dc9a6f 100644 --- a/tests/test_xfsm.cpp +++ b/tests/unit/test_xfsm.cpp @@ -1,7 +1,7 @@ #define CATCH_CONFIG_MAIN #include "catch.hpp" -#include "xspcomm/xfsm.h" +#include "xspcomm/xcomuse/fsm.h" #include "xspcomm/xclock.h" using namespace xspcomm; diff --git a/tests/test_xtrigger.cpp b/tests/unit/test_xtrigger.cpp similarity index 66% rename from tests/test_xtrigger.cpp rename to tests/unit/test_xtrigger.cpp index 6f0c332..1df08f4 100644 --- a/tests/test_xtrigger.cpp +++ b/tests/unit/test_xtrigger.cpp @@ -397,160 +397,3 @@ TEST_CASE("XTriggerEngine samples DriveStable without advancing time", REQUIRE(sampled.hits[0].tick == 1); REQUIRE(engine.Disarm(handle)); } - -TEST_CASE("Coverage counts persist without returning ordinary hits", "[xtrigger][coverage]") { - XClock clock([](bool) { return 0; }); - XTriggerEngine engine(clock, 4); - XData value(8, XData::InOut); - value = 1; - const int source = engine.ExprNewSignal(&value); - const int one = engine.ExprNewCompare(static_cast(ExprOp::EQ), source, engine.ExprNewConst(1)); - XCoverageItem item; - item.signal = &value; - XCoverageBin bin; - bin.root = one; - auto handle = engine.ArmEdge(XPhase::RisingStable); - engine.AttachCoverage(handle, {item}, {bin}); - auto limit = engine.ArmClockCycles(10); - auto run = engine.RunUntil(100); - REQUIRE(run.advanced_ticks == 20); - REQUIRE(run.hits.size() == 1); - REQUIRE(run.hits.front().slot == limit.slot); - const auto first = engine.CoverageSnapshot(handle); - REQUIRE(first.counters[0] == 10); - REQUIRE(first.counters.back() == 10); - REQUIRE(engine.CoverageSnapshot(handle).counters == first.counters); - REQUIRE_THROWS(engine.ClearExecutionState()); - engine.ResetCoverage(handle, false); - REQUIRE(engine.CoverageSnapshot(handle).counters == first.counters); - engine.ResetCoverage(handle, true); - REQUIRE(engine.CoverageSnapshot(handle).epoch == 1); - REQUIRE(engine.CoverageSnapshot(handle).counters[0] == 0); - REQUIRE(engine.Disarm(handle)); - REQUIRE_THROWS(engine.CoverageSnapshot(handle)); - REQUIRE(engine.Disarm(limit)); - engine.ClearExecutionState(); - REQUIRE(engine.ActiveCount() == 0); -} - -TEST_CASE("Coverage transitions overlap and have isolated registration history", "[xtrigger][coverage]") { - XClock clock([](bool) { return 0; }); - XTriggerEngine engine(clock); - XData value(8, XData::InOut); - value = 1; - const int source = engine.ExprNewSignal(&value); - const int one = engine.ExprNewCompare(static_cast(ExprOp::EQ), source, engine.ExprNewConst(1)); - XCoverageItem item; - item.signal = &value; - XCoverageBin bin; - bin.steps = {{XSequenceStepKind::Wait, one}, {XSequenceStepKind::Next, one}}; - auto h = engine.ArmEdge(XPhase::RisingStable); - engine.AttachCoverage(h, {item}, {bin}); - REQUIRE(engine.RunUntil(6).hits.empty()); - REQUIRE(engine.CoverageSnapshot(h).counters.back() == 2); - engine.ResetCoverage(h, false); - REQUIRE(engine.RunUntil(2).hits.empty()); - REQUIRE(engine.CoverageSnapshot(h).counters.back() == 2); - REQUIRE(engine.Disarm(h)); - auto next = engine.ArmEdge(XPhase::RisingStable); - engine.AttachCoverage(next, {item}, {bin}); - engine.RunUntil(2); - REQUIRE(engine.CoverageSnapshot(next).counters.back() == 0); - REQUIRE(engine.Disarm(next)); -} - -TEST_CASE("Coverage rejects malformed descriptors without changing the watcher", "[xtrigger][coverage]") { - XClock clock([](bool) { return 0; }); - XTriggerEngine engine(clock); - auto h = engine.ArmEdge(XPhase::RisingStable); - XCoverageItem item; - XCoverageBin bin; - XData value(8, XData::InOut); - item.signal = &value; - bin.root = 100000; - REQUIRE_THROWS(engine.AttachCoverage(h, {item}, {bin})); - REQUIRE(engine.RunUntil(2).hits.size() == 1); - REQUIRE(engine.Disarm(h)); -} - - -TEST_CASE("Coverage snapshots preserve full-width illegal values", "[xtrigger][coverage]") { - XClock clock([](bool) { return 0; }); - XTriggerEngine engine(clock); - XData value(128, XData::InOut); - value = "0x80000000000000000000000000000007"; - auto bytes = value.GetBytes(); - XCoverageItem item; - item.signal = &value; - XCoverageBin bin; - bin.kind = 2; - bin.root = engine.ExprNewCompareSigConstBytes(static_cast(ExprOp::EQ), &value, bytes); - auto handle = engine.ArmEdge(XPhase::RisingStable); - engine.AttachCoverage(handle, {item}, {bin}, -1, -1, false); - engine.RunUntil(2); - value = 0; - const auto snapshot = engine.CoverageSnapshot(handle); - REQUIRE(snapshot.illegal_values == std::vector{"80000000000000000000000000000007"}); - REQUIRE(snapshot.illegal_ticks == std::vector{2}); - REQUIRE(snapshot.counters.back() == 1); - engine.ResetCoverage(handle, true); - REQUIRE(engine.CoverageSnapshot(handle).illegal_values.empty()); - REQUIRE(engine.Disarm(handle)); - REQUIRE_THROWS(engine.CoverageSnapshot(handle)); - engine.ClearExecutionState(); - REQUIRE(engine.ActiveCount() == 0); -} - - -TEST_CASE("Coverage overlapping sequences count simultaneous completions", "[xtrigger][coverage]") { - XClock clock([](bool) { return 0; }); - XTriggerEngine engine(clock); - XData start(1, XData::InOut), done(1, XData::InOut), value(8, XData::InOut); - const int start_root = engine.ExprNewSignal(&start), done_root = engine.ExprNewSignal(&done); - auto h = engine.ArmSequence({{XSequenceStepKind::Wait, start_root}, - {XSequenceStepKind::Within, done_root, 1, 3}}); - XCoverageItem item; item.signal = &value; - XCoverageBin bin; bin.root = engine.ExprNewConst(1); - engine.AttachCoverage(h, {item}, {bin}, -1, -1, true, 1024, true, 2, true); - start = 1; - REQUIRE(engine.RunUntil(4).hits.empty()); - auto pending = engine.CoverageSnapshot(h, true); - REQUIRE(pending.diagnostics[0] == 2); - REQUIRE(pending.diagnostics[6] == 2); - REQUIRE(pending.progress.size() == 10); - REQUIRE(engine.CoverageSnapshot(h).progress.empty()); - start = 0; done = 1; - REQUIRE(engine.RunUntil(2).hits.empty()); - auto completed = engine.CoverageSnapshot(h, true); - REQUIRE(completed.counters[0] == 2); - REQUIRE(completed.counters.back() == 2); - REQUIRE(completed.diagnostics[1] == 2); - REQUIRE(completed.progress.empty()); - engine.ResetCoverage(h, true); - REQUIRE(engine.CoverageSnapshot(h).diagnostics[0] == 0); - REQUIRE(engine.Disarm(h)); - engine.ClearExecutionState(); -} - -TEST_CASE("Coverage overlap capacity failure preserves pending state for cleanup", "[xtrigger][coverage]") { - XClock clock([](bool) { return 0; }); - XTriggerEngine engine(clock); - XData start(1, XData::InOut), done(1, XData::InOut), value(8, XData::InOut); - auto h = engine.ArmSequence({{XSequenceStepKind::Wait, engine.ExprNewSignal(&start)}, - {XSequenceStepKind::Wait, engine.ExprNewSignal(&done)}}); - XCoverageItem item; item.signal = &value; - XCoverageBin bin; bin.root = engine.ExprNewConst(1); - REQUIRE_THROWS(engine.AttachCoverage(h, {item}, {bin}, -1, -1, true, 1024, true, 0, true)); - engine.AttachCoverage(h, {item}, {bin}, -1, -1, true, 1024, true, 1, false); - start = 1; - REQUIRE_THROWS(engine.RunUntil(4)); - auto snapshot = engine.CoverageSnapshot(h, true); - REQUIRE(snapshot.incomplete); - REQUIRE(snapshot.counters[0] == 0); - REQUIRE(snapshot.diagnostics.empty()); - REQUIRE(snapshot.progress.size() == 5); - REQUIRE(engine.Disarm(h)); - REQUIRE_THROWS(engine.CoverageSnapshot(h, true)); - engine.ClearExecutionState(); - REQUIRE(engine.ActiveCount() == 0); -} diff --git a/tests/test_xvpi.cpp b/tests/unit/test_xvpi.cpp similarity index 100% rename from tests/test_xvpi.cpp rename to tests/unit/test_xvpi.cpp diff --git a/src/xinstance.cpp b/tests/unit/xdata_cases.cpp similarity index 99% rename from src/xinstance.cpp rename to tests/unit/xdata_cases.cpp index 5b4afb2..63df2eb 100644 --- a/src/xinstance.cpp +++ b/tests/unit/xdata_cases.cpp @@ -1,5 +1,4 @@ #include "xspcomm/xcomm.h" -#include "xspcomm/xinstance.h" #include "xspcomm/xcomuse.h" namespace xspcomm { diff --git a/tests/catch.hpp b/third_party/catch2/catch.hpp similarity index 100% rename from tests/catch.hpp rename to third_party/catch2/catch.hpp diff --git a/include/xspcomm/node.hpp b/third_party/fkyaml/node.hpp similarity index 100% rename from include/xspcomm/node.hpp rename to third_party/fkyaml/node.hpp From 83ab5b2c466ad5ebfb0275b84830a41f9c06bb5e Mon Sep 17 00:00:00 2001 From: Makiras <22570332+Makiras@users.noreply.github.com> Date: Fri, 9 Oct 2026 20:50:43 +0800 Subject: [PATCH 06/10] feat(coverage)!: execute temporal bins through shared trigger matching Extract generic condition and pattern matching from coverage into the Trigger module. Reuse sequence/FSM advancement with ordinary triggers, and keep overlap attempts, completion counts and terminal filtering in a reusable PatternMatcher. Coverage retains gating, reset/abort handling, bin classification and statistics. Support Expr, Sequence and FSM programs on pattern coverpoints. Equivalent bin programs within one coverpoint share one execution while preserving independent terminal selection, hit thresholds and counters. Keep executions independent across coverpoints and expose CoverageExecutionCount for validation. Add focused matcher and coverage regression tests, and document the execution boundaries and protocol. Inline small helpers to avoid additional calls on the sampling path. Validation: - 10 native C++ test targets pass in Release and with ASan/UBSan. - 1,021 experimental XReactor tests pass against a clean installed wheel. - 8,192 legacy sampling steps and 256 stale-handle cases match the baseline. - C++17/C++20 headers, six SWIG generators, TLM wrapper and installed SDK pass. BREAKING CHANGE: extended XCoverageBin descriptors change the public C++ layout. Raise native ABI to 3 and CoverageVersion to 4; consumers must rebuild and update compatibility checks for the new ABI and coverage protocol. --- CMakeLists.txt | 2 +- cmake/Sources.cmake | 1 + docs/architecture.md | 53 ++++++-- docs/maintainer-guidance.md | 15 ++- include/xspcomm/detail/pattern.h | 2 + include/xspcomm/xcoverage.h | 9 +- include/xspcomm/xpattern.h | 6 + include/xspcomm/xtrigger.h | 9 +- src/coverage/engine.cpp | 193 ++++++++++++++++++++-------- src/coverage/state.h | 23 ++-- src/trigger/coverage.cpp | 6 + src/trigger/engine.cpp | 28 ++-- src/trigger/matcher.cpp | 87 +++++++++++++ src/trigger/matcher.h | 75 +++++++++++ tests/CMakeLists.txt | 2 +- tests/unit/test_coverage_core.cpp | 4 +- tests/unit/test_trigger_matcher.cpp | 133 +++++++++++++++++++ tests/unit/test_xcoverage.cpp | 180 ++++++++++++++++++++++++++ 18 files changed, 713 insertions(+), 115 deletions(-) create mode 100644 src/trigger/matcher.cpp create mode 100644 src/trigger/matcher.h create mode 100644 tests/unit/test_trigger_matcher.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c378f0b..3ff2ebe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ else() endif() # Increment only when the native C++ ABI becomes incompatible. -set(XSPCOMM_ABI_VERSION 2) +set(XSPCOMM_ABI_VERSION 3) list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) diff --git a/cmake/Sources.cmake b/cmake/Sources.cmake index 8cd51e7..28e2796 100644 --- a/cmake/Sources.cmake +++ b/cmake/Sources.cmake @@ -21,5 +21,6 @@ set(XSPCOMM_SOURCES pattern.cpp trigger/coverage.cpp trigger/engine.cpp + trigger/matcher.cpp ) list(TRANSFORM XSPCOMM_SOURCES PREPEND "${CMAKE_CURRENT_LIST_DIR}/../src/") diff --git a/docs/architecture.md b/docs/architecture.md index 08b590f..013ac5f 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -8,15 +8,19 @@ headers (`xdata.h`, `xclock.h`, `xexpr.h`, `xtrigger.h`) describe their own modu algorithms depend only on standard headers, so XData and checkers share them. - `xcomuse/`: clock callbacks, condition/range checkers, expression checkers and the text-program FSM adapter. `ExprEngine` itself belongs to `xexpr.h`. -- `xpattern.h`: shared sequence/FSM descriptors. `detail/pattern.h` holds the - matching state required by registrations; `src/pattern.cpp` implements the - shared matching algorithms. -- `xcoverage.h`: coverage descriptors and snapshots. The independent coverage - component in `src/coverage` owns sampling history, bins, crosses, counters and - diagnostics, and has no dependency on trigger types. +- `xpattern.h`: shared event modes and sequence/FSM descriptors. + `detail/pattern.h` holds borrowed program views and matching state; + `src/pattern.cpp` implements sequence/FSM advancement. The common execution + component in `src/trigger/matcher.*` manages expression events, overlapping + attempts, completion counts and FSM terminal results for native consumers. +- `xcoverage.h`: coverage descriptors and snapshots. The component in + `src/coverage` organizes points and bins, selects completion results, and owns + crosses, counters and statistical policies. It uses the common trigger + matcher for execution history and advancement. - `src/trigger`: trigger registration/execution and a small coverage adapter. - Both consumers use the same expression and pattern implementations. Coverage - runs in the native sampling phase, with no event queue or language callback. + Ordinary triggers and coverage use the same expression events and pattern + advancement. Coverage runs in the native sampling phase, with no event queue + or language callback. - `src/core`: signal storage/value operations, backend bindings, ports, clocks, coroutines and configuration. YAML parsing stays in the configuration source; third-party YAML headers are not part of the public header dependency graph. @@ -42,9 +46,36 @@ Update includes directly; removed paths do not have forwarding headers: `xinstance.h` and its `test_xdata()` helper are removed from the production SDK; that test implementation is compiled into `test_xdata` instead. Python-only -third-call declarations live beside the Python wrapper. Existing runtime class -layouts and Python public methods are retained; source clients must update the -includes above and rebuild bindings as needed. +third-call declarations live beside the Python wrapper. Existing signal/clock +layouts and callable interfaces are retained. Coverage +descriptors have expanded, so native clients must rebuild against ABI 3; +source clients must also update the includes above. + +## Trigger programs and coverage + +Language frontends lower declarations to expression roots, sequence steps or +FSM transitions. A value point observes an XData signal; a pattern point sets +`XCoverageItem.pattern` and organizes complete trigger programs without a +synthetic signal. Pattern bins select Expr, Sequence or FSM with +`XCoverageBin.program_kind` (0, 1 or 2), and specify event mode, bounded overlap +and optional FSM terminals. `CoverageVersion() == 4` identifies this descriptor +protocol; the native library ABI is independently versioned as 3. + +Equivalent programs within one point share an execution when their roots, +steps/transitions, event mode and overlap capacity agree. Terminal selection, +bin kind and coverage thresholds do not change execution. Each bin retains its +own count, and different points or registrations retain independent history. +`CoverageExecutionCount(handle)` counts these point-local pattern executions. + +Each group is registered on one clock/phase. Pattern completion counts are +preserved, including multiple terminals completing in one sample. Unselected +FSM terminals still end their attempts. Closing a gate clears the affected bin +history; abort clears source and bin history. Reset can preserve counters while +clearing execution history. Pattern ignore/illegal bins affect their own +completion results; value points retain their classification priority rules. +Crosses containing pattern points are rejected until completion correlation +semantics are defined. Program reclamation during a long execution and complete +expression width/signed/cast/four-state semantics remain separate work. ## TLM source SDK diff --git a/docs/maintainer-guidance.md b/docs/maintainer-guidance.md index 30e9ecc..4144bc8 100644 --- a/docs/maintainer-guidance.md +++ b/docs/maintainer-guidance.md @@ -56,14 +56,15 @@ extensions; a Python version specifier alone cannot verify C++ ABI identity. ## Migrating to the native trigger engine The native trigger engine changes the C++ layouts of `XClock`, `XData`, and -`ExprNode`, so this release uses native ABI 2 (`libxspcomm.so.2`). Rebuild -Picker-generated DUT extensions and other native bindings against the new -headers and library. Keep each generated DUT and its runtime on a matching -native ABI; changing a library symlink does not make an ABI 1 binary compatible. +`ExprNode`, which introduced native ABI 2. Complete per-bin trigger programs now +expand `XCoverageBin`, so the current native ABI is 3 (`libxspcomm.so.3`). Rebuild +Picker-generated DUT extensions and other native bindings against these headers +and library, and update the consumer's ABI requirement together. Keep each +generated DUT and its runtime on a matching native ABI. -`XTriggerEngine.CoverageVersion() == 3` identifies the coverage snapshot -protocol. It is separate from the native library ABI and the Python package -version. The Python `XPin` wrapper is removed; use `XData` directly instead. +`XTriggerEngine.CoverageVersion() == 4` identifies the complete trigger-bin +descriptor protocol. It is separate from the native library ABI and the Python +package version. The Python `XPin` wrapper is removed; use `XData` directly instead. Picker 2.0.1 restricts xspcomm to `<0.2`. Before releasing Picker with an xspcomm `0.2.x` package, update its supported dependency range and rebuild its diff --git a/include/xspcomm/detail/pattern.h b/include/xspcomm/detail/pattern.h index f3fae7c..256b4d0 100644 --- a/include/xspcomm/detail/pattern.h +++ b/include/xspcomm/detail/pattern.h @@ -23,6 +23,8 @@ struct PatternView { const std::vector *sequence = nullptr; const std::vector *fsm = nullptr; uint32_t start_state = 0; + int root = -1; + XConditionMode mode = XConditionMode::Enter; }; bool AdvanceSequence(const std::vector &steps, diff --git a/include/xspcomm/xcoverage.h b/include/xspcomm/xcoverage.h index faa5633..cfc01cf 100644 --- a/include/xspcomm/xcoverage.h +++ b/include/xspcomm/xcoverage.h @@ -14,7 +14,8 @@ namespace detail { class CoverageState; } // Coverage uses numeric IDs in the hot path. Human names live in the client. struct XCoverageItem { int gate = -1; - XData *signal = nullptr; // direct source, any XData width; null for a cross + bool pattern = false; // event point; no synthetic XData source + XData *signal = nullptr; // direct value source; null for a cross or pattern point std::vector dimensions; // point IDs; empty for a point }; @@ -24,6 +25,12 @@ struct XCoverageBin { int root = -1; std::vector steps; bool overlap = true; + uint32_t program_kind = 0; // Expr=0, Sequence=1, FSM=2 (pattern points) + XConditionMode mode = XConditionMode::Enter; + uint32_t max_active = 1; + uint32_t state_count = 0, start_state = 0; + std::vector transitions; + std::vector terminals; // empty selects all std::vector dimensions; // normal bin IDs for a cross tuple }; diff --git a/include/xspcomm/xpattern.h b/include/xspcomm/xpattern.h index 58cfe00..905164b 100644 --- a/include/xspcomm/xpattern.h +++ b/include/xspcomm/xpattern.h @@ -5,6 +5,12 @@ namespace xspcomm { +enum class XConditionMode : uint8_t { + Enter = 0, + EachSample = 1, + Change = 2, +}; + enum class XSequenceStepKind : uint8_t { Wait = 0, Within = 1, diff --git a/include/xspcomm/xtrigger.h b/include/xspcomm/xtrigger.h index d619ff0..45951e0 100644 --- a/include/xspcomm/xtrigger.h +++ b/include/xspcomm/xtrigger.h @@ -38,12 +38,6 @@ enum class XStopReason : uint8_t { BackendError = 8, }; -enum class XConditionMode : uint8_t { - Enter = 0, - EachSample = 1, - Change = 2, -}; - struct XRegistrationHandle { uint32_t slot = std::numeric_limits::max(); uint32_t generation = 0; @@ -190,7 +184,7 @@ class XTriggerEngine { uint32_t state_count, uint32_t start_state, const std::vector &transitions, XPhase phase = XPhase::RisingStable, uint64_t source_id = 0); - static uint32_t CoverageVersion() { return 3; } + static uint32_t CoverageVersion() { return 4; } void AttachCoverage(XRegistrationHandle handle, const std::vector &items, const std::vector &bins, @@ -199,6 +193,7 @@ class XTriggerEngine { bool overlap = false, size_t max_active = 1, bool diagnostics = false); XCoverageSnapshot CoverageSnapshot(XRegistrationHandle handle, bool progress = false) const; void ResetCoverage(XRegistrationHandle handle, bool counters = true); + size_t CoverageExecutionCount(XRegistrationHandle handle) const; bool Disarm(XRegistrationHandle handle); bool Rearm(XRegistrationHandle handle); bool RearmSample(XRegistrationHandle handle); diff --git a/src/coverage/engine.cpp b/src/coverage/engine.cpp index 94bf80f..af005b3 100644 --- a/src/coverage/engine.cpp +++ b/src/coverage/engine.cpp @@ -3,15 +3,33 @@ #include #include +#include namespace xspcomm { namespace detail { +static bool SameCoverageExecution(const XCoverageBin &a, const XCoverageBin &b) +{ + if (std::tie(a.item, a.program_kind, a.root, a.mode, a.overlap, a.max_active, + a.state_count, a.start_state) != + std::tie(b.item, b.program_kind, b.root, b.mode, b.overlap, b.max_active, + b.state_count, b.start_state)) return false; + if (a.steps.size() != b.steps.size() || a.transitions.size() != b.transitions.size()) return false; + return std::equal(a.steps.begin(), a.steps.end(), b.steps.begin(), [](const auto &x, const auto &y) { + return std::tie(x.kind, x.root, x.minimum, x.maximum, x.cycles) == + std::tie(y.kind, y.root, y.minimum, y.maximum, y.cycles); + }) && std::equal(a.transitions.begin(), a.transitions.end(), b.transitions.begin(), [](const auto &x, const auto &y) { + return std::tie(x.from_state, x.root, x.next_state, x.terminal_id, x.trigger) == + std::tie(y.from_state, y.root, y.next_state, y.terminal_id, y.trigger); + }); +} + CoverageState::CoverageState(uint32_t generation, ExprEngine &expr, const PatternView &source, const std::vector &items, const std::vector &bins, int gate, int abort, bool raise_illegal, size_t diagnostic_capacity, bool overlap, size_t max_active, bool diagnostics) + : source_matcher(source) { if (items.empty() || bins.empty() || diagnostic_capacity == 0) throw std::invalid_argument("empty coverage definition or diagnostic capacity"); @@ -28,19 +46,51 @@ CoverageState::CoverageState(uint32_t generation, ExprEngine &expr, const Patter check(gate); check(abort); for (size_t i = 0; i < items.size(); ++i) { check(items[i].gate); - if (items[i].dimensions.empty() != (items[i].signal != nullptr)) + if (items[i].pattern && (items[i].signal || !items[i].dimensions.empty())) + throw std::invalid_argument("pattern point cannot have a signal or dimensions"); + if (!items[i].pattern && items[i].dimensions.empty() != (items[i].signal != nullptr)) throw std::invalid_argument("coverage point requires a signal; cross must not have one"); for (auto d : items[i].dimensions) - if (d >= i || !items[d].dimensions.empty()) throw std::invalid_argument("invalid cross point"); + if (d >= i || !items[d].dimensions.empty() || items[d].pattern) throw std::invalid_argument("invalid cross point"); } for (const auto &bin : bins) { if (bin.item >= items.size() || bin.kind > 3) throw std::invalid_argument("invalid coverage bin"); check(bin.root); + const bool pattern = items[bin.item].pattern; + if (pattern) { + if (bin.kind == 3 || bin.program_kind > 2 || static_cast(bin.mode) > 2 || + !bin.max_active || (!bin.overlap && bin.max_active != 1)) + throw std::invalid_argument("invalid pattern bin options"); + if (bin.program_kind == 0 && (bin.root < 0 || !bin.steps.empty() || !bin.transitions.empty() || !bin.terminals.empty() || bin.overlap)) + throw std::invalid_argument("invalid expression pattern"); + if (bin.program_kind == 1 && (bin.root != -1 || bin.steps.empty() || !bin.transitions.empty() || !bin.terminals.empty())) + throw std::invalid_argument("invalid sequence pattern"); + if (bin.program_kind != 0 && bin.mode != XConditionMode::Enter) + throw std::invalid_argument("condition modes apply only to expression patterns"); + if (bin.program_kind == 2) { + if (bin.root != -1 || !bin.state_count || bin.start_state >= bin.state_count || !bin.steps.empty()) + throw std::invalid_argument("invalid FSM pattern"); + for (const auto &t : bin.transitions) { + check(t.root); + if (t.from_state >= bin.state_count || (!t.trigger && t.next_state >= bin.state_count)) + throw std::invalid_argument("invalid FSM transition"); + } + for (auto terminal : bin.terminals) + if (std::none_of(bin.transitions.begin(), bin.transitions.end(), [&](const auto &t) { return t.trigger && t.terminal_id == terminal; })) + throw std::invalid_argument("unknown FSM terminal"); + } + } for (const auto &step : bin.steps) { - if (step.root < 0 || (step.kind != XSequenceStepKind::Wait && step.kind != XSequenceStepKind::Next)) + if (step.root < 0 || (!pattern && step.kind != XSequenceStepKind::Wait && step.kind != XSequenceStepKind::Next)) throw std::invalid_argument("coverage transition requires adjacent steps"); + if (static_cast(step.kind) > 3 || + (step.kind == XSequenceStepKind::Within && step.minimum > step.maximum) || + (step.kind == XSequenceStepKind::Hold && !step.cycles)) + throw std::invalid_argument("invalid coverage sequence step"); check(step.root); } + if (pattern && bin.overlap && bin.program_kind == 1 && bin.steps.front().kind != XSequenceStepKind::Wait) + throw std::invalid_argument("overlapping Sequence must start with Wait"); const auto &dims = items[bin.item].dimensions; if (bin.dimensions.size() != dims.size()) throw std::invalid_argument("invalid cross tuple"); for (size_t j = 0; j < dims.size(); ++j) { @@ -58,68 +108,76 @@ CoverageState::CoverageState(uint32_t generation, ExprEngine &expr, const Patter snapshot.counters.resize(2 + items.size() * 5 + bins.size()); delta.resize(snapshot.counters.size()); matched.resize(bins.size()); normal_counts.resize(items.size()); - attempts.resize(bins.size()); - for (size_t i = 0; i < bins.size(); ++i) attempts[i].reserve(bins[i].steps.size()); + matchers.resize(bins.size()); + owners.resize(bins.size()); + for (size_t i = 0; i < bins.size(); ++i) { + owners[i] = i; + if (items[bins[i].item].pattern) { + for (size_t previous = 0; previous < i; ++previous) { + if (owners[previous] == previous && SameCoverageExecution(bins[i], bins[previous])) { + owners[i] = previous; + break; + } + } + } + if (owners[i] != i) { + this->bins[i].steps.clear(); + this->bins[i].transitions.clear(); + continue; + } + const auto &bin = this->bins[i]; + PatternView program; + if (items[bin.item].pattern && bin.program_kind == 2) { + program.fsm = &bin.transitions; + program.start_state = bin.start_state; + } else if (!bin.steps.empty()) program.sequence = &bin.steps; + else { + program.root = bin.root; + program.mode = bin.mode; + } + matchers[i] = PatternMatcher(program); + } snapshot.illegal_bins.reserve(diagnostic_capacity); snapshot.illegal_values.reserve(diagnostic_capacity); snapshot.illegal_ticks.reserve(diagnostic_capacity); } -void CoverageState::SamplePattern(ExprEngine &expr, const PatternView &source, uint64_t tick) +void CoverageState::SamplePattern(ExprEngine &expr, uint64_t tick) { - const auto completed = AdvanceAttempts(expr, source, source_attempts, 0, overlap, max_active); + const auto completed = Advance(expr, source_matcher, 0, overlap, max_active); for (size_t i = 0; i < completed; ++i) Sample(expr, tick); } -size_t CoverageState::AdvanceAttempts(ExprEngine &expr, const PatternView &program, - std::vector &attempts, size_t pattern, bool overlap, size_t max_active) +inline size_t CoverageState::Advance(ExprEngine &expr, PatternMatcher &matcher, + size_t pattern, bool overlap, size_t max_active) { - const bool fsm = program.fsm != nullptr; - const bool had_active = !attempts.empty(); - auto advance = [&](PatternState &state) { - uint32_t terminal = 0; - return fsm ? AdvanceFsm(*program.fsm, state, expr, terminal) : AdvanceSequence(*program.sequence, state, expr); - }; - size_t completed = 0; - size_t kept = 0; - for (size_t i = 0; i < attempts.size(); ++i) { - auto &state = attempts[i]; - const bool done = advance(state); - const bool failed = fsm ? (!done && state.fsm_current_state == program.start_state) - : state.sequence_failed; - if (done) { ++completed; Bump(pattern, CoverageState::Completed); } - else if (failed) Bump(pattern, state.sequence_expired ? CoverageState::Expired : CoverageState::Failed); - if (!done && !failed) { - if (kept != i) attempts[kept] = state; - ++kept; - } + const auto update = matcher.Advance(expr, overlap, max_active); + Bump(pattern, Started, update.started); + Bump(pattern, Completed, update.completed); + Bump(pattern, Failed, update.failed); + Bump(pattern, Expired, update.expired); + Bump(pattern, PeakActive, update.peak_active); + if (update.capacity_exhausted) { + snapshot.incomplete = true; + throw std::overflow_error("coverage active pattern capacity exhausted"); } - attempts.resize(kept); - if (overlap || !had_active) { - PatternState candidate; - candidate.fsm_current_state = program.start_state; - const bool done = advance(candidate); - const bool active = fsm ? candidate.fsm_current_state != program.start_state - : candidate.sequence_index || candidate.sequence_age || candidate.sequence_held; - if (done || active) { - if (attempts.size() >= max_active) { - snapshot.incomplete = true; - throw std::overflow_error("coverage active pattern capacity exhausted"); - } - Bump(pattern, CoverageState::Started); - Bump(pattern, CoverageState::PeakActive, attempts.size() + 1); - if (done) { ++completed; Bump(pattern, CoverageState::Completed); } - else attempts.push_back(candidate); - } - } - return completed; + return update.completed; +} + +size_t CoverageState::ExecutionCount() const +{ + size_t count = 0; + for (size_t b = 0; b < bins.size(); ++b) + count += items[bins[b].item].pattern && owners[b] == b; + return count; } void CoverageState::ClearHistory(bool aborted) { const auto reason = aborted ? CoverageState::Aborted : CoverageState::Cleared; - ClearAttempts(0, source_attempts, reason); - for (size_t b = 0; b < attempts.size(); ++b) ClearAttempts(b + 1, attempts[b], reason); + ClearMatcher(0, source_matcher, reason); + for (size_t b = 0; b < matchers.size(); ++b) + if (owners[b] == b) ClearMatcher(b + 1, matchers[b], reason); } void CoverageState::ResetCounters() @@ -139,13 +197,18 @@ XCoverageSnapshot CoverageState::Snapshot(uint64_t tick, bool progress) const { auto snapshot = this->snapshot; snapshot.tick = tick; + if (!snapshot.diagnostics.empty()) { + for (size_t b = 0; b < bins.size(); ++b) if (owners[b] != b) + std::copy_n(snapshot.diagnostics.begin() + (owners[b] + 1) * DiagnosticCount, + DiagnosticCount, snapshot.diagnostics.begin() + (b + 1) * DiagnosticCount); + } if (progress) { auto append = [&](size_t id, const std::vector &states) { for (const auto &s : states) snapshot.progress.insert(snapshot.progress.end(), { id, s.sequence_index, s.sequence_age, s.sequence_held, s.fsm_current_state}); }; - append(0, source_attempts); - for (size_t b = 0; b < attempts.size(); ++b) append(b + 1, attempts[b]); + append(0, source_matcher.States()); + for (size_t b = 0; b < matchers.size(); ++b) append(b + 1, matchers[owners[b]].States()); } return snapshot; } @@ -159,7 +222,8 @@ void CoverageState::Sample(ExprEngine &expr, uint64_t tick) auto enabled = [&](int r) { return r < 0 || (expr.IsKnown(r) && expr.Eval(r)); }; if (!enabled(gate)) { delta[1] = 1; - for (size_t b = 0; b < attempts.size(); ++b) ClearAttempts(b + 1, attempts[b], CoverageState::Cleared); + for (size_t b = 0; b < matchers.size(); ++b) + if (owners[b] == b) ClearMatcher(b + 1, matchers[b], Cleared); } else { delta[0] = 1; for (size_t i = 0; i < items.size(); ++i) { @@ -171,8 +235,8 @@ void CoverageState::Sample(ExprEngine &expr, uint64_t tick) if (!gate || !known) { delta[base + 1] = 1; if (gate && !known) delta[base + 4] = 1; - for (size_t b = 0; b < bins.size(); ++b) if (bins[b].item == i) - ClearAttempts(b + 1, attempts[b], CoverageState::Cleared); + for (size_t b = 0; b < bins.size(); ++b) + if (bins[b].item == i && owners[b] == b) ClearMatcher(b + 1, matchers[b], Cleared); continue; } delta[base] = 1; @@ -196,6 +260,24 @@ void CoverageState::Sample(ExprEngine &expr, uint64_t tick) delta[base + 3] = combinations ? combinations - hits : 1; continue; } + if (item.pattern) { + bool any = false; + for (size_t b = 0; b < bins.size(); ++b) { + const auto &bin = bins[b]; + if (bin.item != i) continue; + auto &matcher = matchers[owners[b]]; + if (owners[b] == b) { + if (bin.program_kind == 0) matcher.Advance(expr); + else Advance(expr, matcher, b + 1, bin.overlap, bin.max_active); + } + const auto completed = matcher.Count(bin.terminals); + delta[offset + b] = completed; + any = any || completed; + if (bin.kind == 1 && completed) delta[base + 2] = 1; + } + if (!any) delta[base + 3] = 1; + continue; + } int priority = -1; for (size_t b = 0; b < bins.size(); ++b) { const auto &bin = bins[b]; @@ -203,8 +285,7 @@ void CoverageState::Sample(ExprEngine &expr, uint64_t tick) bool hit = false; if (bin.steps.empty()) hit = enabled(bin.root); else { - hit = AdvanceAttempts(expr, {&bin.steps}, attempts[b], - b + 1, bin.overlap, bin.steps.size()) != 0; + hit = Advance(expr, matchers[b], b + 1, bin.overlap, bin.steps.size()) != 0; } matched[b] = hit; if (hit) priority = std::max(priority, static_cast(bin.kind)); diff --git a/src/coverage/state.h b/src/coverage/state.h index 29bf324..2b9480f 100644 --- a/src/coverage/state.h +++ b/src/coverage/state.h @@ -2,7 +2,7 @@ #define XSPCOMM_COVERAGE_STATE_H #include "xspcomm/xcoverage.h" -#include "xspcomm/detail/pattern.h" +#include "../trigger/matcher.h" #include "xspcomm/xexpr.h" #include #include @@ -10,7 +10,7 @@ namespace xspcomm::detail { -// Coverage owns its sampling history and counters. No trigger types are needed. +// Coverage applies statistical policies to executions provided by trigger matchers. class CoverageState { public: enum class SampleStatus { Ready, Skip, Aborted }; @@ -31,19 +31,20 @@ class CoverageState { return SampleStatus::Ready; } void Sample(ExprEngine &expr, uint64_t tick); - void SamplePattern(ExprEngine &expr, const PatternView &source, uint64_t tick); + void SamplePattern(ExprEngine &expr, uint64_t tick); + size_t ExecutionCount() const; void ClearHistory(bool aborted = false); void ResetCounters(); XCoverageSnapshot Snapshot(uint64_t tick, bool progress) const; private: - size_t AdvanceAttempts(ExprEngine &expr, const PatternView &program, - std::vector &attempts, size_t pattern, - bool overlap, size_t max_active); + size_t Advance(ExprEngine &expr, PatternMatcher &matcher, + size_t pattern, bool overlap, size_t max_active); std::vector items; std::vector bins; - std::vector> attempts; - std::vector source_attempts; + PatternMatcher source_matcher; + std::vector matchers; + std::vector owners; bool overlap = false; size_t max_active = 1; enum { Started, Completed, Failed, Expired, Aborted, Cleared, PeakActive, DiagnosticCount }; @@ -57,9 +58,9 @@ class CoverageState { } count += amount; } - void ClearAttempts(size_t pattern, std::vector &states, size_t reason) { - Bump(pattern, reason, states.size()); - states.clear(); + void ClearMatcher(size_t pattern, PatternMatcher &matcher, size_t reason) { + Bump(pattern, reason, matcher.States().size()); + matcher.Clear(); } std::vector delta, normal_counts; std::vector matched; diff --git a/src/trigger/coverage.cpp b/src/trigger/coverage.cpp index 098d709..37f8e2c 100644 --- a/src/trigger/coverage.cpp +++ b/src/trigger/coverage.cpp @@ -45,4 +45,10 @@ XCoverageSnapshot XTriggerEngine::CoverageSnapshot(XRegistrationHandle handle, b return watcher.coverage->Snapshot(clock->GetHalfTick(), progress); } +size_t XTriggerEngine::CoverageExecutionCount(XRegistrationHandle handle) const +{ + CheckCoverageHandle(handle); + return watchers[handle.slot].coverage->ExecutionCount(); +} + } // namespace xspcomm diff --git a/src/trigger/engine.cpp b/src/trigger/engine.cpp index 26ce6c4..e86fd3a 100644 --- a/src/trigger/engine.cpp +++ b/src/trigger/engine.cpp @@ -1,6 +1,7 @@ #include "xspcomm/xtrigger.h" #include "xspcomm/xexpr.h" #include "../coverage/state.h" +#include "matcher.h" #include #include @@ -400,7 +401,7 @@ inline bool XTriggerEngine::EvaluateCoverage(Watcher &watcher) } const auto source = CoverageSource(watcher); if (source.sequence || source.fsm) { - coverage.SamplePattern(*expr_engine, source, clock->GetHalfTick()); + coverage.SamplePattern(*expr_engine, clock->GetHalfTick()); return true; } return false; @@ -444,13 +445,8 @@ void XTriggerEngine::EvaluatePhase(XPhase phase) (watcher.expected_wide ? watcher.signal->Equal(*watcher.expected_wide) : value == watcher.expected); - const bool emit = known && ( - watcher.condition_mode == XConditionMode::EachSample - ? current - : watcher.condition_mode == XConditionMode::Enter - ? current && !watcher.last_condition - : current != watcher.last_condition); - if (known) watcher.last_condition = current; + const bool emit = detail::MatchCondition(known, current, + watcher.condition_mode, watcher.last_condition); if (emit) { AppendHit(slot, watcher, XHitKind::Value, value); } @@ -486,26 +482,22 @@ void XTriggerEngine::EvaluatePhase(XPhase phase) const bool known = expr_engine->IsKnown(watcher.expr_root); const bool current = known && expr_engine->Eval(watcher.expr_root) != 0; - const bool emit = known && ( - watcher.condition_mode == XConditionMode::EachSample - ? current - : watcher.condition_mode == XConditionMode::Enter - ? current && !watcher.last_condition - : current != watcher.last_condition); - if (known) watcher.last_condition = current; + const bool emit = detail::MatchCondition(known, current, + watcher.condition_mode, watcher.last_condition); if (emit) { AppendHit(slot, watcher, XHitKind::Condition, current ? 1 : 0); } break; } case WatcherKind::Sequence: - if (detail::AdvanceSequence(watcher.sequence_steps, watcher, *expr_engine)) { + if (detail::AdvancePattern({&watcher.sequence_steps}, watcher, *expr_engine).completed) { AppendHit(slot, watcher, XHitKind::Fsm, 1); } break; case WatcherKind::Fsm: { - uint32_t terminal = 0; - if (detail::AdvanceFsm(watcher.fsm_transitions, watcher, *expr_engine, terminal)) AppendHit(slot, watcher, XHitKind::Fsm, terminal); + const auto result = detail::AdvancePattern( + {nullptr, &watcher.fsm_transitions, watcher.fsm_start_state}, watcher, *expr_engine); + if (result.completed) AppendHit(slot, watcher, XHitKind::Fsm, result.terminal); break; } } diff --git a/src/trigger/matcher.cpp b/src/trigger/matcher.cpp new file mode 100644 index 0000000..6b2ce01 --- /dev/null +++ b/src/trigger/matcher.cpp @@ -0,0 +1,87 @@ +#include "matcher.h" +#include "xspcomm/xexpr.h" + +#include +#include + +namespace xspcomm::detail { + +PatternMatcher::PatternMatcher(PatternView program) : program(program) +{ + if (program.sequence) attempts.reserve(program.sequence->size()); + if (program.fsm) { + for (const auto &transition : *program.fsm) { + if (transition.trigger && + std::find(terminal_ids.begin(), terminal_ids.end(), transition.terminal_id) == terminal_ids.end()) + terminal_ids.push_back(transition.terminal_id); + } + terminal_counts.resize(terminal_ids.size()); + } +} + +MatchUpdate PatternMatcher::Advance(ExprEngine &expr, bool overlap, size_t max_active) +{ + MatchUpdate update; + completed = 0; + std::fill(terminal_counts.begin(), terminal_counts.end(), 0); + if (!program.sequence && !program.fsm) { + const bool known = expr.IsKnown(program.root); + const bool current = known && expr.Eval(program.root) != 0; + completed = MatchCondition(known, current, program.mode, last_condition); + update.completed = completed; + return update; + } + auto record = [&](uint32_t terminal) { + ++completed; + ++update.completed; + if (program.fsm) { + const auto found = std::find(terminal_ids.begin(), terminal_ids.end(), terminal); + if (found == terminal_ids.end()) throw std::logic_error("unknown trigger completion terminal"); + ++terminal_counts[found - terminal_ids.begin()]; + } + }; + const bool had_active = !attempts.empty(); + size_t kept = 0; + for (size_t i = 0; i < attempts.size(); ++i) { + auto &state = attempts[i]; + const auto result = AdvancePattern(program, state, expr); + if (result.completed) record(result.terminal); + else if (result.failed) { + if (state.sequence_expired) ++update.expired; + else ++update.failed; + } + if (!result.completed && !result.failed) { + if (kept != i) attempts[kept] = state; + ++kept; + } + } + attempts.resize(kept); + if (overlap || !had_active) { + PatternState candidate; + candidate.fsm_current_state = program.start_state; + const auto result = AdvancePattern(program, candidate, expr); + const bool active = program.fsm ? candidate.fsm_current_state != program.start_state : + candidate.sequence_index || candidate.sequence_age || candidate.sequence_held; + if (result.completed || active) { + if (attempts.size() >= max_active) { + update.capacity_exhausted = true; + return update; + } + ++update.started; + update.peak_active = attempts.size() + 1; + if (result.completed) record(result.terminal); + else attempts.push_back(candidate); + } + } + return update; +} + +void PatternMatcher::Clear() +{ + attempts.clear(); + last_condition = false; + completed = 0; + std::fill(terminal_counts.begin(), terminal_counts.end(), 0); +} + +} // namespace xspcomm::detail diff --git a/src/trigger/matcher.h b/src/trigger/matcher.h new file mode 100644 index 0000000..7c48f99 --- /dev/null +++ b/src/trigger/matcher.h @@ -0,0 +1,75 @@ +#ifndef XSPCOMM_TRIGGER_MATCHER_H +#define XSPCOMM_TRIGGER_MATCHER_H + +#include "xspcomm/detail/pattern.h" +#include + +namespace xspcomm::detail { + +inline bool MatchCondition(bool known, bool current, XConditionMode mode, bool &previous) +{ + if (!known) return false; + const bool hit = mode == XConditionMode::EachSample ? current : + mode == XConditionMode::Enter ? current && !previous : current != previous; + previous = current; + return hit; +} + +struct PatternResult { + bool completed = false; + bool failed = false; + uint32_t terminal = 0; +}; + +inline PatternResult AdvancePattern(const PatternView &program, PatternState &state, ExprEngine &expr) +{ + PatternResult result; + if (program.fsm) { + result.completed = AdvanceFsm(*program.fsm, state, expr, result.terminal); + result.failed = !result.completed && state.fsm_current_state == program.start_state; + } else { + result.completed = AdvanceSequence(*program.sequence, state, expr); + result.failed = state.sequence_failed; + } + return result; +} + +struct MatchUpdate { + size_t started = 0; + size_t completed = 0; + size_t failed = 0; + size_t expired = 0; + size_t peak_active = 0; + bool capacity_exhausted = false; +}; + +// One execution of a borrowed program. Other executions have independent history. +class PatternMatcher { +public: + explicit PatternMatcher(PatternView program = {}); + MatchUpdate Advance(ExprEngine &expr, bool overlap = false, size_t max_active = 1); + // Counts from the latest sample, optionally restricted to selected FSM terminals. + uint64_t Count(const std::vector &terminals = {}) const + { + if (terminals.empty()) return completed; + uint64_t count = 0; + for (size_t i = 0; i < terminal_ids.size(); ++i) + if (std::find(terminals.begin(), terminals.end(), terminal_ids[i]) != terminals.end()) + count += terminal_counts[i]; + return count; + } + const std::vector &States() const { return attempts; } + void Clear(); + +private: + PatternView program; + std::vector attempts; + std::vector terminal_ids; + std::vector terminal_counts; + uint64_t completed = 0; + bool last_condition = false; +}; + +} // namespace xspcomm::detail + +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4b5edf3..d227db8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,5 +1,5 @@ if(NOT XSPCOMM_WASM_LIB) - foreach(component IN ITEMS xdata xclock xtrigger xcoverage xcomuse xexpr xvpi xfsm) + foreach(component IN ITEMS xdata xclock xtrigger xcoverage xcomuse xexpr xvpi xfsm trigger_matcher) add_executable(test_${component} unit/test_${component}.cpp) target_include_directories(test_${component} PRIVATE ${PROJECT_SOURCE_DIR}/third_party/catch2 diff --git a/tests/unit/test_coverage_core.cpp b/tests/unit/test_coverage_core.cpp index c194240..e493eca 100644 --- a/tests/unit/test_coverage_core.cpp +++ b/tests/unit/test_coverage_core.cpp @@ -62,7 +62,7 @@ TEST_CASE("Coverage abort clears overlapping pattern history", "[coverage][patte for (uint64_t tick = 1; tick <= 2; ++tick) { expr.SetCycle(tick); REQUIRE(coverage.BeginSample(expr, tick) == CoverageState::SampleStatus::Ready); - coverage.SamplePattern(expr, source, tick); + coverage.SamplePattern(expr, tick); } REQUIRE(coverage.Snapshot(2, true).progress.size() == 10); abort = 1; @@ -75,6 +75,6 @@ TEST_CASE("Coverage abort clears overlapping pattern history", "[coverage][patte done = 1; abort = 0; REQUIRE(coverage.BeginSample(expr, 4) == CoverageState::SampleStatus::Ready); - coverage.SamplePattern(expr, source, 4); + coverage.SamplePattern(expr, 4); REQUIRE(coverage.Snapshot(4, true).counters[0] == 0); } diff --git a/tests/unit/test_trigger_matcher.cpp b/tests/unit/test_trigger_matcher.cpp new file mode 100644 index 0000000..f9ab35a --- /dev/null +++ b/tests/unit/test_trigger_matcher.cpp @@ -0,0 +1,133 @@ +#define CATCH_CONFIG_MAIN +#include "catch.hpp" + +#include "trigger/matcher.h" +#include "xspcomm/xexpr.h" + +using namespace xspcomm; +using detail::PatternMatcher; +using detail::PatternView; + +TEST_CASE("Trigger matchers retain expression event modes across unknown samples", "[matcher]") { + ExprEngine expr; + XData value(1, XData::InOut); + PatternView program; + program.root = expr.NewSignal(&value); + PatternMatcher enter(program); + program.mode = XConditionMode::EachSample; + PatternMatcher each(program); + program.mode = XConditionMode::Change; + PatternMatcher change(program); + + const unsigned samples[] = {0, 1, 1, 0, 1}; + const unsigned enters[] = {0, 1, 0, 0, 1}; + const unsigned changes[] = {0, 1, 0, 1, 1}; + for (size_t i = 0; i < 5; ++i) { + value = samples[i]; + REQUIRE(enter.Advance(expr).completed == enters[i]); + REQUIRE(each.Advance(expr).completed == samples[i]); + REQUIRE(change.Advance(expr).completed == changes[i]); + } + value = "x"; + REQUIRE(enter.Advance(expr).completed == 0); + REQUIRE(each.Advance(expr).completed == 0); + REQUIRE(change.Advance(expr).completed == 0); + value = 1; + REQUIRE(enter.Advance(expr).completed == 0); + REQUIRE(each.Advance(expr).completed == 1); + REQUIRE(change.Advance(expr).completed == 0); + enter.Clear(); + REQUIRE(enter.Advance(expr).completed == 1); +} + +TEST_CASE("Trigger executions share a program without sharing history", "[matcher]") { + ExprEngine expr; + XData start(1, XData::InOut), done(1, XData::InOut); + const std::vector steps = { + {XSequenceStepKind::Wait, expr.NewSignal(&start)}, + {XSequenceStepKind::Within, expr.NewSignal(&done), 1, 3}}; + PatternMatcher first({&steps}), second({&steps}); + start = 1; + done = 0; + REQUIRE(first.Advance(expr).started == 1); + REQUIRE(first.States().size() == 1); + REQUIRE(second.States().empty()); + start = 0; + done = 1; + REQUIRE(first.Advance(expr).completed == 1); + REQUIRE(second.Advance(expr).completed == 0); + REQUIRE(first.States().empty()); +} + +TEST_CASE("Trigger overlap reports capacity exhaustion and retains pending executions", "[matcher]") { + ExprEngine expr; + XData start(1, XData::InOut), done(1, XData::InOut); + const std::vector steps = { + {XSequenceStepKind::Wait, expr.NewSignal(&start)}, + {XSequenceStepKind::Within, expr.NewSignal(&done), 1, 5}}; + PatternMatcher matcher({&steps}); + start = 1; + done = 0; + REQUIRE(matcher.Advance(expr, true, 2).peak_active == 1); + REQUIRE(matcher.Advance(expr, true, 2).peak_active == 2); + const auto exhausted = matcher.Advance(expr, true, 2); + REQUIRE(exhausted.capacity_exhausted); + REQUIRE(exhausted.started == 0); + REQUIRE(matcher.States().size() == 2); + matcher.Clear(); + REQUIRE(matcher.States().empty()); + start = 0; + done = 1; + REQUIRE(matcher.Advance(expr, true, 2).completed == 0); +} + +TEST_CASE("Trigger FSM execution preserves completion counts for every terminal", "[matcher]") { + ExprEngine expr; + XData request(1, XData::InOut), response(1, XData::InOut); + const auto req = expr.NewSignal(&request), rsp = expr.NewSignal(&response); + const std::vector transitions = { + {0, req, 1, 0, false}, {1, rsp, 0, 7, true}, + {1, -1, 2, 0, false}, {2, rsp, 0, 8, true}}; + PatternMatcher matcher({nullptr, &transitions, 0}); + request = 1; + response = 0; + for (unsigned i = 0; i < 3; ++i) { + REQUIRE(matcher.Advance(expr, true, 3).completed == 0); + REQUIRE(matcher.States().size() == i + 1); + } + request = 0; + response = 1; + REQUIRE(matcher.Advance(expr, true, 3).completed == 3); + REQUIRE(matcher.Count({7}) == 1); + REQUIRE(matcher.Count({8}) == 2); + REQUIRE(matcher.Count({7, 8}) == 3); + REQUIRE(matcher.Count() == 3); + REQUIRE(matcher.States().empty()); + matcher.Clear(); + REQUIRE(matcher.Count() == 0); +} + +TEST_CASE("Trigger sequence expiry is separate from a failed adjacent step", "[matcher]") { + ExprEngine expr; + XData start(1, XData::InOut), done(1, XData::InOut); + const auto req = expr.NewSignal(&start), ack = expr.NewSignal(&done); + const std::vector timed = { + {XSequenceStepKind::Wait, req}, {XSequenceStepKind::Within, ack, 1, 1}}; + const std::vector adjacent = { + {XSequenceStepKind::Wait, req}, {XSequenceStepKind::Next, ack}}; + PatternMatcher window({&timed}), next({&adjacent}); + start = 1; + done = 0; + REQUIRE(window.Advance(expr).started == 1); + REQUIRE(next.Advance(expr).started == 1); + start = 0; + REQUIRE(window.Advance(expr).expired == 0); + const auto failed = next.Advance(expr); + REQUIRE(failed.failed == 1); + REQUIRE(failed.expired == 0); + const auto expired = window.Advance(expr); + REQUIRE(expired.expired == 1); + REQUIRE(expired.failed == 0); + REQUIRE(window.States().empty()); + REQUIRE(next.States().empty()); +} diff --git a/tests/unit/test_xcoverage.cpp b/tests/unit/test_xcoverage.cpp index 9b1aae7..2fefcaa 100644 --- a/tests/unit/test_xcoverage.cpp +++ b/tests/unit/test_xcoverage.cpp @@ -162,3 +162,183 @@ TEST_CASE("Coverage overlap capacity failure preserves pending state for cleanup engine.ClearExecutionState(); REQUIRE(engine.ActiveCount() == 0); } + +TEST_CASE("Coverage bins run full independent temporal programs", "[xtrigger][coverage]") { + XClock clock([](bool) { return 0; }); + XTriggerEngine engine(clock); + XData start(1, XData::InOut), done(1, XData::InOut), stable(1, XData::InOut); + const auto req = engine.ExprNewSignal(&start), ack = engine.ExprNewSignal(&done); + const auto ready = engine.ExprNewSignal(&stable); + auto handle = engine.ArmSample(XPhase::RisingStable); + XCoverageItem item; item.pattern = true; + XCoverageBin long_bin; long_bin.program_kind = 1; long_bin.overlap = false; + long_bin.steps = {{XSequenceStepKind::Wait, req}, {XSequenceStepKind::Within, ack, 1, 4}, + {XSequenceStepKind::Hold, ready, 0, 0, 2}}; + auto short_bin = long_bin; short_bin.steps[1].maximum = 1; + engine.AttachCoverage(handle, {item}, {long_bin, short_bin}); + start = 1; REQUIRE(engine.RunUntil(2).hits.empty()); + start = 0; REQUIRE(engine.RunUntil(2).hits.empty()); + done = 1; REQUIRE(engine.RunUntil(2).hits.empty()); + done = 0; stable = 1; + REQUIRE(engine.RunUntil(4).hits.empty()); + const auto snapshot = engine.CoverageSnapshot(handle); + REQUIRE(snapshot.counters[0] == 5); + REQUIRE(snapshot.counters[7] == 1); + REQUIRE(snapshot.counters[8] == 0); + REQUIRE(engine.Disarm(handle)); +} + +TEST_CASE("Coverage FSM terminal filtering ends every terminal attempt", "[xtrigger][coverage]") { + XClock clock([](bool) { return 0; }); + XTriggerEngine engine(clock); + XData start(1, XData::InOut), success(1, XData::InOut), failure(1, XData::InOut); + XCoverageItem item; item.pattern = true; + XCoverageBin bin; bin.program_kind = 2; bin.overlap = false; bin.state_count = 2; + bin.transitions = {{0, engine.ExprNewSignal(&start), 1, 0, false}, + {1, engine.ExprNewSignal(&success), 0, 7, true}, + {1, engine.ExprNewSignal(&failure), 0, 8, true}}; + bin.terminals = {7}; + auto h = engine.ArmSample(XPhase::RisingStable); + engine.AttachCoverage(h, {item}, {bin}, -1, -1, true, 1024, false, 1, true); + start = 1; engine.RunUntil(2); + start = 0; failure = 1; engine.RunUntil(2); + auto failed = engine.CoverageSnapshot(h, true); + REQUIRE(failed.counters.back() == 0); + REQUIRE(failed.progress.empty()); + REQUIRE(failed.diagnostics[8] == 1); // Bin completed even though its terminal was unselected. + failure = 0; success = 1; engine.RunUntil(2); + REQUIRE(engine.CoverageSnapshot(h).counters.back() == 0); + start = 1; success = 0; engine.RunUntil(2); + start = 0; success = 1; engine.RunUntil(2); + REQUIRE(engine.CoverageSnapshot(h).counters.back() == 1); + REQUIRE(engine.Disarm(h)); +} + +TEST_CASE("Coverage pattern overlap preserves simultaneous completion counts", "[xtrigger][coverage]") { + XClock clock([](bool) { return 0; }); + XTriggerEngine engine(clock); + XData start(1, XData::InOut), done(1, XData::InOut); + auto h = engine.ArmSample(XPhase::RisingStable); + XCoverageItem item; item.pattern = true; + XCoverageBin bin; bin.program_kind = 1; bin.overlap = true; bin.max_active = 3; + bin.steps = {{XSequenceStepKind::Wait, engine.ExprNewSignal(&start)}, + {XSequenceStepKind::Within, engine.ExprNewSignal(&done), 1, 5}}; + engine.AttachCoverage(h, {item}, {bin}); + start = 1; REQUIRE(engine.RunUntil(6).hits.empty()); + start = 0; done = 1; REQUIRE(engine.RunUntil(2).hits.empty()); + REQUIRE(engine.CoverageSnapshot(h).counters.back() == 3); + REQUIRE(engine.Disarm(h)); +} + +TEST_CASE("Coverage rejects malformed pattern descriptors atomically", "[xtrigger][coverage]") { + XClock clock([](bool) { return 0; }); + XTriggerEngine engine(clock); + auto h = engine.ArmSample(XPhase::RisingStable); + XCoverageItem item; item.pattern = true; + XCoverageBin bin; bin.overlap = false; bin.root = engine.ExprNewConst(1); + auto invalid = bin; invalid.mode = static_cast(99); + REQUIRE_THROWS(engine.AttachCoverage(h, {item}, {invalid})); + invalid = bin; invalid.max_active = 0; + REQUIRE_THROWS(engine.AttachCoverage(h, {item}, {invalid})); + invalid = bin; invalid.program_kind = 2; invalid.state_count = 1; + invalid.root = -1; invalid.terminals = {99}; + REQUIRE_THROWS(engine.AttachCoverage(h, {item}, {invalid})); + engine.AttachCoverage(h, {item}, {bin}); + engine.RunUntil(4); + REQUIRE(engine.CoverageSnapshot(h).counters.back() == 1); // ENTER, not EACH_SAMPLE. + REQUIRE(engine.Disarm(h)); +} + +TEST_CASE("Coverage shares an FSM and routes every terminal to its bins", "[xtrigger][coverage]") { + XClock clock([](bool) { return 0; }); + XTriggerEngine engine(clock); + XData request(1, XData::InOut), response(1, XData::InOut), correct(1, XData::InOut); + const auto req = engine.ExprNewSignal(&request), rsp = engine.ExprNewSignal(&response); + const auto good = engine.ExprNewSignal(&correct); + const auto ok = engine.ExprNewBinary(static_cast(ExprOp::LAND), rsp, good); + XCoverageItem item; item.pattern = true; + XCoverageBin first; first.program_kind = 2; first.state_count = 2; first.overlap = false; + first.transitions = {{0, req, 1, 0, false}, {1, ok, 0, 7, true}, {1, rsp, 0, 8, true}}; + first.terminals = {7}; + auto second = first; second.terminals = {8}; second.kind = 2; + auto all = first; all.terminals.clear(); + const auto handle = engine.ArmSample(XPhase::RisingStable); + engine.AttachCoverage(handle, {item}, {first, second, all}, -1, -1, false, 1024, false, 1, true); + REQUIRE(engine.ActiveCount() == 1); + REQUIRE(engine.CoverageExecutionCount(handle) == 1); + request = 1; engine.RunUntil(2); + auto pending = engine.CoverageSnapshot(handle, true); + REQUIRE(pending.progress.size() == 15); // Three bin views of the same pending observation. + request = 0; response = 1; correct = 1; engine.RunUntil(2); + auto success = engine.CoverageSnapshot(handle); + REQUIRE(success.counters[7] == 1); + REQUIRE(success.counters[8] == 0); + REQUIRE(success.counters[9] == 1); + request = 1; response = 0; engine.RunUntil(2); + request = 0; response = 1; correct = 0; engine.RunUntil(2); + auto failure = engine.CoverageSnapshot(handle, true); + REQUIRE(failure.counters[7] == 1); + REQUIRE(failure.counters[8] == 1); + REQUIRE(failure.counters[9] == 2); + REQUIRE(failure.illegal_bins.size() == 1); + REQUIRE(failure.illegal_bins[0] == 1); + REQUIRE(failure.progress.empty()); + for (size_t bin = 0; bin < 3; ++bin) { + REQUIRE(failure.diagnostics[(bin + 1) * 7] == 2); + REQUIRE(failure.diagnostics[(bin + 1) * 7 + 1] == 2); + } + REQUIRE(engine.Disarm(handle)); +} + +TEST_CASE("Shared coverage FSM retains simultaneous counts for different terminals", "[xtrigger][coverage]") { + XClock clock([](bool) { return 0; }); + XTriggerEngine engine(clock); + XData request(1, XData::InOut), response(1, XData::InOut); + const auto req = engine.ExprNewSignal(&request), rsp = engine.ExprNewSignal(&response); + XCoverageItem item; item.pattern = true; + XCoverageBin fast; fast.program_kind = 2; fast.state_count = 3; + fast.overlap = true; fast.max_active = 3; + fast.transitions = {{0, req, 1, 0, false}, {1, rsp, 0, 7, true}, + {1, -1, 2, 0, false}, {2, rsp, 0, 8, true}}; + fast.terminals = {7}; + auto slow = fast; slow.terminals = {8}; + auto all = fast; all.terminals.clear(); + const auto handle = engine.ArmSample(XPhase::RisingStable); + engine.AttachCoverage(handle, {item}, {fast, slow, all}, -1, -1, true, 1024, false, 1, true); + REQUIRE(engine.CoverageExecutionCount(handle) == 1); + request = 1; REQUIRE(engine.RunUntil(6).hits.empty()); + request = 0; response = 1; REQUIRE(engine.RunUntil(2).hits.empty()); + const auto snapshot = engine.CoverageSnapshot(handle); + REQUIRE(snapshot.counters[7] == 1); + REQUIRE(snapshot.counters[8] == 2); + REQUIRE(snapshot.counters[9] == 3); + for (size_t bin = 0; bin < 3; ++bin) { + REQUIRE(snapshot.diagnostics[(bin + 1) * 7] == 3); + REQUIRE(snapshot.diagnostics[(bin + 1) * 7 + 1] == 3); + REQUIRE(snapshot.diagnostics[(bin + 1) * 7 + 6] == 3); + } + REQUIRE(engine.Disarm(handle)); +} + +TEST_CASE("Coverage shares only identical execution policies in the same point", "[xtrigger][coverage]") { + XClock clock([](bool) { return 0; }); + XTriggerEngine engine(clock); + XData request(1, XData::InOut), response(1, XData::InOut); + const auto req = engine.ExprNewSignal(&request), rsp = engine.ExprNewSignal(&response); + XCoverageItem item; item.pattern = true; + XCoverageBin original; original.program_kind = 1; original.overlap = false; + original.steps = {{XSequenceStepKind::Wait, req}, {XSequenceStepKind::Within, rsp, 1, 4}}; + auto alias = original; + auto other_point = original; other_point.item = 1; + auto short_window = original; short_window.steps[1].maximum = 2; + auto overlapping = original; overlapping.overlap = true; overlapping.max_active = 3; + auto h = engine.ArmSample(XPhase::RisingStable); + engine.AttachCoverage(h, {item, item}, {original, alias, other_point, short_window, overlapping}); + REQUIRE(engine.CoverageExecutionCount(h) == 4); + request = 1; engine.RunUntil(2); + engine.ResetCoverage(h, false); + request = 0; response = 1; engine.RunUntil(2); + const auto snapshot = engine.CoverageSnapshot(h); + for (size_t bin = 0; bin < 5; ++bin) REQUIRE(snapshot.counters[12 + bin] == 0); + REQUIRE(engine.Disarm(h)); +} From 93c302105934ba2d146a36643109599cb2488f93 Mon Sep 17 00:00:00 2001 From: Makiras <22570332+Makiras@users.noreply.github.com> Date: Sat, 10 Oct 2026 17:16:35 +0800 Subject: [PATCH 07/10] refactor!: separate sampling runtime and streamline coverage execution Expose XEngine as the common sampling runtime, with coverage bins composed from shared trigger programs and matchers. Retain the XTriggerEngine source alias and preserve its name in generated language bindings. Separate coverage item kinds from bin policies, move implementation headers under detail/, and split reusable memory access from comparison operations. Unify ComUse expressions and conditions, track attached callback lifetimes, and retain alignment-safe native memory access without per-sample allocation. Keep expression matching on a lightweight shared path, move optional coverage diagnostics out of the hot path, pre-index illegal bins, and inline condition evaluators. Skip unused wall-clock sampling when RunUntil has no time budget. BREAKING CHANGE: raise the native ABI to 4 and coverage protocol to 5. Native consumers must rebuild; coverage descriptors now use explicit item/bin kinds and embedded trigger programs. Publish the preview as 0.3.0.dev1. Validation: - Release C++: 10/10 suites passed. - ASan/UBSan C++: 10/10 passed at O1 without LTO; leak detection disabled. - Python: 1021 tests passed against the benchmarked native library. - Differential: 8192 samples and 256 stale handles match the baseline exactly. - Three alternating local benchmark rounds: expression coverage is about 3% faster, and 16-bin Sequence/FSM coverage is about 14%/15% faster than 83ab5b2. The ordinary Edge microbenchmark remains about 2.5% slower than that baseline. --- CMakeLists.txt | 2 +- cmake/Sources.cmake | 7 +- docs/architecture.md | 79 +++-- docs/maintainer-guidance.md | 10 +- include/xspcomm/common/compare.h | 59 +--- include/xspcomm/common/memory.h | 1 + include/xspcomm/common/memory/access.h | 79 +++++ include/xspcomm/common/operation.h | 11 + .../xspcomm/detail}/coverage/state.h | 7 +- include/xspcomm/detail/data/native_memory.h | 70 +++++ include/xspcomm/detail/lifetime.h | 20 ++ include/xspcomm/detail/pattern.h | 38 --- .../xspcomm/detail}/trigger/matcher.h | 27 +- include/xspcomm/detail/trigger/program.h | 77 +++++ include/xspcomm/detail/trigger/registration.h | 104 +++++++ include/xspcomm/trigger/types.h | 69 +++++ include/xspcomm/xclock.h | 4 + include/xspcomm/xcomuse/callback.h | 21 +- include/xspcomm/xcomuse/condition.h | 47 +-- include/xspcomm/xcomuse/expr.h | 17 +- include/xspcomm/xcoverage.h | 26 +- include/xspcomm/xengine.h | 121 ++++++++ include/xspcomm/xpattern.h | 16 + include/xspcomm/xtrigger.h | 212 +------------ include/xspcomm/xutil.h | 8 +- src/common/lifetime.cpp | 8 + src/common/memory.cpp | 61 ++-- src/comuse/callback.cpp | 33 +++ src/comuse/condition.cpp | 196 ++++++------ src/comuse/expr.cpp | 98 ++---- src/comuse/range.cpp | 8 +- src/core/clock.cpp | 49 +-- src/core/data_backends.cpp | 82 +----- src/coverage/engine.cpp | 278 ++++++++++-------- .../registration.cpp} | 14 +- src/pattern.cpp | 78 ----- src/{trigger => runtime}/engine.cpp | 230 +++++---------- src/trigger/matcher.cpp | 19 +- src/trigger/pattern.cpp | 118 ++++++++ swig/xcomm.i | 25 +- tests/CMakeLists.txt | 5 +- tests/unit/test_coverage_core.cpp | 6 +- tests/unit/test_trigger_matcher.cpp | 2 +- tests/unit/test_xcomuse.cpp | 124 ++++++++ tests/unit/test_xcoverage.cpp | 115 +++++--- 45 files changed, 1522 insertions(+), 1159 deletions(-) create mode 100644 include/xspcomm/common/memory/access.h create mode 100644 include/xspcomm/common/operation.h rename {src => include/xspcomm/detail}/coverage/state.h (88%) create mode 100644 include/xspcomm/detail/data/native_memory.h create mode 100644 include/xspcomm/detail/lifetime.h delete mode 100644 include/xspcomm/detail/pattern.h rename {src => include/xspcomm/detail}/trigger/matcher.h (70%) create mode 100644 include/xspcomm/detail/trigger/program.h create mode 100644 include/xspcomm/detail/trigger/registration.h create mode 100644 include/xspcomm/trigger/types.h create mode 100644 include/xspcomm/xengine.h create mode 100644 src/common/lifetime.cpp rename src/{trigger/coverage.cpp => coverage/registration.cpp} (78%) delete mode 100644 src/pattern.cpp rename src/{trigger => runtime}/engine.cpp (66%) create mode 100644 src/trigger/pattern.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ff2ebe..ddcb1af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ else() endif() # Increment only when the native C++ ABI becomes incompatible. -set(XSPCOMM_ABI_VERSION 3) +set(XSPCOMM_ABI_VERSION 4) list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) diff --git a/cmake/Sources.cmake b/cmake/Sources.cmake index 28e2796..ebe6c70 100644 --- a/cmake/Sources.cmake +++ b/cmake/Sources.cmake @@ -1,4 +1,5 @@ set(XSPCOMM_SOURCES + common/lifetime.cpp common/memory.cpp comuse/callback.cpp comuse/condition.cpp @@ -18,9 +19,9 @@ set(XSPCOMM_SOURCES coverage/engine.cpp expr/engine.cpp expr/parser.cpp - pattern.cpp - trigger/coverage.cpp - trigger/engine.cpp + trigger/pattern.cpp + coverage/registration.cpp + runtime/engine.cpp trigger/matcher.cpp ) list(TRANSFORM XSPCOMM_SOURCES PREPEND "${CMAKE_CURRENT_LIST_DIR}/../src/") diff --git a/docs/architecture.md b/docs/architecture.md index 013ac5f..f7f2c62 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -4,23 +4,30 @@ Public interfaces are installed below `include/xspcomm`. Common class entry headers (`xdata.h`, `xclock.h`, `xexpr.h`, `xtrigger.h`) describe their own modules; `xcomm.h` and `xcomuse.h` are the aggregate entry points. -- `common/`: comparison algorithms and memory/string utilities. Comparison - algorithms depend only on standard headers, so XData and checkers share them. -- `xcomuse/`: clock callbacks, condition/range checkers, expression checkers and - the text-program FSM adapter. `ExprEngine` itself belongs to `xexpr.h`. -- `xpattern.h`: shared event modes and sequence/FSM descriptors. - `detail/pattern.h` holds borrowed program views and matching state; - `src/pattern.cpp` implements sequence/FSM advancement. The common execution - component in `src/trigger/matcher.*` manages expression events, overlapping - attempts, completion counts and FSM terminal results for native consumers. -- `xcoverage.h`: coverage descriptors and snapshots. The component in - `src/coverage` organizes points and bins, selects completion results, and owns - crosses, counters and statistical policies. It uses the common trigger - matcher for execution history and advancement. -- `src/trigger`: trigger registration/execution and a small coverage adapter. - Ordinary triggers and coverage use the same expression events and pattern - advancement. Coverage runs in the native sampling phase, with no event queue - or language callback. +- `common/`: comparison operations and algorithms, plus memory/string utilities. + `common/memory/access.h` provides non-owning reads and writes with exact access + widths. It depends only on standard headers and is shared by comparison and + native signal backends. `ComUseDataArray` retains storage ownership/copy duties. +- `xcomuse/`: clock callbacks, condition/range checkers, expression construction + and the text-program FSM adapter. `ComUseExprCheck` uses `ComUseCondCheck`'s + registration namespace, hit processing and clock-stop behavior. Native pointer, + XData and expression evaluation keep their own comparison semantics. +- `xpattern.h` and `trigger/types.h`: common trigger program descriptions, event + modes, sequence/FSM descriptors, handles and sampling results. +- `detail/trigger/`: program validation, single-execution state advancement, + overlapping matchers and trigger registrations. The matching kernel has no + coverage dependency. Implementations live together under `src/trigger`. +- `xcoverage.h` and `detail/coverage/`: explicitly typed points/bins and snapshots, + plus coverage execution history and statistical policy. Bins contain a shared + `XTriggerProgram` description and select completion results. Coverage owns + crosses, counters, classification and terminal selection. +- `xengine.h` and `src/runtime`: the shared clock/phase schedule and registration + lifecycle. The engine sends matches to either trigger events or coverage + statistics. `xtrigger.h` retains the `XTriggerEngine` entry name as an alias of + `XEngine`; language bindings retain their existing class name. Coverage-specific + registration operations live under `src/coverage`, not the trigger kernel. +- `detail/data/native_memory.h`: the templated native-to-vector storage adapter. + Fixed word counts stay expanded at compile time; widths are selected at binding. - `src/core`: signal storage/value operations, backend bindings, ports, clocks, coroutines and configuration. YAML parsing stays in the configuration source; third-party YAML headers are not part of the public header dependency graph. @@ -46,20 +53,26 @@ Update includes directly; removed paths do not have forwarding headers: `xinstance.h` and its `test_xdata()` helper are removed from the production SDK; that test implementation is compiled into `test_xdata` instead. Python-only -third-call declarations live beside the Python wrapper. Existing signal/clock -layouts and callable interfaces are retained. Coverage -descriptors have expanded, so native clients must rebuild against ABI 3; +third-call declarations live beside the Python wrapper. Existing signal +access and clock stepping interfaces are retained. Coverage descriptors and +clock/checker/runtime layouts have changed, so native clients must rebuild against ABI 4; source clients must also update the includes above. ## Trigger programs and coverage -Language frontends lower declarations to expression roots, sequence steps or -FSM transitions. A value point observes an XData signal; a pattern point sets -`XCoverageItem.pattern` and organizes complete trigger programs without a -synthetic signal. Pattern bins select Expr, Sequence or FSM with -`XCoverageBin.program_kind` (0, 1 or 2), and specify event mode, bounded overlap -and optional FSM terminals. `CoverageVersion() == 4` identifies this descriptor -protocol; the native library ABI is independently versioned as 3. +Language frontends lower declarations to `XTriggerProgram`: an explicitly typed +expression, sequence or FSM, with event mode and execution capacity. The trigger +and coverage paths use the same validator and advancement kernel. Matching state +belongs to each execution and is independent of the immutable program description. + +`XCoverageItem.kind` selects Value, Pattern or Cross. Value points require an +XData source; pattern points have no synthetic source; crosses name preceding +value points. `XCoverageBin.kind` selects Normal, Ignore, Illegal or Default, +while `XCoverageBin.program` supplies matching and `terminals` selects FSM +completion results. Impossible field combinations are rejected at registration. +`CoverageVersion() == 5` identifies this descriptor protocol; the native library +ABI is independently versioned as 4. Protocol 4 frontends must migrate flat bin +program fields to `bin.program` and replace `item.pattern` with an explicit kind. Equivalent programs within one point share an execution when their roots, steps/transitions, event mode and overlap capacity agree. Terminal selection, @@ -77,6 +90,18 @@ Crosses containing pattern points are rejected until completion correlation semantics are defined. Program reclamation during a long execution and complete expression width/signed/cast/four-state semantics remain separate work. +## ComUse callback ownership + +`ComUseStepCb.Attach(clock, rising)` registers a managed clock callback; `Detach` +and destruction remove its registrations. Destroying the clock first is safe. +Removal during another callback defers vector erasure until iteration finishes. +The callback base has a virtual destructor and cannot be copied with registrations. + +`ComUseCondCheck.BindXClock` specifies clocks to stop on a hit, separately from +sampling registration. `ClearClock` clears that stop list. Existing `GetCb` / +`CSelf` raw callback registration remains available and requires the caller to +unregister before destroying the checker; use `Attach` for managed ownership. + ## TLM source SDK The standalone TLM integration lives in `integrations/tlm`. Installation exports diff --git a/docs/maintainer-guidance.md b/docs/maintainer-guidance.md index 4144bc8..e91b74c 100644 --- a/docs/maintainer-guidance.md +++ b/docs/maintainer-guidance.md @@ -57,15 +57,21 @@ extensions; a Python version specifier alone cannot verify C++ ABI identity. The native trigger engine changes the C++ layouts of `XClock`, `XData`, and `ExprNode`, which introduced native ABI 2. Complete per-bin trigger programs now -expand `XCoverageBin`, so the current native ABI is 3 (`libxspcomm.so.3`). Rebuild +reorganize `XCoverageBin` and checker/runtime layouts, so the current native ABI is 4 (`libxspcomm.so.4`). Rebuild Picker-generated DUT extensions and other native bindings against these headers and library, and update the consumer's ABI requirement together. Keep each generated DUT and its runtime on a matching native ABI. -`XTriggerEngine.CoverageVersion() == 4` identifies the complete trigger-bin +`XTriggerEngine.CoverageVersion() == 5` identifies the complete trigger-bin descriptor protocol. It is separate from the native library ABI and the Python package version. The Python `XPin` wrapper is removed; use `XData` directly instead. Picker 2.0.1 restricts xspcomm to `<0.2`. Before releasing Picker with an xspcomm `0.2.x` package, update its supported dependency range and rebuild its bundled native runtime. + +The PR #30 review migration introduces `XEngine` with the existing +`XTriggerEngine` entry alias, explicit coverage kinds and `bin.program`. +Use the `0.3.0.dev1` preview for ABI 4 / descriptor protocol 5; `0.3.0.dev0` +identified the earlier ABI 3 / protocol 4 preview. Update frontend lowering and +rebuild native clients together. diff --git a/include/xspcomm/common/compare.h b/include/xspcomm/common/compare.h index 9d32b6e..b88458b 100644 --- a/include/xspcomm/common/compare.h +++ b/include/xspcomm/common/compare.h @@ -4,7 +4,8 @@ #include #include #include -#include +#include "xspcomm/common/memory/access.h" +#include "xspcomm/common/operation.h" namespace xspcomm::compare { @@ -34,58 +35,6 @@ bool Compare(Op operation, Left&& lhs, Right&& rhs) { } } -template struct UIntBytes; - -template -struct UIntWord { - static uint64_t Load(const unsigned char* data) { - T value; - std::memcpy(&value, data, sizeof(value)); - return value; - } -}; - -template -struct UIntParts { - static uint64_t Load(const unsigned char* data) { - return UIntBytes::Load(data) | - (UIntBytes::Load(data + LowBytes) << (LowBytes * 8)); - } -}; - -// Fixed byte widths use native integers or consecutive low/high parts. -#define XSPCOMM_UINT_BYTES(Bytes, ...) \ - template <> struct UIntBytes : __VA_ARGS__ {}; -XSPCOMM_UINT_BYTES(1, UIntWord) -XSPCOMM_UINT_BYTES(2, UIntWord) -XSPCOMM_UINT_BYTES(3, UIntParts<2, 1>) -XSPCOMM_UINT_BYTES(4, UIntWord) -XSPCOMM_UINT_BYTES(5, UIntParts<4, 1>) -XSPCOMM_UINT_BYTES(6, UIntParts<4, 2>) -XSPCOMM_UINT_BYTES(7, UIntParts<6, 1>) -XSPCOMM_UINT_BYTES(8, UIntWord) -#undef XSPCOMM_UINT_BYTES - -// A non-owning reader for little-endian buffers, including a partial last word. -struct ByteWords { - const unsigned char* data; - size_t bytes; - - size_t Count() const { return bytes / 4 + (bytes % 4 != 0); } - - uint32_t operator()(size_t i) const { - const size_t offset = i * 4; - const size_t count = std::min(sizeof(uint32_t), bytes - offset); - const auto* word = data + offset; - if (count == sizeof(uint32_t)) return UIntBytes<4>::Load(word); - switch (count) { - case 1: return UIntBytes<1>::Load(word); - case 2: return UIntBytes<2>::Load(word); - default: return UIntBytes<3>::Load(word); - } - } -}; - template int Order(T lhs, T rhs) { return lhs < rhs ? -1 : lhs > rhs ? 1 : 0; @@ -119,8 +68,8 @@ inline int CompareSignedBytes(const unsigned char* a, const unsigned char* b, si const bool bnegative = (b[bytes - 1] & 0x80) != 0; if (anegative != bnegative) return anegative ? -1 : 1; // Within the same sign, two's-complement values retain unsigned ordering. - const ByteWords lhs{a, bytes}; - const ByteWords rhs{b, bytes}; + const memory::ByteWords lhs{a, bytes}; + const memory::ByteWords rhs{b, bytes}; return CompareWords(lhs.Count(), lhs, rhs); } diff --git a/include/xspcomm/common/memory.h b/include/xspcomm/common/memory.h index 6495d3d..a99e51d 100644 --- a/include/xspcomm/common/memory.h +++ b/include/xspcomm/common/memory.h @@ -2,6 +2,7 @@ #define XSPCOMM_COMMON_MEMORY_H #include "xspcomm/xutil.h" +#include "xspcomm/common/memory/access.h" #include #include #include diff --git a/include/xspcomm/common/memory/access.h b/include/xspcomm/common/memory/access.h new file mode 100644 index 0000000..ef19bfe --- /dev/null +++ b/include/xspcomm/common/memory/access.h @@ -0,0 +1,79 @@ +#ifndef XSPCOMM_COMMON_MEMORY_ACCESS_H +#define XSPCOMM_COMMON_MEMORY_ACCESS_H + +#include +#include +#include +#include +#include + +namespace xspcomm::memory { + +// Native storage access without alignment or strict-aliasing assumptions. +template +T Load(const void* source) { + static_assert(std::is_trivially_copyable_v); + T value; + std::memcpy(&value, source, sizeof(T)); + return value; +} + +template +void Store(void* destination, T value) { + static_assert(std::is_trivially_copyable_v); + std::memcpy(destination, &value, sizeof(T)); +} + +template struct UIntBytes; + +template +struct UIntWord { + static uint64_t Load(const unsigned char* data) { + return memory::Load(data); + } +}; + +template +struct UIntParts { + static uint64_t Load(const unsigned char* data) { + return UIntBytes::Load(data) | + (UIntBytes::Load(data + LowBytes) << (LowBytes * 8)); + } +}; + +// Fixed byte widths use native integers or consecutive low/high parts. +#define XSPCOMM_UINT_BYTES(Bytes, ...) \ + template <> struct UIntBytes : __VA_ARGS__ {}; +XSPCOMM_UINT_BYTES(1, UIntWord) +XSPCOMM_UINT_BYTES(2, UIntWord) +XSPCOMM_UINT_BYTES(3, UIntParts<2, 1>) +XSPCOMM_UINT_BYTES(4, UIntWord) +XSPCOMM_UINT_BYTES(5, UIntParts<4, 1>) +XSPCOMM_UINT_BYTES(6, UIntParts<4, 2>) +XSPCOMM_UINT_BYTES(7, UIntParts<6, 1>) +XSPCOMM_UINT_BYTES(8, UIntWord) +#undef XSPCOMM_UINT_BYTES + +// A non-owning reader for little-endian buffers, including a partial last word. +struct ByteWords { + const unsigned char* data; + size_t bytes; + + size_t Count() const { return bytes / 4 + (bytes % 4 != 0); } + + uint32_t operator()(size_t i) const { + const size_t offset = i * 4; + const size_t count = std::min(sizeof(uint32_t), bytes - offset); + const auto* word = data + offset; + if (count == sizeof(uint32_t)) return UIntBytes<4>::Load(word); + switch (count) { + case 1: return UIntBytes<1>::Load(word); + case 2: return UIntBytes<2>::Load(word); + default: return UIntBytes<3>::Load(word); + } + } +}; + +} // namespace xspcomm::memory + +#endif diff --git a/include/xspcomm/common/operation.h b/include/xspcomm/common/operation.h new file mode 100644 index 0000000..ca68bb1 --- /dev/null +++ b/include/xspcomm/common/operation.h @@ -0,0 +1,11 @@ +#ifndef XSPCOMM_COMMON_OPERATION_H +#define XSPCOMM_COMMON_OPERATION_H + +namespace xspcomm { + +enum class CompareOp { EQ = 0, NE = 1, GT = 2, GE = 3, LT = 4, LE = 5 }; +typedef CompareOp ComUseCondCmp; + +} // namespace xspcomm + +#endif diff --git a/src/coverage/state.h b/include/xspcomm/detail/coverage/state.h similarity index 88% rename from src/coverage/state.h rename to include/xspcomm/detail/coverage/state.h index 2b9480f..571a3d6 100644 --- a/src/coverage/state.h +++ b/include/xspcomm/detail/coverage/state.h @@ -2,7 +2,7 @@ #define XSPCOMM_COVERAGE_STATE_H #include "xspcomm/xcoverage.h" -#include "../trigger/matcher.h" +#include "xspcomm/detail/trigger/matcher.h" #include "xspcomm/xexpr.h" #include #include @@ -38,6 +38,10 @@ class CoverageState { XCoverageSnapshot Snapshot(uint64_t tick, bool progress) const; private: + void RecordDiagnostics(size_t pattern, const MatchUpdate &update); + void SampleValuePoint(ExprEngine &expr, size_t item, size_t offset); + inline void SamplePatternPoint(ExprEngine &expr, size_t item, size_t offset); + void SampleCrossPoint(size_t item, size_t offset); size_t Advance(ExprEngine &expr, PatternMatcher &matcher, size_t pattern, bool overlap, size_t max_active); std::vector items; @@ -45,6 +49,7 @@ class CoverageState { PatternMatcher source_matcher; std::vector matchers; std::vector owners; + std::vector illegal_indices; bool overlap = false; size_t max_active = 1; enum { Started, Completed, Failed, Expired, Aborted, Cleared, PeakActive, DiagnosticCount }; diff --git a/include/xspcomm/detail/data/native_memory.h b/include/xspcomm/detail/data/native_memory.h new file mode 100644 index 0000000..9516a02 --- /dev/null +++ b/include/xspcomm/detail/data/native_memory.h @@ -0,0 +1,70 @@ +#ifndef XSPCOMM_DETAIL_DATA_NATIVE_MEMORY_H +#define XSPCOMM_DETAIL_DATA_NATIVE_MEMORY_H + +#include "xspcomm/xdata.h" +#include "xspcomm/common/memory/access.h" +#include +#include + +namespace xspcomm::detail { +// Count == 0 uses a runtime loop; fixed counts are expanded at compile time. +template +struct VecMemoryIO { + static_assert(std::is_same_v || + std::is_same_v || + std::is_same_v); + + template + static void ReadFixed(xsvLogicVecVal* dst, const unsigned char* src, + std::index_sequence) { + ((dst[I].aval = static_cast(memory::Load(src + I * sizeof(T)))), ...); + } + + template + static void WriteFixed(unsigned char* dst, const xsvLogicVecVal* src, + std::index_sequence) { + (memory::Store(dst + I * sizeof(T), static_cast(src[I].aval)), ...); + } + + static void Read(xsvLogicVecVal* dst, const void* src, size_t count) { + const auto* bytes = static_cast(src); + const size_t words = sizeof(T) < sizeof(uint32_t) ? 1 : count; + if constexpr (Count != 0) { + // ReInit preserves callbacks, so the current width can differ. + if (likely(words == Count)) { + ReadFixed(dst, bytes, std::make_index_sequence{}); + return; + } + } + for (size_t i = 0; i < words; ++i) + dst[i].aval = static_cast(memory::Load(bytes + i * sizeof(T))); + } + + static void Write(void* dst, const xsvLogicVecVal* src, size_t count) { + auto* bytes = static_cast(dst); + const size_t words = sizeof(T) < sizeof(uint32_t) ? 1 : count; + if constexpr (Count != 0) { + if (likely(words == Count)) { + WriteFixed(bytes, src, std::make_index_sequence{}); + return; + } + } + for (size_t i = 0; i < words; ++i) + memory::Store(bytes + i * sizeof(T), static_cast(src[i].aval)); + } + + using VecCallback = xfunction; + static void Bind(uint64_t address, const uint32_t& count, + VecCallback& read, VecCallback& write) { + read = [address, &count](xsvLogicVecVal* dst) { + Read(dst, reinterpret_cast(address), count); + }; + write = [address, &count](const xsvLogicVecVal* src) { + Write(reinterpret_cast(address), src, count); + }; + } +}; +} // namespace xspcomm::detail + + +#endif diff --git a/include/xspcomm/detail/lifetime.h b/include/xspcomm/detail/lifetime.h new file mode 100644 index 0000000..c95a68d --- /dev/null +++ b/include/xspcomm/detail/lifetime.h @@ -0,0 +1,20 @@ +#ifndef XSPCOMM_DETAIL_LIFETIME_H +#define XSPCOMM_DETAIL_LIFETIME_H + +#include + +namespace xspcomm::detail { + +// Copies have independent lifetimes, even when the containing object is copied. +class Lifetime { + std::shared_ptr token; +public: + Lifetime(); + Lifetime(const Lifetime &); + Lifetime &operator=(const Lifetime &) { return *this; } + std::weak_ptr Observe() const { return token; } +}; + +} // namespace xspcomm::detail + +#endif diff --git a/include/xspcomm/detail/pattern.h b/include/xspcomm/detail/pattern.h deleted file mode 100644 index 256b4d0..0000000 --- a/include/xspcomm/detail/pattern.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef XSPCOMM_DETAIL_PATTERN_H -#define XSPCOMM_DETAIL_PATTERN_H - -#include "xspcomm/xpattern.h" -#include -#include - -namespace xspcomm { -class ExprEngine; -namespace detail { - -struct PatternState { - bool sequence_failed = false; - bool sequence_expired = false; - size_t sequence_index = 0; - uint64_t sequence_age = 0; - uint64_t sequence_held = 0; - uint32_t fsm_current_state = 0; -}; - -// A non-owning view, valid only while evaluating the owning registration. -struct PatternView { - const std::vector *sequence = nullptr; - const std::vector *fsm = nullptr; - uint32_t start_state = 0; - int root = -1; - XConditionMode mode = XConditionMode::Enter; -}; - -bool AdvanceSequence(const std::vector &steps, - PatternState &state, ExprEngine &expr); -bool AdvanceFsm(const std::vector &transitions, - PatternState &state, ExprEngine &expr, uint32_t &terminal); - -} // namespace detail -} // namespace xspcomm - -#endif diff --git a/src/trigger/matcher.h b/include/xspcomm/detail/trigger/matcher.h similarity index 70% rename from src/trigger/matcher.h rename to include/xspcomm/detail/trigger/matcher.h index 7c48f99..d2151f5 100644 --- a/src/trigger/matcher.h +++ b/include/xspcomm/detail/trigger/matcher.h @@ -1,7 +1,7 @@ -#ifndef XSPCOMM_TRIGGER_MATCHER_H -#define XSPCOMM_TRIGGER_MATCHER_H +#ifndef XSPCOMM_DETAIL_TRIGGER_MATCHER_H +#define XSPCOMM_DETAIL_TRIGGER_MATCHER_H -#include "xspcomm/detail/pattern.h" +#include "xspcomm/detail/trigger/program.h" #include namespace xspcomm::detail { @@ -15,25 +15,6 @@ inline bool MatchCondition(bool known, bool current, XConditionMode mode, bool & return hit; } -struct PatternResult { - bool completed = false; - bool failed = false; - uint32_t terminal = 0; -}; - -inline PatternResult AdvancePattern(const PatternView &program, PatternState &state, ExprEngine &expr) -{ - PatternResult result; - if (program.fsm) { - result.completed = AdvanceFsm(*program.fsm, state, expr, result.terminal); - result.failed = !result.completed && state.fsm_current_state == program.start_state; - } else { - result.completed = AdvanceSequence(*program.sequence, state, expr); - result.failed = state.sequence_failed; - } - return result; -} - struct MatchUpdate { size_t started = 0; size_t completed = 0; @@ -48,6 +29,8 @@ class PatternMatcher { public: explicit PatternMatcher(PatternView program = {}); MatchUpdate Advance(ExprEngine &expr, bool overlap = false, size_t max_active = 1); + // Expression programs use the same history without temporal diagnostics. + uint64_t AdvanceCondition(ExprEngine &expr); // Counts from the latest sample, optionally restricted to selected FSM terminals. uint64_t Count(const std::vector &terminals = {}) const { diff --git a/include/xspcomm/detail/trigger/program.h b/include/xspcomm/detail/trigger/program.h new file mode 100644 index 0000000..fd7e725 --- /dev/null +++ b/include/xspcomm/detail/trigger/program.h @@ -0,0 +1,77 @@ +#ifndef XSPCOMM_DETAIL_TRIGGER_PROGRAM_H +#define XSPCOMM_DETAIL_TRIGGER_PROGRAM_H + +#include "xspcomm/xpattern.h" +#include +#include + +namespace xspcomm { +class ExprEngine; +namespace detail { + +class ProgramValidator { +public: + static bool Validate(const XTriggerProgram &program, const ExprEngine &expr); + static bool Sequence(const std::vector &steps, const ExprEngine &expr); + static bool Fsm(uint32_t states, uint32_t start, const std::vector &transitions, + const ExprEngine &expr); +}; + +struct PatternState { + bool sequence_failed = false; + bool sequence_expired = false; + size_t sequence_index = 0; + uint64_t sequence_age = 0; + uint64_t sequence_held = 0; + uint32_t fsm_current_state = 0; +}; + +// A non-owning view, valid only while evaluating the owning registration. +struct PatternView { + const std::vector *sequence = nullptr; + const std::vector *fsm = nullptr; + uint32_t start_state = 0; + int root = -1; + XConditionMode mode = XConditionMode::Enter; + + static PatternView From(const XTriggerProgram &program) { + PatternView view; + view.start_state = program.start_state; + view.root = program.root; + view.mode = program.mode; + if (program.kind == XTriggerProgramKind::Sequence) view.sequence = &program.steps; + if (program.kind == XTriggerProgramKind::Fsm) view.fsm = &program.transitions; + return view; + } +}; + +struct PatternResult { + bool completed = false; + bool failed = false; + uint32_t terminal = 0; +}; + +class PatternExecutor { +public: + static PatternResult Advance(const PatternView &program, PatternState &state, ExprEngine &expr) { + PatternResult result; + if (program.fsm) { + result.completed = AdvanceFsm(*program.fsm, state, expr, result.terminal); + result.failed = !result.completed && state.fsm_current_state == program.start_state; + } else { + result.completed = AdvanceSequence(*program.sequence, state, expr); + result.failed = state.sequence_failed; + } + return result; + } +private: + static bool AdvanceSequence(const std::vector &steps, + PatternState &state, ExprEngine &expr); + static bool AdvanceFsm(const std::vector &transitions, + PatternState &state, ExprEngine &expr, uint32_t &terminal); +}; + +} // namespace detail +} // namespace xspcomm + +#endif diff --git a/include/xspcomm/detail/trigger/registration.h b/include/xspcomm/detail/trigger/registration.h new file mode 100644 index 0000000..faddeff --- /dev/null +++ b/include/xspcomm/detail/trigger/registration.h @@ -0,0 +1,104 @@ +#ifndef XSPCOMM_DETAIL_TRIGGER_REGISTRATION_H +#define XSPCOMM_DETAIL_TRIGGER_REGISTRATION_H + +#include "xspcomm/trigger/types.h" +#include "xspcomm/detail/trigger/matcher.h" +#include "xspcomm/xexpr.h" +#include + +namespace xspcomm::detail { + +struct TriggerMatch { + bool hit = false; + XHitKind kind = XHitKind::Condition; + uint64_t value = 0; + uint64_t x_mask = 0; +}; + +// The trigger kernel owns matching state and never references coverage policy. +struct TriggerRegistration : PatternState { + enum class Kind : uint8_t { Edge, ClockCycles, ValueEq, ValueChange, Sample, Expr, Sequence, Fsm }; + uint32_t generation = 0; + bool occupied = false; + bool armed = false; + Kind kind = Kind::Edge; + XPhase phase = XPhase::RisingStable; + uint64_t source_id = 0; + uint64_t remaining = 0; + uint64_t initial_count = 0; + XData *signal = nullptr; + uint64_t expected = 0; + uint64_t expected_x_mask = 0; + std::shared_ptr expected_wide; + std::vector expected_bytes; + std::vector expected_x_bytes; + XTriggerProgram program; + bool last_condition = false; + + void ResetPattern() { + sequence_index = sequence_age = sequence_held = 0; + fsm_current_state = program.start_state; + remaining = initial_count; + } + + TriggerMatch Evaluate(ExprEngine &expr, XPhase phase) { + switch (kind) { + case Kind::Edge: + return {true, phase == XPhase::RisingStable ? XHitKind::ClockRise : + phase == XPhase::DriveStable ? XHitKind::DriveStable : XHitKind::ClockFall}; + case Kind::ClockCycles: + if (--remaining == 0) return {true, XHitKind::ClockCycles}; + break; + case Kind::ValueEq: { + const uint64_t value = signal->W() > 64 ? 0 : signal->U(); + const bool known = signal->DataValid(); + const bool current = known && (expected_wide ? signal->Equal(*expected_wide) : value == expected); + if (MatchCondition(known, current, program.mode, last_condition)) + return {true, XHitKind::Value, value}; + break; + } + case Kind::ValueChange: + if (signal->W() > 64) { + auto value = signal->GetVU8(); + auto x_value = signal->GetBvalBytes(); + if (value != expected_bytes || x_value != expected_x_bytes) { + expected_bytes = std::move(value); + expected_x_bytes = std::move(x_value); + return {true, XHitKind::ValueChange}; + } + } else { + const uint64_t value = signal->U(); + const uint64_t x_mask = signal->XMask(); + if (value != expected || x_mask != expected_x_mask) { + expected = value; + expected_x_mask = x_mask; + return {true, XHitKind::ValueChange, value, x_mask}; + } + } + break; + case Kind::Sample: + return {true, XHitKind::Condition}; + case Kind::Expr: { + const bool known = expr.IsKnown(program.root); + const bool current = known && expr.Eval(program.root) != 0; + if (MatchCondition(known, current, program.mode, last_condition)) + return {true, XHitKind::Condition, current ? 1U : 0U}; + break; + } + case Kind::Sequence: + if (PatternExecutor::Advance(PatternView::From(program), *this, expr).completed) + return {true, XHitKind::Fsm, 1}; + break; + case Kind::Fsm: { + const auto result = PatternExecutor::Advance(PatternView::From(program), *this, expr); + if (result.completed) return {true, XHitKind::Fsm, result.terminal}; + break; + } + } + return {}; + } +}; + +} // namespace xspcomm::detail + +#endif diff --git a/include/xspcomm/trigger/types.h b/include/xspcomm/trigger/types.h new file mode 100644 index 0000000..f2ca2f2 --- /dev/null +++ b/include/xspcomm/trigger/types.h @@ -0,0 +1,69 @@ +#ifndef XSPCOMM_TRIGGER_TYPES_H +#define XSPCOMM_TRIGGER_TYPES_H + +#include "xspcomm/xclock.h" +#include +#include +#include + +namespace xspcomm { + +enum class XHitKind : uint8_t { + ClockFall = 0, + ClockRise = 1, + ClockCycles = 2, + Value = 3, + Condition = 4, + Fsm = 5, + ValueChange = 6, + DriveStable = 7, +}; + +enum class XStopReason : uint8_t { + RunLimit = 0, + TriggerHit = 1, + BackendStop = 2, + QuantumExpired = 3, + EdgeBarrier = 4, + UserPause = 5, + SimulationClose = 6, + CallbackError = 7, + BackendError = 8, +}; + +struct XRegistrationHandle { + uint32_t slot = std::numeric_limits::max(); + uint32_t generation = 0; + + bool IsValid() const { + return slot != std::numeric_limits::max(); + } +}; + +struct XBackendHit { + uint64_t event_id = 0; + uint64_t tick = 0; + uint64_t source_id = 0; + uint64_t value = 0; + uint64_t x_mask = 0; + uint32_t slot = 0; + uint32_t generation = 0; + XHitKind kind = XHitKind::ClockFall; + XPhase phase = XPhase::FallingStable; + uint16_t flags = 0; +}; + +struct XRunResult { + uint64_t advanced_ticks = 0; + XPhase stopped_phase = XPhase::RisingStable; + XStopReason stop_reason = XStopReason::RunLimit; + std::vector hits; + + bool IsPhaseBarrier() const { + return advanced_ticks != 0 || stopped_phase == XPhase::DriveStable; + } +}; + +} // namespace xspcomm + +#endif diff --git a/include/xspcomm/xclock.h b/include/xspcomm/xclock.h index de71950..708658e 100644 --- a/include/xspcomm/xclock.h +++ b/include/xspcomm/xclock.h @@ -4,6 +4,7 @@ #include "xspcomm/xdata.h" #include "xspcomm/xport.h" #include "xspcomm/xutil.h" +#include "xspcomm/detail/lifetime.h" #if ENABLE_XCOROUTINE #include "xspcomm/xcoroutine.h" @@ -61,6 +62,8 @@ enum class XPhase { class XClock { + friend class ComUseStepCb; + detail::Lifetime callback_lifetime; xfunction step_fc; #if ENABLE_XCOROUTINE std::map, _XAWait *> cor_handler; @@ -68,6 +71,7 @@ class XClock std::vector list_call_back_ris; std::vector list_call_back_fal; bool in_callback = false; + bool callbacks_removed = false; bool is_disable = false; int fast_mode_level = 0; bool half_cycle_open = false; diff --git a/include/xspcomm/xcomuse/callback.h b/include/xspcomm/xcomuse/callback.h index 1372684..b97c501 100644 --- a/include/xspcomm/xcomuse/callback.h +++ b/include/xspcomm/xcomuse/callback.h @@ -2,19 +2,36 @@ #define XSPCOMM_XCOMUSE_CALLBACK_H #include "xspcomm/xutil.h" +#include "xspcomm/detail/lifetime.h" #include +#include +#include namespace xspcomm { class XData; + class XClock; class ComUseStepCb{ int cb_maxcts = -1; int cb_counts = 0; bool cb_enable = true; + detail::Lifetime lifetime; + struct Registration { + XClock *clock; + std::weak_ptr lifetime; + bool rising; + std::string description; + }; + std::vector registrations; public: - uint64_t cycle; - ComUseStepCb(){} + uint64_t cycle = 0; + ComUseStepCb() = default; + ComUseStepCb(const ComUseStepCb &) = delete; + ComUseStepCb &operator=(const ComUseStepCb &) = delete; + virtual ~ComUseStepCb(); + void Attach(XClock *clock, bool rising = true); + void Detach(); void Disable(); void Enable(); bool IsDisable(); diff --git a/include/xspcomm/xcomuse/condition.h b/include/xspcomm/xcomuse/condition.h index cc78d99..e706a53 100644 --- a/include/xspcomm/xcomuse/condition.h +++ b/include/xspcomm/xcomuse/condition.h @@ -12,44 +12,37 @@ namespace xspcomm { class XClock; + class ExprEngine; // Condition Checker - enum class ComUseCondCmp{ - EQ = 0, - NE = 1, - GT = 2, - GE = 3, - LT = 4, - LE = 5, - }; class ComUseCondCheck: public ComUseStepCb{ using XDataCmpFn = bool (*)(XData*, XData*); using PtrCmpFn = bool (*)(ComUseCondCheck*, uint64_t, uint64_t, int); - template + template static bool XDataCmp(XData* a, XData* b) { return compare::Compare(*a, *b); } - template + template static bool PtrCmp(ComUseCondCheck*, uint64_t a, uint64_t b, int bytes) { const int order = compare::CompareSignedBytes( reinterpret_cast(a), reinterpret_cast(b), bytes); return compare::Compare(order, 0); } - static XDataCmpFn SelectXDataCmpFn(ComUseCondCmp cmp); - static PtrCmpFn SelectPtrCmpFn(ComUseCondCmp cmp); + static XDataCmpFn SelectXDataCmpFn(CompareOp cmp); + static PtrCmpFn SelectPtrCmpFn(CompareOp cmp); std::vector clk_list; struct CondXDataEntry { std::string name; XData* pin = nullptr; XData* val = nullptr; - ComUseCondCmp cmp = ComUseCondCmp::EQ; + CompareOp cmp = CompareOp::EQ; XData* valid = nullptr; XData* valid_value = nullptr; xfunction func = nullptr; int triggered = 0; uint64_t arg = 0; - ComUseCondCmp valid_cmp = ComUseCondCmp::EQ; + CompareOp valid_cmp = CompareOp::EQ; XDataCmpFn cmp_fn = nullptr; XDataCmpFn valid_cmp_fn = nullptr; }; @@ -57,7 +50,7 @@ namespace xspcomm { std::string name; uint64_t pin_ptr = 0; uint64_t val_ptr = 0; - ComUseCondCmp cmp = ComUseCondCmp::EQ; + CompareOp cmp = CompareOp::EQ; int bytes = 0; uint64_t valid_ptr = 0; uint64_t valid_value_ptr = 0; @@ -65,10 +58,22 @@ namespace xspcomm { xfunction func = nullptr; int triggered = 0; uint64_t arg = 0; - ComUseCondCmp valid_cmp = ComUseCondCmp::EQ; + CompareOp valid_cmp = CompareOp::EQ; PtrCmpFn cmp_fn = nullptr; PtrCmpFn valid_cmp_fn = nullptr; }; + struct ExprEntry { + std::string name; + ExprEngine *engine = nullptr; + int root = -1; + bool triggered = false; + }; + std::vector expressions; + std::unordered_map expression_index; + void RemoveExpression(const std::string &name); + inline bool Evaluate(CondXDataEntry &entry); + inline bool Evaluate(CondUint64Entry &entry); + inline bool Evaluate(ExprEntry &entry); std::unordered_map cond_idx_xdata; std::unordered_map cond_idx_uint64; std::vector cond_vec_xdata; @@ -79,13 +84,15 @@ namespace xspcomm { static bool RemoveUint64Cond(std::unordered_map &idx, std::vector &vec, const std::string &name); + protected: + void SetExpression(std::string name, ExprEngine &engine, int root); public: ComUseCondCheck(XClock* clk=nullptr){if(clk)this->clk_list.push_back(clk);} void BindXClock(XClock *clk); - void SetCondition(std::string unique_name, XData* pin, XData* val, ComUseCondCmp cmp, XData *valid = nullptr, XData *valid_value = nullptr, xfunction func = nullptr, uint64_t arg=0); - void SetCondition(std::string unique_name, uint64_t pin_ptr, uint64_t val_ptr, ComUseCondCmp cmp, int bytes, uint64_t valid_ptr = 0, uint64_t valid_value_ptr = 0, int valid_bytes = 1, xfunction func = nullptr, uint64_t arg=0); - ComUseCondCmp GetValidCmpMode(std::string unique_name); - void SetValidCmpMode(std::string unique_name, ComUseCondCmp cmp); + void SetCondition(std::string unique_name, XData* pin, XData* val, CompareOp cmp, XData *valid = nullptr, XData *valid_value = nullptr, xfunction func = nullptr, uint64_t arg=0); + void SetCondition(std::string unique_name, uint64_t pin_ptr, uint64_t val_ptr, CompareOp cmp, int bytes, uint64_t valid_ptr = 0, uint64_t valid_value_ptr = 0, int valid_bytes = 1, xfunction func = nullptr, uint64_t arg=0); + CompareOp GetValidCmpMode(std::string unique_name); + void SetValidCmpMode(std::string unique_name, CompareOp cmp); void RemoveCondition(std::string unique_name); std::map ListCondition(); std::vector GetTriggeredConditionKeys(); diff --git a/include/xspcomm/xcomuse/expr.h b/include/xspcomm/xcomuse/expr.h index a5cadbc..88c04f3 100644 --- a/include/xspcomm/xcomuse/expr.h +++ b/include/xspcomm/xcomuse/expr.h @@ -1,29 +1,18 @@ #ifndef XSPCOMM_XCOMUSE_EXPR_H #define XSPCOMM_XCOMUSE_EXPR_H -#include "xspcomm/xcomuse/callback.h" +#include "xspcomm/xcomuse/condition.h" #include "xspcomm/xexpr.h" -#include namespace xspcomm { class XClock; - class ComUseExprCheck: public ComUseStepCb{ + class ComUseExprCheck: public ComUseCondCheck{ ExprEngine engine; - struct ExprItem{ - std::string name; - int root = -1; - uint64_t last_trigger_cycle = (uint64_t)-1; - }; - std::vector clk_list; - std::vector expr_list; - std::unordered_map expr_index; - uint64_t last_eval_cycle = 0; public: - ComUseExprCheck(XClock* clk=nullptr){if(clk)this->clk_list.push_back(clk);} - void BindXClock(XClock *clk); + ComUseExprCheck(XClock* clk=nullptr) : ComUseCondCheck(clk) {} int ExprNewConst(uint64_t v); int ExprNewSignal(XData* sig); int ExprNewUnary(int op, int child); diff --git a/include/xspcomm/xcoverage.h b/include/xspcomm/xcoverage.h index cfc01cf..bb878e0 100644 --- a/include/xspcomm/xcoverage.h +++ b/include/xspcomm/xcoverage.h @@ -11,27 +11,23 @@ namespace xspcomm { class XData; namespace detail { class CoverageState; } -// Coverage uses numeric IDs in the hot path. Human names live in the client. +enum class XCoverageItemKind : uint8_t { Value = 0, Pattern = 1, Cross = 2 }; +enum class XCoverageBinKind : uint8_t { Normal = 0, Ignore = 1, Illegal = 2, Default = 3 }; + +// Coverage owns classification and statistics; trigger programs own matching. struct XCoverageItem { + XCoverageItemKind kind = XCoverageItemKind::Value; int gate = -1; - bool pattern = false; // event point; no synthetic XData source - XData *signal = nullptr; // direct value source; null for a cross or pattern point - std::vector dimensions; // point IDs; empty for a point + XData *signal = nullptr; // Value only + std::vector dimensions; // Cross only; preceding value point IDs }; struct XCoverageBin { uint32_t item = 0; - uint32_t kind = 0; // normal / ignore / illegal / default - int root = -1; - std::vector steps; - bool overlap = true; - uint32_t program_kind = 0; // Expr=0, Sequence=1, FSM=2 (pattern points) - XConditionMode mode = XConditionMode::Enter; - uint32_t max_active = 1; - uint32_t state_count = 0, start_state = 0; - std::vector transitions; - std::vector terminals; // empty selects all - std::vector dimensions; // normal bin IDs for a cross tuple + XCoverageBinKind kind = XCoverageBinKind::Normal; + XTriggerProgram program; + std::vector terminals; // FSM completion selection; empty selects all + std::vector dimensions; // Cross tuple of normal bin IDs }; struct XCoverageSnapshot { diff --git a/include/xspcomm/xengine.h b/include/xspcomm/xengine.h new file mode 100644 index 0000000..65ed09f --- /dev/null +++ b/include/xspcomm/xengine.h @@ -0,0 +1,121 @@ +#ifndef XSPCOMM_XENGINE_H +#define XSPCOMM_XENGINE_H + +#include "xspcomm/trigger/types.h" +#include "xspcomm/xdata.h" +#include "xspcomm/xcoverage.h" +#include "xspcomm/detail/trigger/registration.h" + +#include +#include +#include +#include + +namespace xspcomm { + +class ExprEngine; + +// Shared sampling runtime. Trigger matches and coverage statistics are clients +// of the same clock/phase schedule; the matching kernel has no coverage policy. +class XEngine { + using WatcherKind = detail::TriggerRegistration::Kind; + struct Watcher : detail::TriggerRegistration { + std::shared_ptr coverage; + }; + + XClock *clock = nullptr; + size_t capacity = 0; + uint64_t next_event_id = 1; + std::vector watchers; + std::vector free_slots; + std::vector hit_buffer; + std::unique_ptr expr_engine; + XPhase evaluation_phase = XPhase::RisingStable; + + XRegistrationHandle Allocate(); + void EvaluatePhase(XPhase phase); + bool HasArmedPhase(XPhase phase) const; + void AppendHit(uint32_t slot, Watcher &watcher, XHitKind kind, + uint64_t value, uint64_t event_id = 0, + uint64_t x_mask = 0); + bool EvaluateCoverage(Watcher &watcher); + detail::PatternView CoverageSource(const Watcher &watcher) const { + if (watcher.kind == WatcherKind::Sequence || watcher.kind == WatcherKind::Fsm) + return detail::PatternView::From(watcher.program); + return {}; + } + void CheckCoverageHandle(XRegistrationHandle handle) const; + +public: + explicit XEngine(XClock *clock, size_t capacity = 1024); + explicit XEngine(XClock &clock, size_t capacity = 1024) + : XEngine(&clock, capacity) {} + ~XEngine(); + + XRegistrationHandle ArmEdge(XPhase phase, uint64_t source_id = 0); + XRegistrationHandle ArmClockCycles( + uint64_t cycles, XPhase phase = XPhase::RisingStable, + uint64_t source_id = 0); + XRegistrationHandle ArmValueEq( + XData *signal, uint64_t expected, + XPhase phase = XPhase::RisingStable, + XConditionMode mode = XConditionMode::Enter, + uint64_t source_id = 0); + XRegistrationHandle ArmValueEqBytes( + XData *signal, std::vector &expected, + XPhase phase, XConditionMode mode, uint64_t source_id); + XRegistrationHandle ArmValueChange( + XData *signal, XPhase phase = XPhase::RisingStable, + uint64_t source_id = 0); + XRegistrationHandle ArmSample( + XPhase phase = XPhase::RisingStable, uint64_t source_id = 0); + int ExprNewConst(uint64_t value); + int ExprNewSignal(XData *signal); + int ExprNewUnary(int op, int child); + int ExprNewBinary(int op, int lhs, int rhs); + int ExprNewCompare(int op, int lhs, int rhs); + int ExprNewCompareSigSig(int op, XData *lhs, XData *rhs); + int ExprNewCompareSigConstBytes( + int op, XData *lhs, std::vector &rhs); + int ExprNewCompareConstBytesSig( + int op, std::vector &lhs, XData *rhs); + int ExprNewMaskedCompareSigConstBytes( + XData *lhs, std::vector &value, std::vector &mask); + XRegistrationHandle ArmExpr( + int root, XPhase phase = XPhase::RisingStable, + XConditionMode mode = XConditionMode::Enter, + uint64_t source_id = 0); + XRegistrationHandle ArmSequence( + const std::vector &steps, + XPhase phase = XPhase::RisingStable, uint64_t source_id = 0); + XRegistrationHandle ArmFsm( + uint32_t state_count, uint32_t start_state, + const std::vector &transitions, + XPhase phase = XPhase::RisingStable, uint64_t source_id = 0); + static uint32_t CoverageVersion() { return 5; } + void AttachCoverage(XRegistrationHandle handle, + const std::vector &items, + const std::vector &bins, + int gate = -1, int abort = -1, + bool raise_illegal = true, size_t diagnostic_capacity = 1024, + bool overlap = false, size_t max_active = 1, bool diagnostics = false); + XCoverageSnapshot CoverageSnapshot(XRegistrationHandle handle, bool progress = false) const; + void ResetCoverage(XRegistrationHandle handle, bool counters = true); + size_t CoverageExecutionCount(XRegistrationHandle handle) const; + bool Disarm(XRegistrationHandle handle); + bool Rearm(XRegistrationHandle handle); + bool RearmSample(XRegistrationHandle handle); + XRunResult RunUntil( + uint64_t max_half_ticks, uint64_t max_wall_time_ns = 0, + uint32_t budget_check_interval = 64); + XRunResult SamplePhase(XPhase phase); + size_t ActiveCount() const; + size_t Capacity() const { return capacity; } + void ClearExecutionState(); + void Clear(); +}; + + +} // namespace xspcomm + +#endif diff --git a/include/xspcomm/xpattern.h b/include/xspcomm/xpattern.h index 905164b..0514d1f 100644 --- a/include/xspcomm/xpattern.h +++ b/include/xspcomm/xpattern.h @@ -2,6 +2,7 @@ #define XSPCOMM_XPATTERN_H #include +#include namespace xspcomm { @@ -34,6 +35,21 @@ struct XFsmTransition { bool trigger = false; }; +enum class XTriggerProgramKind : uint8_t { Expr = 0, Sequence = 1, Fsm = 2 }; + +// Immutable after registration; each execution keeps its own matching state. +struct XTriggerProgram { + XTriggerProgramKind kind = XTriggerProgramKind::Expr; + int root = -1; + std::vector steps; + std::vector transitions; + uint32_t state_count = 0; + uint32_t start_state = 0; + XConditionMode mode = XConditionMode::Enter; + bool overlap = true; + uint32_t max_active = 1; +}; + } // namespace xspcomm #endif diff --git a/include/xspcomm/xtrigger.h b/include/xspcomm/xtrigger.h index 45951e0..557fe29 100644 --- a/include/xspcomm/xtrigger.h +++ b/include/xspcomm/xtrigger.h @@ -1,212 +1,10 @@ -#ifndef __xspcomm_xtrigger_h__ -#define __xspcomm_xtrigger_h__ +#ifndef XSPCOMM_XTRIGGER_H +#define XSPCOMM_XTRIGGER_H -#include "xspcomm/xclock.h" -#include "xspcomm/xdata.h" -#include "xspcomm/xcoverage.h" -#include "xspcomm/detail/pattern.h" - -#include -#include -#include -#include +#include "xspcomm/xengine.h" namespace xspcomm { - -class ExprEngine; - -enum class XHitKind : uint8_t { - ClockFall = 0, - ClockRise = 1, - ClockCycles = 2, - Value = 3, - Condition = 4, - Fsm = 5, - ValueChange = 6, - DriveStable = 7, -}; - -enum class XStopReason : uint8_t { - RunLimit = 0, - TriggerHit = 1, - BackendStop = 2, - QuantumExpired = 3, - EdgeBarrier = 4, - UserPause = 5, - SimulationClose = 6, - CallbackError = 7, - BackendError = 8, -}; - -struct XRegistrationHandle { - uint32_t slot = std::numeric_limits::max(); - uint32_t generation = 0; - - bool IsValid() const { - return slot != std::numeric_limits::max(); - } -}; - -struct XBackendHit { - uint64_t event_id = 0; - uint64_t tick = 0; - uint64_t source_id = 0; - uint64_t value = 0; - uint64_t x_mask = 0; - uint32_t slot = 0; - uint32_t generation = 0; - XHitKind kind = XHitKind::ClockFall; - XPhase phase = XPhase::FallingStable; - uint16_t flags = 0; -}; - -struct XRunResult { - uint64_t advanced_ticks = 0; - XPhase stopped_phase = XPhase::RisingStable; - XStopReason stop_reason = XStopReason::RunLimit; - std::vector hits; - - bool IsPhaseBarrier() const { - return advanced_ticks != 0 || stopped_phase == XPhase::DriveStable; - } -}; - -class XTriggerEngine { - enum class WatcherKind : uint8_t { - Edge, - ClockCycles, - ValueEq, - ValueChange, - Sample, - Expr, - Sequence, - Fsm, - }; - - struct Watcher : detail::PatternState { - uint32_t generation = 0; - bool occupied = false; - bool armed = false; - WatcherKind kind = WatcherKind::Edge; - XPhase phase = XPhase::RisingStable; - uint64_t source_id = 0; - uint64_t remaining = 0; - uint64_t initial_count = 0; - XData *signal = nullptr; - uint64_t expected = 0; - uint64_t expected_x_mask = 0; - std::shared_ptr expected_wide; - std::vector expected_bytes; - std::vector expected_x_bytes; - XConditionMode condition_mode = XConditionMode::Enter; - bool last_condition = false; - int expr_root = -1; - std::vector sequence_steps; - std::vector fsm_transitions; - uint32_t fsm_state_count = 0; - uint32_t fsm_start_state = 0; - std::shared_ptr coverage; - - void ResetPattern() { - sequence_index = sequence_age = sequence_held = 0; - fsm_current_state = fsm_start_state; - remaining = initial_count; - } - }; - - XClock *clock = nullptr; - size_t capacity = 0; - uint64_t next_event_id = 1; - std::vector watchers; - std::vector free_slots; - std::vector hit_buffer; - std::unique_ptr expr_engine; - XPhase evaluation_phase = XPhase::RisingStable; - - XRegistrationHandle Allocate(); - void EvaluatePhase(XPhase phase); - bool HasArmedPhase(XPhase phase) const; - void AppendHit(uint32_t slot, Watcher &watcher, XHitKind kind, - uint64_t value, uint64_t event_id = 0, - uint64_t x_mask = 0); - bool EvaluateCoverage(Watcher &watcher); - detail::PatternView CoverageSource(const Watcher &watcher) const - { - if (watcher.kind == WatcherKind::Sequence) return {&watcher.sequence_steps}; - if (watcher.kind == WatcherKind::Fsm) return {nullptr, &watcher.fsm_transitions, watcher.fsm_start_state}; - return {}; - } - void CheckCoverageHandle(XRegistrationHandle handle) const; - -public: - explicit XTriggerEngine(XClock *clock, size_t capacity = 1024); - explicit XTriggerEngine(XClock &clock, size_t capacity = 1024) - : XTriggerEngine(&clock, capacity) {} - ~XTriggerEngine(); - - XRegistrationHandle ArmEdge(XPhase phase, uint64_t source_id = 0); - XRegistrationHandle ArmClockCycles( - uint64_t cycles, XPhase phase = XPhase::RisingStable, - uint64_t source_id = 0); - XRegistrationHandle ArmValueEq( - XData *signal, uint64_t expected, - XPhase phase = XPhase::RisingStable, - XConditionMode mode = XConditionMode::Enter, - uint64_t source_id = 0); - XRegistrationHandle ArmValueEqBytes( - XData *signal, std::vector &expected, - XPhase phase, XConditionMode mode, uint64_t source_id); - XRegistrationHandle ArmValueChange( - XData *signal, XPhase phase = XPhase::RisingStable, - uint64_t source_id = 0); - XRegistrationHandle ArmSample( - XPhase phase = XPhase::RisingStable, uint64_t source_id = 0); - int ExprNewConst(uint64_t value); - int ExprNewSignal(XData *signal); - int ExprNewUnary(int op, int child); - int ExprNewBinary(int op, int lhs, int rhs); - int ExprNewCompare(int op, int lhs, int rhs); - int ExprNewCompareSigSig(int op, XData *lhs, XData *rhs); - int ExprNewCompareSigConstBytes( - int op, XData *lhs, std::vector &rhs); - int ExprNewCompareConstBytesSig( - int op, std::vector &lhs, XData *rhs); - int ExprNewMaskedCompareSigConstBytes( - XData *lhs, std::vector &value, std::vector &mask); - XRegistrationHandle ArmExpr( - int root, XPhase phase = XPhase::RisingStable, - XConditionMode mode = XConditionMode::Enter, - uint64_t source_id = 0); - XRegistrationHandle ArmSequence( - const std::vector &steps, - XPhase phase = XPhase::RisingStable, uint64_t source_id = 0); - XRegistrationHandle ArmFsm( - uint32_t state_count, uint32_t start_state, - const std::vector &transitions, - XPhase phase = XPhase::RisingStable, uint64_t source_id = 0); - static uint32_t CoverageVersion() { return 4; } - void AttachCoverage(XRegistrationHandle handle, - const std::vector &items, - const std::vector &bins, - int gate = -1, int abort = -1, - bool raise_illegal = true, size_t diagnostic_capacity = 1024, - bool overlap = false, size_t max_active = 1, bool diagnostics = false); - XCoverageSnapshot CoverageSnapshot(XRegistrationHandle handle, bool progress = false) const; - void ResetCoverage(XRegistrationHandle handle, bool counters = true); - size_t CoverageExecutionCount(XRegistrationHandle handle) const; - bool Disarm(XRegistrationHandle handle); - bool Rearm(XRegistrationHandle handle); - bool RearmSample(XRegistrationHandle handle); - XRunResult RunUntil( - uint64_t max_half_ticks, uint64_t max_wall_time_ns = 0, - uint32_t budget_check_interval = 64); - XRunResult SamplePhase(XPhase phase); - size_t ActiveCount() const; - size_t Capacity() const { return capacity; } - void ClearExecutionState(); - void Clear(); -}; - -} // namespace xspcomm +using XTriggerEngine = XEngine; +} #endif diff --git a/include/xspcomm/xutil.h b/include/xspcomm/xutil.h index bb88d32..a10dadc 100644 --- a/include/xspcomm/xutil.h +++ b/include/xspcomm/xutil.h @@ -266,15 +266,13 @@ std::string inline removeSuffix(const std::string& str, const std::string& suffi inline void big_shift(int* a, int size, int shift) { if (size <= 0) return; int intBits = sizeof(int) * 8; - // Unsigned arithmetic also handles the magnitude of INT_MIN. - unsigned int absShift = shift < 0 ? 0U - static_cast(shift) - : static_cast(shift); - int numInts = absShift / intBits; + const int64_t distance = shift < 0 ? -static_cast(shift) : shift; + const int numInts = distance / intBits; if (numInts >= size) { std::memset(a, 0, size * sizeof(int)); return; } - int bitShift = absShift % intBits; + int bitShift = distance % intBits; if (shift < 0) { if (numInts > 0) { std::memmove(a + numInts, a, (size - numInts) * sizeof(int)); diff --git a/src/common/lifetime.cpp b/src/common/lifetime.cpp new file mode 100644 index 0000000..38c4e94 --- /dev/null +++ b/src/common/lifetime.cpp @@ -0,0 +1,8 @@ +#include "xspcomm/detail/lifetime.h" + +namespace xspcomm::detail { + +Lifetime::Lifetime() : token(std::make_shared(0)) {} +Lifetime::Lifetime(const Lifetime &) : Lifetime() {} + +} // namespace xspcomm::detail diff --git a/src/common/memory.cpp b/src/common/memory.cpp index 459f73b..a180d2c 100644 --- a/src/common/memory.cpp +++ b/src/common/memory.cpp @@ -1,42 +1,49 @@ #include "xspcomm/common/memory.h" namespace xspcomm { +namespace { -uint64_t GetFromU64Array(uint64_t address, int index){ - return ((uint64_t *)address)[index]; -} -uint32_t GetFromU32Array(uint64_t address, int index){ - return ((uint32_t *)address)[index]; -} -uint8_t GetFromU8Array(uint64_t address, int index){ - return ((uint8_t *)address)[index]; +template +unsigned char *ArrayElement(uint64_t address, int index) { + auto *base = reinterpret_cast(address); + return base + ptrdiff_t(index) * ptrdiff_t(sizeof(T)); } -uint64_t GetFromU64Array(unsigned long long * address, int index){ - return address[index]; +} // namespace + +uint64_t GetFromU64Array(uint64_t address, int index) { + return memory::Load(ArrayElement(address, index)); } -uint32_t GetFromU32Array(unsigned int * address, int index){ - return address[index]; +uint32_t GetFromU32Array(uint64_t address, int index) { + return memory::Load(ArrayElement(address, index)); } -uint8_t GetFromU8Array(unsigned char * address, int index){ - return address[index]; +uint8_t GetFromU8Array(uint64_t address, int index) { + return memory::Load(ArrayElement(address, index)); } -// Get Array Item -void SetU64Array(uint64_t address, int index, uint64_t data){((uint64_t*)address)[index]=data;} -void SetU32Array(uint64_t address, int index, uint32_t data){((uint32_t*)address)[index]=data;} -void SetU8Array(uint64_t address, int index, uint8_t data){((uint8_t*)address)[index] =data;} +uint64_t GetFromU64Array(unsigned long long *address, int index) { return memory::Load(address + index); } +uint32_t GetFromU32Array(unsigned int *address, int index) { return memory::Load(address + index); } +uint8_t GetFromU8Array(unsigned char *address, int index) { return memory::Load(address + index); } -void SetU64Array(unsigned long long *address, int index, uint64_t data){address[index]=data;} -void SetU32Array(unsigned int *address, int index, uint32_t data){address[index]=data;} -void SetU8Array(unsigned char *address, int index, uint8_t data){address[index]=data;} +void SetU64Array(uint64_t address, int index, uint64_t data) { + memory::Store(ArrayElement(address, index), data); +} +void SetU32Array(uint64_t address, int index, uint32_t data) { + memory::Store(ArrayElement(address, index), data); +} +void SetU8Array(uint64_t address, int index, uint8_t data) { + memory::Store(ArrayElement(address, index), data); +} -uint64_t U64PtrAsU64(unsigned long long *p){return (uint64_t)p;} -uint64_t U32PtrAsU64(unsigned int *p){return (uint64_t)p;} -uint64_t U8PtrAsU64(unsigned char *p){return (uint64_t)p;} +void SetU64Array(unsigned long long *address, int index, uint64_t data) { memory::Store(address + index, data); } +void SetU32Array(unsigned int *address, int index, uint32_t data) { memory::Store(address + index, data); } +void SetU8Array(unsigned char *address, int index, uint8_t data) { memory::Store(address + index, data); } -unsigned long long *U64AsU64Ptr(uint64_t p){return (unsigned long long *)p;} -unsigned int *U64AsU32Ptr(uint64_t p){return (unsigned int *)p;} -unsigned char *U64AsU8Ptr(uint64_t p){return (unsigned char *)p;} +uint64_t U64PtrAsU64(unsigned long long *p) { return reinterpret_cast(p); } +uint64_t U32PtrAsU64(unsigned int *p) { return reinterpret_cast(p); } +uint64_t U8PtrAsU64(unsigned char *p) { return reinterpret_cast(p); } +unsigned long long *U64AsU64Ptr(uint64_t p) { return reinterpret_cast(p); } +unsigned int *U64AsU32Ptr(uint64_t p) { return reinterpret_cast(p); } +unsigned char *U64AsU8Ptr(uint64_t p) { return reinterpret_cast(p); } } // namespace xspcomm diff --git a/src/comuse/callback.cpp b/src/comuse/callback.cpp index 2aa1519..b784710 100644 --- a/src/comuse/callback.cpp +++ b/src/comuse/callback.cpp @@ -1,8 +1,41 @@ #include "xspcomm/xcomuse/callback.h" #include "xspcomm/xclock.h" +#include namespace xspcomm { +ComUseStepCb::~ComUseStepCb() { Detach(); } + +void ComUseStepCb::Attach(XClock *clock, bool rising) { + if (!clock) throw std::invalid_argument("callback clock must not be null"); + if (clock->in_callback) throw std::logic_error("cannot attach while clock callbacks are running"); + for (const auto ®istration : registrations) + if (registration.clock == clock && registration.rising == rising && !registration.lifetime.expired()) return; + const std::string description = "ComUseStepCb:" + std::to_string(CSelf()) + (rising ? ":rise" : ":fall"); + Registration registration{clock, clock->callback_lifetime.Observe(), rising, description}; + registrations.push_back(registration); + try { + const auto alive = lifetime.Observe(); + auto callback = [alive, this](uint64_t cycle, void *) { + if (!alive.expired()) Cb(cycle, this); + }; + if (rising) clock->StepRis(callback, nullptr, description); + else clock->StepFal(callback, nullptr, description); + } catch (...) { + registrations.pop_back(); + throw; + } +} + +void ComUseStepCb::Detach() { + for (const auto ®istration : registrations) { + if (registration.lifetime.expired()) continue; + if (registration.rising) registration.clock->RemoveStepRisCbByDesc(registration.description); + else registration.clock->RemoveStepFalCbByDesc(registration.description); + } + registrations.clear(); +} + u_int64_t ComUseStepCb::GetCb(){ return (u_int64_t)ComUseStepCb::Cb; } diff --git a/src/comuse/condition.cpp b/src/comuse/condition.cpp index 8725e2f..7bc8c65 100644 --- a/src/comuse/condition.cpp +++ b/src/comuse/condition.cpp @@ -1,5 +1,6 @@ #include "xspcomm/xcomuse/condition.h" #include "xspcomm/xclock.h" +#include "xspcomm/xexpr.h" namespace xspcomm { @@ -8,6 +9,9 @@ void ComUseCondCheck::BindXClock(XClock *clk){ } void ComUseCondCheck::SetCondition(std::string unique_name, XData* pin, XData* val, ComUseCondCmp cmp, XData *valid, XData *valid_value, xfunction func, uint64_t arg){ Assert(func == nullptr, "func type error: %d", func.func == nullptr); + RemoveExpression(unique_name); + if (!pin || !val || !SelectXDataCmpFn(cmp) || (valid && !valid_value)) + throw std::invalid_argument("invalid XData comparison"); RemoveUint64Cond(this->cond_idx_uint64, this->cond_vec_uint64, unique_name); auto it = this->cond_idx_xdata.find(unique_name); if(it == this->cond_idx_xdata.end()){ @@ -39,6 +43,10 @@ void ComUseCondCheck::SetCondition(std::string unique_name, XData* pin, XData* v } }; void ComUseCondCheck::SetCondition(std::string unique_name, uint64_t pin_ptr, uint64_t val_ptr, ComUseCondCmp cmp, int bytes, uint64_t valid_ptr, uint64_t valid_value_ptr, int valid_bytes, xfunction func, uint64_t arg){ + RemoveExpression(unique_name); + if (!SelectPtrCmpFn(cmp) || (!func && (!pin_ptr || !val_ptr || bytes <= 0)) || + (valid_ptr && (!valid_value_ptr || valid_bytes <= 0))) + throw std::invalid_argument("invalid pointer comparison"); RemoveXDataCond(this->cond_idx_xdata, this->cond_vec_xdata, unique_name); auto it = this->cond_idx_uint64.find(unique_name); if(it == this->cond_idx_uint64.end()){ @@ -74,6 +82,7 @@ void ComUseCondCheck::SetCondition(std::string unique_name, uint64_t pin_ptr, ui } }; void ComUseCondCheck::RemoveCondition(std::string unique_name){ + RemoveExpression(unique_name); RemoveXDataCond(this->cond_idx_xdata, this->cond_vec_xdata, unique_name); RemoveUint64Cond(this->cond_idx_uint64, this->cond_vec_uint64, unique_name); }; @@ -85,6 +94,7 @@ std::vector ComUseCondCheck::GetTriggeredConditionKeys(){ for(auto &e : this->cond_vec_uint64){ if(e.triggered)ret.push_back(e.name); } + for (const auto &entry : expressions) if (entry.triggered) ret.push_back(entry.name); return ret; } std::map ComUseCondCheck::ListCondition(){ @@ -95,6 +105,7 @@ std::map ComUseCondCheck::ListCondition(){ for(auto &e : this->cond_vec_uint64){ ret[e.name] = e.triggered ? true : false; } + for (const auto &entry : expressions) ret[entry.name] = entry.triggered; return ret; } ComUseCondCmp ComUseCondCheck::GetValidCmpMode(std::string unique_name){ @@ -110,6 +121,7 @@ ComUseCondCmp ComUseCondCheck::GetValidCmpMode(std::string unique_name){ return ComUseCondCmp::EQ; // default } void ComUseCondCheck::SetValidCmpMode(std::string unique_name, ComUseCondCmp cmp){ + if (!SelectXDataCmpFn(cmp)) throw std::invalid_argument("invalid comparison operation"); auto it = this->cond_idx_xdata.find(unique_name); if(it != this->cond_idx_xdata.end()){ auto &entry = this->cond_vec_xdata[it->second]; @@ -128,6 +140,8 @@ void ComUseCondCheck::SetValidCmpMode(std::string unique_name, ComUseCondCmp cmp } void ComUseCondCheck::ClearClock(){this->clk_list.clear();} void ComUseCondCheck::ClearCondition(){ + expressions.clear(); + expression_index.clear(); this->cond_idx_xdata.clear(); this->cond_idx_uint64.clear(); this->cond_vec_xdata.clear(); @@ -141,86 +155,80 @@ xfunction ComUseCondCheck::AsPtrXFunc(uint64 xfunction ret = (bool (*)(uint64_t, uint64_t, uint64_t))func; return ret; } -void ComUseCondCheck::Call(){ - if (likely(this->cond_vec_xdata.empty() && this->cond_vec_uint64.empty())) { - return; +namespace { +template +bool RemoveEntry(Index &index, Entries &entries, const std::string &name) { + const auto found = index.find(name); + if (found == index.end()) return false; + const size_t at = found->second; + if (at + 1 != entries.size()) { + entries[at] = std::move(entries.back()); + index[entries[at].name] = at; } - // check XData condition - bool is_triggered = false; - for(auto &entry : this->cond_vec_xdata){ - auto pin = entry.pin; - auto val = entry.val; - auto valid = entry.valid; - auto valid_value = entry.valid_value; - auto func = entry.func; - auto arg = entry.arg; - auto cmp_fn = entry.cmp_fn; - auto valid_cmp_fn = entry.valid_cmp_fn; - // not triggered - entry.triggered = 0; - if (unlikely(valid != nullptr)){ - Assert(valid_value != nullptr, "valid_value is null"); - if(unlikely(!valid_cmp_fn || !valid_cmp_fn(valid, valid_value))) continue; - } - // check cmp - if(unlikely(func)){ - if(func(pin, val, arg)){ - entry.triggered = 1; - } - }else{ - if(unlikely(!cmp_fn)){ - Assert(0, "cmp xdata type error"); - }else if(cmp_fn(pin, val)){ - entry.triggered = 1; - } - } - if(unlikely(!is_triggered && entry.triggered)){ - for(auto &clk : this->clk_list){ - clk->Disable(); - } - is_triggered = true; - this->IncCbCount(); - } + entries.pop_back(); + index.erase(found); + return true; +} + +template +void CheckEntries(Entries &entries, Evaluate evaluate, OnHit on_hit) { + for (auto &entry : entries) { + entry.triggered = evaluate(entry); + if (entry.triggered) on_hit(); } - // check uint64_t condition - for(auto &entry : this->cond_vec_uint64){ - auto pin_ptr = entry.pin_ptr; - auto val_ptr = entry.val_ptr; - auto bytes = entry.bytes; - auto valid_ptr = entry.valid_ptr; - auto valid_value_ptr = entry.valid_value_ptr; - auto valid_bytes = entry.valid_bytes; - auto func = entry.func; - auto arg = entry.arg; - auto cmp_fn = entry.cmp_fn; - auto valid_cmp_fn = entry.valid_cmp_fn; - // not triggered - entry.triggered = 0; - if (unlikely(valid_ptr != 0)){ - Assert(valid_value_ptr != 0, "valid_value is null"); - if(unlikely(!valid_cmp_fn || !valid_cmp_fn(this, valid_ptr, valid_value_ptr, valid_bytes))) continue; - } - // check cmp - if(unlikely(func)){ - if(func(pin_ptr, val_ptr, arg)){ - entry.triggered = 1; - } - }else{ - if(unlikely(!cmp_fn)){ - Assert(0, "cmp ptr type error"); - }else if(cmp_fn(this, pin_ptr, val_ptr, bytes)){ - entry.triggered = 1; - } - } - if(unlikely(!is_triggered && entry.triggered)){ - for(auto &clk : this->clk_list){ - clk->Disable(); - } - is_triggered = true; - this->IncCbCount(); - } +} +} + +inline bool ComUseCondCheck::Evaluate(CondXDataEntry &entry) { + if (entry.valid && !entry.valid_cmp_fn(entry.valid, entry.valid_value)) return false; + return entry.func ? entry.func(entry.pin, entry.val, entry.arg) + : entry.cmp_fn(entry.pin, entry.val); +} + +inline bool ComUseCondCheck::Evaluate(CondUint64Entry &entry) { + if (entry.valid_ptr && !entry.valid_cmp_fn(this, entry.valid_ptr, entry.valid_value_ptr, entry.valid_bytes)) + return false; + return entry.func ? entry.func(entry.pin_ptr, entry.val_ptr, entry.arg) + : entry.cmp_fn(this, entry.pin_ptr, entry.val_ptr, entry.bytes); +} + +inline bool ComUseCondCheck::Evaluate(ExprEntry &entry) { + return entry.root >= 0 && entry.engine->Eval(entry.root) != 0; +} + +void ComUseCondCheck::Call() { + // Keep comparisons specialized at registration; no per-sample allocation. + auto evaluate = [this](auto &entry) { return Evaluate(entry); }; + bool triggered = false; + auto on_hit = [&] { + if (triggered) return; + for (auto *clock : clk_list) clock->Disable(); + IncCbCount(); + triggered = true; + }; + CheckEntries(cond_vec_xdata, evaluate, on_hit); + CheckEntries(cond_vec_uint64, evaluate, on_hit); + CheckEntries(expressions, evaluate, on_hit); +} + +void ComUseCondCheck::SetExpression(std::string name, ExprEngine &engine, int root) { + RemoveXDataCond(cond_idx_xdata, cond_vec_xdata, name); + RemoveUint64Cond(cond_idx_uint64, cond_vec_uint64, name); + const auto found = expression_index.find(name); + if (found == expression_index.end()) { + expression_index[name] = expressions.size(); + expressions.push_back({std::move(name), &engine, root}); + } else { + auto &entry = expressions[found->second]; + entry.engine = &engine; + entry.root = root; } -}; +} + +void ComUseCondCheck::RemoveExpression(const std::string &name) { + RemoveEntry(expression_index, expressions, name); +} + ComUseCondCheck::XDataCmpFn ComUseCondCheck::SelectXDataCmpFn(ComUseCondCmp cmp){ switch (cmp) { case ComUseCondCmp::EQ: return &ComUseCondCheck::XDataCmp; @@ -245,36 +253,14 @@ ComUseCondCheck::PtrCmpFn ComUseCondCheck::SelectPtrCmpFn(ComUseCondCmp cmp){ } } -bool ComUseCondCheck::RemoveXDataCond(std::unordered_map &idx, - std::vector &vec, - const std::string &name){ - auto it = idx.find(name); - if(it == idx.end()) return false; - size_t at = it->second; - size_t last = vec.size() - 1; - if(at != last){ - vec[at] = std::move(vec[last]); - idx[vec[at].name] = at; - } - vec.pop_back(); - idx.erase(it); - return true; +bool ComUseCondCheck::RemoveXDataCond(std::unordered_map &index, + std::vector &entries, const std::string &name) { + return RemoveEntry(index, entries, name); } -bool ComUseCondCheck::RemoveUint64Cond(std::unordered_map &idx, - std::vector &vec, - const std::string &name){ - auto it = idx.find(name); - if(it == idx.end()) return false; - size_t at = it->second; - size_t last = vec.size() - 1; - if(at != last){ - vec[at] = std::move(vec[last]); - idx[vec[at].name] = at; - } - vec.pop_back(); - idx.erase(it); - return true; +bool ComUseCondCheck::RemoveUint64Cond(std::unordered_map &index, + std::vector &entries, const std::string &name) { + return RemoveEntry(index, entries, name); } } // namespace xspcomm diff --git a/src/comuse/expr.cpp b/src/comuse/expr.cpp index 776e0a1..17010da 100644 --- a/src/comuse/expr.cpp +++ b/src/comuse/expr.cpp @@ -3,10 +3,6 @@ namespace xspcomm { -void ComUseExprCheck::BindXClock(XClock *clk){ - this->clk_list.push_back(clk); -} - int ComUseExprCheck::ExprNewConst(uint64_t v){ return this->engine.NewConst(v); } @@ -16,37 +12,37 @@ int ComUseExprCheck::ExprNewSignal(XData* sig){ } int ComUseExprCheck::ExprNewUnary(int op, int child){ - return this->engine.NewUnary((ExprOp)op, child); + return this->engine.NewUnary(static_cast(op), child); } int ComUseExprCheck::ExprNewBinary(int op, int lhs, int rhs){ - return this->engine.NewBinary((ExprOp)op, lhs, rhs); + return this->engine.NewBinary(static_cast(op), lhs, rhs); } int ComUseExprCheck::ExprNewCompare(int op, int lhs, int rhs){ - return this->engine.NewCompare((ExprOp)op, lhs, rhs); + return this->engine.NewCompare(static_cast(op), lhs, rhs); } int ComUseExprCheck::ExprNewCompareSigSig(int op, XData* lhs, XData* rhs){ - return this->engine.NewCompareSigSig((ExprOp)op, lhs, rhs); + return this->engine.NewCompareSigSig(static_cast(op), lhs, rhs); } int ComUseExprCheck::ExprNewCompareSigConst(int op, XData* lhs, uint64_t rhs){ - return this->engine.NewCompareSigConst((ExprOp)op, lhs, rhs); + return this->engine.NewCompareSigConst(static_cast(op), lhs, rhs); } int ComUseExprCheck::ExprNewCompareConstSig(int op, uint64_t lhs, XData* rhs){ - return this->engine.NewCompareConstSig((ExprOp)op, lhs, rhs); + return this->engine.NewCompareConstSig(static_cast(op), lhs, rhs); } int ComUseExprCheck::ExprNewCompareSigConstBytes( int op, XData* lhs, std::vector &rhs){ - return this->engine.NewCompareSigConstBytes((ExprOp)op, lhs, rhs); + return this->engine.NewCompareSigConstBytes(static_cast(op), lhs, rhs); } int ComUseExprCheck::ExprNewCompareConstBytesSig( int op, std::vector &lhs, XData* rhs){ - return this->engine.NewCompareConstBytesSig((ExprOp)op, lhs, rhs); + return this->engine.NewCompareConstBytesSig(static_cast(op), lhs, rhs); } int ComUseExprCheck::CompileExpr(std::string expr, XSignalCFG* cfg){ @@ -58,78 +54,22 @@ int ComUseExprCheck::CompileExpr(std::string expr, XSignalCFG* cfg){ } } -void ComUseExprCheck::SetExpr(std::string name, int root){ - auto it = this->expr_index.find(name); - if(it == this->expr_index.end()){ - ExprItem item; - item.name = name; - item.root = root; - this->expr_list.push_back(std::move(item)); - this->expr_index[name] = this->expr_list.size() - 1; - }else{ - this->expr_list[it->second].root = root; - } +void ComUseExprCheck::SetExpr(std::string name, int root) { + SetExpression(std::move(name), engine, root); } -void ComUseExprCheck::RemoveExpr(std::string name){ - auto it = this->expr_index.find(name); - if(it == this->expr_index.end()) return; - size_t at = it->second; - size_t last = this->expr_list.size() - 1; - if(at != last){ - this->expr_list[at] = std::move(this->expr_list[last]); - this->expr_index[this->expr_list[at].name] = at; - } - this->expr_list.pop_back(); - this->expr_index.erase(it); -} +void ComUseExprCheck::RemoveExpr(std::string name) { RemoveCondition(std::move(name)); } +std::map ComUseExprCheck::ListExpr() { return ListCondition(); } +std::vector ComUseExprCheck::GetTriggeredExprKeys() { return GetTriggeredConditionKeys(); } -std::map ComUseExprCheck::ListExpr(){ - std::map ret; - for(const auto &item : this->expr_list){ - ret[item.name] = (item.last_trigger_cycle == this->last_eval_cycle); - } - return ret; +void ComUseExprCheck::ClearExpr() { + ClearCondition(); + engine.Clear(); } -std::vector ComUseExprCheck::GetTriggeredExprKeys(){ - std::vector ret; - for(const auto &item : this->expr_list){ - if(item.last_trigger_cycle == this->last_eval_cycle){ - ret.push_back(item.name); - } - } - return ret; -} - -void ComUseExprCheck::ClearExpr(){ - this->expr_index.clear(); - this->expr_list.clear(); - this->last_eval_cycle = 0; - this->engine.Clear(); -} - -void ComUseExprCheck::Call(){ - if (likely(this->expr_list.empty())) { - return; - } - bool triggered = false; - this->engine.SetCycle(this->cycle); - this->last_eval_cycle = this->cycle; - for(auto &item : this->expr_list){ - if(item.root < 0) continue; - uint64_t v = this->engine.Eval(item.root); - if(v != 0){ - item.last_trigger_cycle = this->last_eval_cycle; - if(!triggered){ - for(auto &clk : this->clk_list){ - clk->Disable(); - } - triggered = true; - this->IncCbCount(); - } - } - } +void ComUseExprCheck::Call() { + engine.SetCycle(cycle); + ComUseCondCheck::Call(); } } // namespace xspcomm diff --git a/src/comuse/range.cpp b/src/comuse/range.cpp index c83675e..419e3ad 100644 --- a/src/comuse/range.cpp +++ b/src/comuse/range.cpp @@ -8,8 +8,8 @@ namespace xspcomm { template bool ComUseRangeCheck::Compare(uint64_t a, uint64_t b, uint64_t self) { const auto* check = reinterpret_cast(self); - const auto target = compare::UIntBytes::Load(reinterpret_cast(a)); - const auto center = compare::UIntBytes::Load(reinterpret_cast(b)); + const auto target = memory::UIntBytes::Load(reinterpret_cast(a)); + const auto center = memory::UIntBytes::Load(reinterpret_cast(b)); return cmp(target, center, check->range); } @@ -21,8 +21,8 @@ const ComUseRangeCheck::ArrayCompare ComUseRangeCheck::comparisons[] = { bool ComUseRangeCheck::ArrayWideCmp(uint64_t a, uint64_t b, uint64_t self) { const auto* check = reinterpret_cast(self); const size_t bytes = check->bytes; - const compare::ByteWords target{reinterpret_cast(a), bytes}; - const compare::ByteWords center{reinterpret_cast(b), bytes}; + const memory::ByteWords target{reinterpret_cast(a), bytes}; + const memory::ByteWords center{reinterpret_cast(b), bytes}; return compare::WithinRange(target.Count(), check->range, target, center); } diff --git a/src/core/clock.cpp b/src/core/clock.cpp index 301a8c1..8365c57 100644 --- a/src/core/clock.cpp +++ b/src/core/clock.cpp @@ -55,9 +55,15 @@ void XClock::_call_back(std::vector &list) } this->in_callback = true; for (auto &e : list) { - e.func(this->clk, e.args); + if (e.func) e.func(this->clk, e.args); } this->in_callback = false; + if (callbacks_removed) { + for (auto *callbacks : {&list_call_back_ris, &list_call_back_fal}) + callbacks->erase(std::remove_if(callbacks->begin(), callbacks->end(), + [](const auto &callback) { return !callback.func; }), callbacks->end()); + callbacks_removed = false; + } } void XClock::_add_cb(std::vector &cblist, xfunction func, void *args, @@ -349,29 +355,24 @@ int XClock::_remove_step_cb_by(int type, int group, std::string desc, xfunction< int remove_count = 0; std::vector *vec = &this->list_call_back_ris; if(group != 0)vec = &this->list_call_back_fal; - remove_count = (int)vec->size(); - vec->erase( - std::remove_if(vec->begin(), vec->end(), - [&func, &desc, &type](const auto& x) { - switch (type) - { - case 0: - return x.func == func; - break; - case 1: - return x.desc == desc; - break; - case 2: - return x.func==func && x.desc == desc; - break; - default: - break; - } - return false; - }), - vec->end() - ); - return remove_count - (int)vec->size(); + auto matches = [&](const auto &callback) { + if (!callback.func) return false; + if (type == 0) return callback.func == func; + if (type == 1) return callback.desc == desc; + return callback.func == func && callback.desc == desc; + }; + if (in_callback) { + for (auto &callback : *vec) if (matches(callback)) { + callbacks_removed = true; + callback.func = nullptr; + ++remove_count; + } + } else { + const auto previous = vec->size(); + vec->erase(std::remove_if(vec->begin(), vec->end(), matches), vec->end()); + remove_count = previous - vec->size(); + } + return remove_count; } void XClock::_fal_pins(){ diff --git a/src/core/data_backends.cpp b/src/core/data_backends.cpp index 0c34c1b..37a7e9c 100644 --- a/src/core/data_backends.cpp +++ b/src/core/data_backends.cpp @@ -1,82 +1,10 @@ #include "xspcomm/xdata.h" #include "xspcomm/xexpr.h" -#include -#include +#include "xspcomm/detail/data/native_memory.h" namespace xspcomm { -namespace { -// Count == 0 uses a runtime loop; fixed counts are expanded at compile time. -template -struct VecMemoryIO { - static_assert(std::is_same_v || - std::is_same_v || - std::is_same_v); - - static T Load(const unsigned char* src) { - T value; - std::memcpy(&value, src, sizeof(T)); - return value; - } - - static void Store(unsigned char* dst, uint32_t value) { - const T native = static_cast(value); - std::memcpy(dst, &native, sizeof(T)); - } - - template - static void ReadFixed(xsvLogicVecVal* dst, const unsigned char* src, - std::index_sequence) { - ((dst[I].aval = static_cast(Load(src + I * sizeof(T)))), ...); - } - - template - static void WriteFixed(unsigned char* dst, const xsvLogicVecVal* src, - std::index_sequence) { - (Store(dst + I * sizeof(T), src[I].aval), ...); - } - - static void Read(xsvLogicVecVal* dst, const void* src, size_t count) { - const auto* bytes = static_cast(src); - const size_t words = sizeof(T) < sizeof(uint32_t) ? 1 : count; - if constexpr (Count != 0) { - // ReInit preserves callbacks, so the current width can differ. - if (likely(words == Count)) { - ReadFixed(dst, bytes, std::make_index_sequence{}); - return; - } - } - for (size_t i = 0; i < words; ++i) - dst[i].aval = static_cast(Load(bytes + i * sizeof(T))); - } - - static void Write(void* dst, const xsvLogicVecVal* src, size_t count) { - auto* bytes = static_cast(dst); - const size_t words = sizeof(T) < sizeof(uint32_t) ? 1 : count; - if constexpr (Count != 0) { - if (likely(words == Count)) { - WriteFixed(bytes, src, std::make_index_sequence{}); - return; - } - } - for (size_t i = 0; i < words; ++i) - Store(bytes + i * sizeof(T), src[i].aval); - } - - using VecCallback = xfunction; - static void Bind(uint64_t address, const uint32_t& count, - VecCallback& read, VecCallback& write) { - read = [address, &count](xsvLogicVecVal* dst) { - Read(dst, reinterpret_cast(address), count); - }; - write = [address, &count](const xsvLogicVecVal* src) { - Write(reinterpret_cast(address), src, count); - }; - } -}; -} // namespace - void XData::BindDPIPtr(uint64_t read_ptr, uint64_t write_ptr) { if (this->mWidth > 0) { @@ -144,22 +72,22 @@ void XData::BindNativeData(uint64_t pdata){ *native = value; }; } else if (this->mWidth <= 8) { - VecMemoryIO::Bind(pdata, this->vecSize, this->vecRead, this->vecWrite); + detail::VecMemoryIO::Bind(pdata, this->vecSize, this->vecRead, this->vecWrite); } else if (this->mWidth <= 16) { - VecMemoryIO::Bind(pdata, this->vecSize, this->vecRead, this->vecWrite); + detail::VecMemoryIO::Bind(pdata, this->vecSize, this->vecRead, this->vecWrite); } else { #define XCOMM_NATIVE_WORD_COUNTS(M) \ M(1) M(2) M(3) M(4) M(5) M(6) M(7) M(8) \ M(9) M(10) M(11) M(12) M(13) M(14) M(15) M(16) #define XCOMM_BIND_NATIVE_CASE(N) \ case N: \ - VecMemoryIO::Bind(pdata, this->vecSize, this->vecRead, this->vecWrite); \ + detail::VecMemoryIO::Bind(pdata, this->vecSize, this->vecRead, this->vecWrite); \ break; switch (this->vecSize) { XCOMM_NATIVE_WORD_COUNTS(XCOMM_BIND_NATIVE_CASE) default: - VecMemoryIO::Bind(pdata, this->vecSize, this->vecRead, this->vecWrite); + detail::VecMemoryIO::Bind(pdata, this->vecSize, this->vecRead, this->vecWrite); break; } #undef XCOMM_BIND_NATIVE_CASE diff --git a/src/coverage/engine.cpp b/src/coverage/engine.cpp index af005b3..d468d4e 100644 --- a/src/coverage/engine.cpp +++ b/src/coverage/engine.cpp @@ -1,4 +1,4 @@ -#include "state.h" +#include "xspcomm/detail/coverage/state.h" #include "xspcomm/xexpr.h" #include @@ -11,15 +11,15 @@ namespace detail { static bool SameCoverageExecution(const XCoverageBin &a, const XCoverageBin &b) { - if (std::tie(a.item, a.program_kind, a.root, a.mode, a.overlap, a.max_active, - a.state_count, a.start_state) != - std::tie(b.item, b.program_kind, b.root, b.mode, b.overlap, b.max_active, - b.state_count, b.start_state)) return false; - if (a.steps.size() != b.steps.size() || a.transitions.size() != b.transitions.size()) return false; - return std::equal(a.steps.begin(), a.steps.end(), b.steps.begin(), [](const auto &x, const auto &y) { + if (std::tie(a.item, a.program.kind, a.program.root, a.program.mode, a.program.overlap, a.program.max_active, + a.program.state_count, a.program.start_state) != + std::tie(b.item, b.program.kind, b.program.root, b.program.mode, b.program.overlap, b.program.max_active, + b.program.state_count, b.program.start_state)) return false; + if (a.program.steps.size() != b.program.steps.size() || a.program.transitions.size() != b.program.transitions.size()) return false; + return std::equal(a.program.steps.begin(), a.program.steps.end(), b.program.steps.begin(), [](const auto &x, const auto &y) { return std::tie(x.kind, x.root, x.minimum, x.maximum, x.cycles) == std::tie(y.kind, y.root, y.minimum, y.maximum, y.cycles); - }) && std::equal(a.transitions.begin(), a.transitions.end(), b.transitions.begin(), [](const auto &x, const auto &y) { + }) && std::equal(a.program.transitions.begin(), a.program.transitions.end(), b.program.transitions.begin(), [](const auto &x, const auto &y) { return std::tie(x.from_state, x.root, x.next_state, x.terminal_id, x.trigger) == std::tie(y.from_state, y.root, y.next_state, y.terminal_id, y.trigger); }); @@ -46,56 +46,56 @@ CoverageState::CoverageState(uint32_t generation, ExprEngine &expr, const Patter check(gate); check(abort); for (size_t i = 0; i < items.size(); ++i) { check(items[i].gate); - if (items[i].pattern && (items[i].signal || !items[i].dimensions.empty())) - throw std::invalid_argument("pattern point cannot have a signal or dimensions"); - if (!items[i].pattern && items[i].dimensions.empty() != (items[i].signal != nullptr)) - throw std::invalid_argument("coverage point requires a signal; cross must not have one"); + const auto &item = items[i]; + switch (item.kind) { + case XCoverageItemKind::Value: + if (!item.signal || !item.dimensions.empty()) + throw std::invalid_argument("value point requires only a signal"); + break; + case XCoverageItemKind::Pattern: + if (item.signal || !item.dimensions.empty()) + throw std::invalid_argument("pattern point cannot have a signal or dimensions"); + break; + case XCoverageItemKind::Cross: + if (item.signal || item.dimensions.empty()) + throw std::invalid_argument("cross requires only dimensions"); + break; + default: + throw std::invalid_argument("invalid coverage item kind"); + } for (auto d : items[i].dimensions) - if (d >= i || !items[d].dimensions.empty() || items[d].pattern) throw std::invalid_argument("invalid cross point"); + if (d >= i || items[d].kind != XCoverageItemKind::Value) throw std::invalid_argument("invalid cross point"); } for (const auto &bin : bins) { - if (bin.item >= items.size() || bin.kind > 3) throw std::invalid_argument("invalid coverage bin"); - check(bin.root); - const bool pattern = items[bin.item].pattern; - if (pattern) { - if (bin.kind == 3 || bin.program_kind > 2 || static_cast(bin.mode) > 2 || - !bin.max_active || (!bin.overlap && bin.max_active != 1)) - throw std::invalid_argument("invalid pattern bin options"); - if (bin.program_kind == 0 && (bin.root < 0 || !bin.steps.empty() || !bin.transitions.empty() || !bin.terminals.empty() || bin.overlap)) - throw std::invalid_argument("invalid expression pattern"); - if (bin.program_kind == 1 && (bin.root != -1 || bin.steps.empty() || !bin.transitions.empty() || !bin.terminals.empty())) - throw std::invalid_argument("invalid sequence pattern"); - if (bin.program_kind != 0 && bin.mode != XConditionMode::Enter) - throw std::invalid_argument("condition modes apply only to expression patterns"); - if (bin.program_kind == 2) { - if (bin.root != -1 || !bin.state_count || bin.start_state >= bin.state_count || !bin.steps.empty()) - throw std::invalid_argument("invalid FSM pattern"); - for (const auto &t : bin.transitions) { - check(t.root); - if (t.from_state >= bin.state_count || (!t.trigger && t.next_state >= bin.state_count)) - throw std::invalid_argument("invalid FSM transition"); - } - for (auto terminal : bin.terminals) - if (std::none_of(bin.transitions.begin(), bin.transitions.end(), [&](const auto &t) { return t.trigger && t.terminal_id == terminal; })) - throw std::invalid_argument("unknown FSM terminal"); - } + if (bin.item >= items.size() || static_cast(bin.kind) > 3) throw std::invalid_argument("invalid coverage bin"); + check(bin.program.root); + const bool pattern = items[bin.item].kind == XCoverageItemKind::Pattern; + if (!pattern && !bin.program.steps.empty() && bin.program.kind != XTriggerProgramKind::Sequence) + throw std::invalid_argument("transition bin requires a sequence program"); + if (pattern || bin.program.kind == XTriggerProgramKind::Sequence) { + if (!ProgramValidator::Validate(bin.program, expr)) throw std::invalid_argument("invalid trigger program"); + } else if (bin.program.kind != XTriggerProgramKind::Expr || !bin.program.steps.empty() || + !bin.program.transitions.empty() || bin.program.state_count || bin.program.start_state) { + throw std::invalid_argument("invalid value/cross program"); } - for (const auto &step : bin.steps) { - if (step.root < 0 || (!pattern && step.kind != XSequenceStepKind::Wait && step.kind != XSequenceStepKind::Next)) + if (pattern && bin.kind == XCoverageBinKind::Default) + throw std::invalid_argument("pattern bin cannot be default"); + if (!pattern && !bin.terminals.empty()) + throw std::invalid_argument("terminals require a pattern FSM"); + if (bin.program.kind != XTriggerProgramKind::Fsm && !bin.terminals.empty()) + throw std::invalid_argument("terminals require an FSM program"); + for (auto terminal : bin.terminals) + if (std::none_of(bin.program.transitions.begin(), bin.program.transitions.end(), + [&](const auto &transition) { return transition.trigger && transition.terminal_id == terminal; })) + throw std::invalid_argument("unknown FSM terminal"); + if (!pattern) for (const auto &step : bin.program.steps) + if (step.kind != XSequenceStepKind::Wait && step.kind != XSequenceStepKind::Next) throw std::invalid_argument("coverage transition requires adjacent steps"); - if (static_cast(step.kind) > 3 || - (step.kind == XSequenceStepKind::Within && step.minimum > step.maximum) || - (step.kind == XSequenceStepKind::Hold && !step.cycles)) - throw std::invalid_argument("invalid coverage sequence step"); - check(step.root); - } - if (pattern && bin.overlap && bin.program_kind == 1 && bin.steps.front().kind != XSequenceStepKind::Wait) - throw std::invalid_argument("overlapping Sequence must start with Wait"); const auto &dims = items[bin.item].dimensions; if (bin.dimensions.size() != dims.size()) throw std::invalid_argument("invalid cross tuple"); for (size_t j = 0; j < dims.size(); ++j) { auto d = bin.dimensions[j]; - if (d >= bins.size() || bins[d].kind != 0 || bins[d].item != dims[j]) + if (d >= bins.size() || bins[d].kind != XCoverageBinKind::Normal || bins[d].item != dims[j]) throw std::invalid_argument("invalid cross bin"); } } @@ -112,7 +112,8 @@ CoverageState::CoverageState(uint32_t generation, ExprEngine &expr, const Patter owners.resize(bins.size()); for (size_t i = 0; i < bins.size(); ++i) { owners[i] = i; - if (items[bins[i].item].pattern) { + if (bins[i].kind == XCoverageBinKind::Illegal) illegal_indices.push_back(i); + if ((items[bins[i].item].kind == XCoverageItemKind::Pattern)) { for (size_t previous = 0; previous < i; ++previous) { if (owners[previous] == previous && SameCoverageExecution(bins[i], bins[previous])) { owners[i] = previous; @@ -121,20 +122,12 @@ CoverageState::CoverageState(uint32_t generation, ExprEngine &expr, const Patter } } if (owners[i] != i) { - this->bins[i].steps.clear(); - this->bins[i].transitions.clear(); + this->bins[i].program.steps.clear(); + this->bins[i].program.transitions.clear(); continue; } const auto &bin = this->bins[i]; - PatternView program; - if (items[bin.item].pattern && bin.program_kind == 2) { - program.fsm = &bin.transitions; - program.start_state = bin.start_state; - } else if (!bin.steps.empty()) program.sequence = &bin.steps; - else { - program.root = bin.root; - program.mode = bin.mode; - } + const auto program = PatternView::From(bin.program); matchers[i] = PatternMatcher(program); } snapshot.illegal_bins.reserve(diagnostic_capacity); @@ -148,15 +141,20 @@ void CoverageState::SamplePattern(ExprEngine &expr, uint64_t tick) for (size_t i = 0; i < completed; ++i) Sample(expr, tick); } -inline size_t CoverageState::Advance(ExprEngine &expr, PatternMatcher &matcher, - size_t pattern, bool overlap, size_t max_active) +void CoverageState::RecordDiagnostics(size_t pattern, const MatchUpdate &update) { - const auto update = matcher.Advance(expr, overlap, max_active); Bump(pattern, Started, update.started); Bump(pattern, Completed, update.completed); Bump(pattern, Failed, update.failed); Bump(pattern, Expired, update.expired); Bump(pattern, PeakActive, update.peak_active); +} + +inline size_t CoverageState::Advance(ExprEngine &expr, PatternMatcher &matcher, + size_t pattern, bool overlap, size_t max_active) +{ + const auto update = matcher.Advance(expr, overlap, max_active); + if (!snapshot.diagnostics.empty()) RecordDiagnostics(pattern, update); if (update.capacity_exhausted) { snapshot.incomplete = true; throw std::overflow_error("coverage active pattern capacity exhausted"); @@ -168,7 +166,7 @@ size_t CoverageState::ExecutionCount() const { size_t count = 0; for (size_t b = 0; b < bins.size(); ++b) - count += items[bins[b].item].pattern && owners[b] == b; + count += (items[bins[b].item].kind == XCoverageItemKind::Pattern) && owners[b] == b; return count; } @@ -213,14 +211,90 @@ XCoverageSnapshot CoverageState::Snapshot(uint64_t tick, bool progress) const return snapshot; } +namespace { +inline bool Enabled(ExprEngine &expr, int root) { + return root < 0 || (expr.IsKnown(root) && expr.Eval(root)); +} +} + +void CoverageState::SampleCrossPoint(size_t i, size_t offset) +{ + const auto &item = items[i]; + const size_t base = 2 + i * 5; + uint64_t combinations = 1, hits = 0; + for (auto d : item.dimensions) { + const auto n = normal_counts[d]; + if (n && combinations > std::numeric_limits::max() / n) + throw std::overflow_error("coverage cross product overflow"); + combinations *= n; + } + for (size_t b = 0; b < bins.size(); ++b) { + const auto &bin = bins[b]; + if (bin.item != i) continue; + bool match = true; + for (auto d : bin.dimensions) match = match && matched[d]; + if (!match) continue; + delta[offset + b] = 1; ++hits; + if (bin.kind == XCoverageBinKind::Ignore) ++delta[base + 2]; + } + delta[base + 3] = combinations ? combinations - hits : 1; +} + +inline void CoverageState::SamplePatternPoint(ExprEngine &expr, size_t i, size_t offset) +{ + const size_t base = 2 + i * 5; + bool any = false; + for (size_t b = 0; b < bins.size(); ++b) { + const auto &bin = bins[b]; + if (bin.item != i) continue; + auto &matcher = matchers[owners[b]]; + if (owners[b] == b) { + if (bin.program.kind == XTriggerProgramKind::Expr) matcher.AdvanceCondition(expr); + else Advance(expr, matcher, b + 1, bin.program.overlap, bin.program.max_active); + } + const auto completed = matcher.Count(bin.terminals); + delta[offset + b] = completed; + any = any || completed; + if (bin.kind == XCoverageBinKind::Ignore && completed) delta[base + 2] = 1; + } + if (!any) delta[base + 3] = 1; +} + +void CoverageState::SampleValuePoint(ExprEngine &expr, size_t i, size_t offset) +{ + const size_t base = 2 + i * 5; + int priority = -1; + for (size_t b = 0; b < bins.size(); ++b) { + const auto &bin = bins[b]; + if (bin.item != i || bin.kind == XCoverageBinKind::Default) continue; + bool hit = false; + if (bin.program.steps.empty()) hit = Enabled(expr, bin.program.root); + else { + hit = Advance(expr, matchers[b], b + 1, bin.program.overlap, bin.program.steps.size()) != 0; + } + matched[b] = hit; + if (hit) priority = std::max(priority, static_cast(bin.kind)); + } + if (priority == 1) delta[base + 2] = 1; + bool any = false; + for (size_t b = 0; b < bins.size(); ++b) { + const auto &bin = bins[b]; + if (bin.item != i) continue; + const bool hit = priority < 0 ? bin.kind == XCoverageBinKind::Default : matched[b] && static_cast(bin.kind) == priority; + matched[b] = hit && bin.kind == XCoverageBinKind::Normal; + if (matched[b]) ++normal_counts[i]; + if (hit) { delta[offset + b] = 1; any = true; } + } + if (!any) delta[base + 3] = 1; +} + void CoverageState::Sample(ExprEngine &expr, uint64_t tick) { std::fill(delta.begin(), delta.end(), 0); std::fill(matched.begin(), matched.end(), false); std::fill(normal_counts.begin(), normal_counts.end(), 0); const size_t offset = 2 + items.size() * 5; - auto enabled = [&](int r) { return r < 0 || (expr.IsKnown(r) && expr.Eval(r)); }; - if (!enabled(gate)) { + if (!Enabled(expr, gate)) { delta[1] = 1; for (size_t b = 0; b < matchers.size(); ++b) if (owners[b] == b) ClearMatcher(b + 1, matchers[b], Cleared); @@ -229,7 +303,7 @@ void CoverageState::Sample(ExprEngine &expr, uint64_t tick) for (size_t i = 0; i < items.size(); ++i) { const auto &item = items[i]; const size_t base = 2 + i * 5; - const bool gate = enabled(item.gate); + const bool gate = Enabled(expr, item.gate); // U refreshes all native words; DataValid checks X/Z across the full width. const bool known = !item.signal || (item.signal->U(), item.signal->DataValid()); if (!gate || !known) { @@ -240,71 +314,15 @@ void CoverageState::Sample(ExprEngine &expr, uint64_t tick) continue; } delta[base] = 1; - if (!item.dimensions.empty()) { - uint64_t combinations = 1, hits = 0; - for (auto d : item.dimensions) { - const auto n = normal_counts[d]; - if (n && combinations > std::numeric_limits::max() / n) - throw std::overflow_error("coverage cross product overflow"); - combinations *= n; - } - for (size_t b = 0; b < bins.size(); ++b) { - const auto &bin = bins[b]; - if (bin.item != i) continue; - bool match = true; - for (auto d : bin.dimensions) match = match && matched[d]; - if (!match) continue; - delta[offset + b] = 1; ++hits; - if (bin.kind == 1) ++delta[base + 2]; - } - delta[base + 3] = combinations ? combinations - hits : 1; - continue; - } - if (item.pattern) { - bool any = false; - for (size_t b = 0; b < bins.size(); ++b) { - const auto &bin = bins[b]; - if (bin.item != i) continue; - auto &matcher = matchers[owners[b]]; - if (owners[b] == b) { - if (bin.program_kind == 0) matcher.Advance(expr); - else Advance(expr, matcher, b + 1, bin.overlap, bin.max_active); - } - const auto completed = matcher.Count(bin.terminals); - delta[offset + b] = completed; - any = any || completed; - if (bin.kind == 1 && completed) delta[base + 2] = 1; - } - if (!any) delta[base + 3] = 1; - continue; - } - int priority = -1; - for (size_t b = 0; b < bins.size(); ++b) { - const auto &bin = bins[b]; - if (bin.item != i || bin.kind == 3) continue; - bool hit = false; - if (bin.steps.empty()) hit = enabled(bin.root); - else { - hit = Advance(expr, matchers[b], b + 1, bin.overlap, bin.steps.size()) != 0; - } - matched[b] = hit; - if (hit) priority = std::max(priority, static_cast(bin.kind)); - } - if (priority == 1) delta[base + 2] = 1; - bool any = false; - for (size_t b = 0; b < bins.size(); ++b) { - const auto &bin = bins[b]; - if (bin.item != i) continue; - const bool hit = priority < 0 ? bin.kind == 3 : matched[b] && static_cast(bin.kind) == priority; - matched[b] = hit && bin.kind == 0; - if (matched[b]) ++normal_counts[i]; - if (hit) { delta[offset + b] = 1; any = true; } + switch (item.kind) { + case XCoverageItemKind::Value: SampleValuePoint(expr, i, offset); break; + case XCoverageItemKind::Pattern: SamplePatternPoint(expr, i, offset); break; + case XCoverageItemKind::Cross: SampleCrossPoint(i, offset); break; } - if (!any) delta[base + 3] = 1; } } size_t illegal = 0; - for (size_t b = 0; b < bins.size(); ++b) if (bins[b].kind == 2 && delta[offset + b]) ++illegal; + for (auto b : illegal_indices) if (delta[offset + b]) ++illegal; if (snapshot.illegal_bins.size() + illegal > diagnostic_capacity) throw std::overflow_error("coverage diagnostic capacity exhausted"); for (size_t i = 0; i < delta.size(); ++i) @@ -312,7 +330,7 @@ void CoverageState::Sample(ExprEngine &expr, uint64_t tick) throw std::overflow_error("coverage counter overflow"); for (size_t i = 0; i < delta.size(); ++i) snapshot.counters[i] += delta[i]; snapshot.tick = tick; - for (size_t b = 0; b < bins.size(); ++b) if (bins[b].kind == 2 && delta[offset + b]) { + for (auto b : illegal_indices) if (delta[offset + b]) { snapshot.illegal_bins.push_back(b); snapshot.illegal_ticks.push_back(tick); auto *signal = items[bins[b].item].signal; diff --git a/src/trigger/coverage.cpp b/src/coverage/registration.cpp similarity index 78% rename from src/trigger/coverage.cpp rename to src/coverage/registration.cpp index 37f8e2c..c8880d7 100644 --- a/src/trigger/coverage.cpp +++ b/src/coverage/registration.cpp @@ -1,20 +1,20 @@ -#include "xspcomm/xtrigger.h" +#include "xspcomm/xengine.h" #include "xspcomm/xexpr.h" -#include "../coverage/state.h" +#include "xspcomm/detail/coverage/state.h" #include #include namespace xspcomm { -void XTriggerEngine::CheckCoverageHandle(XRegistrationHandle handle) const +void XEngine::CheckCoverageHandle(XRegistrationHandle handle) const { if (!handle.IsValid() || handle.slot >= watchers.size() || !watchers[handle.slot].occupied || watchers[handle.slot].generation != handle.generation || !watchers[handle.slot].coverage) throw std::invalid_argument("stale coverage handle"); } -void XTriggerEngine::AttachCoverage(XRegistrationHandle handle, +void XEngine::AttachCoverage(XRegistrationHandle handle, const std::vector &items, const std::vector &bins, int gate, int abort, bool raise_illegal, size_t diagnostic_capacity, bool overlap, size_t max_active, bool diagnostics) @@ -28,7 +28,7 @@ void XTriggerEngine::AttachCoverage(XRegistrationHandle handle, gate, abort, raise_illegal, diagnostic_capacity, overlap, max_active, diagnostics); } -void XTriggerEngine::ResetCoverage(XRegistrationHandle handle, bool counters) +void XEngine::ResetCoverage(XRegistrationHandle handle, bool counters) { CheckCoverageHandle(handle); auto &watcher = watchers[handle.slot]; @@ -38,14 +38,14 @@ void XTriggerEngine::ResetCoverage(XRegistrationHandle handle, bool counters) if (counters) watcher.coverage->ResetCounters(); } -XCoverageSnapshot XTriggerEngine::CoverageSnapshot(XRegistrationHandle handle, bool progress) const +XCoverageSnapshot XEngine::CoverageSnapshot(XRegistrationHandle handle, bool progress) const { CheckCoverageHandle(handle); const auto &watcher = watchers[handle.slot]; return watcher.coverage->Snapshot(clock->GetHalfTick(), progress); } -size_t XTriggerEngine::CoverageExecutionCount(XRegistrationHandle handle) const +size_t XEngine::CoverageExecutionCount(XRegistrationHandle handle) const { CheckCoverageHandle(handle); return watchers[handle.slot].coverage->ExecutionCount(); diff --git a/src/pattern.cpp b/src/pattern.cpp deleted file mode 100644 index cfafe42..0000000 --- a/src/pattern.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include "xspcomm/detail/pattern.h" -#include "xspcomm/xexpr.h" - -#include -#include - -namespace xspcomm { - -namespace detail { - -bool AdvanceSequence(const std::vector &steps, PatternState &watcher, ExprEngine &expr) -{ - const auto &step = steps[watcher.sequence_index]; - bool complete = false; - watcher.sequence_failed = watcher.sequence_expired = false; - switch (step.kind) { - case XSequenceStepKind::Wait: - complete = expr.IsKnown(step.root) && - expr.Eval(step.root) != 0; - break; - case XSequenceStepKind::Next: - complete = expr.IsKnown(step.root) && expr.Eval(step.root) != 0; - if (!complete) { - watcher.sequence_index = watcher.sequence_age = watcher.sequence_held = 0; - watcher.sequence_failed = true; - } - break; - case XSequenceStepKind::Within: - watcher.sequence_age += 1; - if (watcher.sequence_age > step.maximum) { - watcher.sequence_index = 0; - watcher.sequence_age = 0; - watcher.sequence_held = 0; - watcher.sequence_failed = watcher.sequence_expired = true; - return false; - } - complete = watcher.sequence_age >= step.minimum && - expr.IsKnown(step.root) && - expr.Eval(step.root) != 0; - break; - case XSequenceStepKind::Hold: - if (expr.IsKnown(step.root) && - expr.Eval(step.root) != 0) { - watcher.sequence_held += 1; - } else { - watcher.sequence_held = 0; - } - complete = watcher.sequence_held >= step.cycles; - break; - } - if (!complete) return false; - watcher.sequence_index += 1; - watcher.sequence_age = 0; - watcher.sequence_held = 0; - if (watcher.sequence_index == steps.size()) { - watcher.sequence_index = 0; - return true; - } - return false; -} - -bool AdvanceFsm(const std::vector &transitions, PatternState &state, ExprEngine &expr, uint32_t &terminal) -{ - for (const auto &transition : transitions) { - if (transition.from_state != state.fsm_current_state) continue; - if (transition.root >= 0 && (!expr.IsKnown(transition.root) || - expr.Eval(transition.root) == 0)) continue; - if (transition.trigger) { terminal = transition.terminal_id; return true; } - state.fsm_current_state = transition.next_state; - break; - } - return false; -} - - -} // namespace detail - -} // namespace xspcomm diff --git a/src/trigger/engine.cpp b/src/runtime/engine.cpp similarity index 66% rename from src/trigger/engine.cpp rename to src/runtime/engine.cpp index e86fd3a..1b4328d 100644 --- a/src/trigger/engine.cpp +++ b/src/runtime/engine.cpp @@ -1,7 +1,7 @@ -#include "xspcomm/xtrigger.h" +#include "xspcomm/xengine.h" #include "xspcomm/xexpr.h" -#include "../coverage/state.h" -#include "matcher.h" +#include "xspcomm/detail/coverage/state.h" +#include "xspcomm/detail/trigger/matcher.h" #include #include @@ -9,7 +9,7 @@ namespace xspcomm { -XTriggerEngine::XTriggerEngine(XClock *clock, size_t capacity) +XEngine::XEngine(XClock *clock, size_t capacity) : clock(clock), capacity(capacity), expr_engine(std::make_unique()) { if (clock == nullptr) { @@ -23,9 +23,9 @@ XTriggerEngine::XTriggerEngine(XClock *clock, size_t capacity) hit_buffer.reserve(capacity); } -XTriggerEngine::~XTriggerEngine() = default; +XEngine::~XEngine() = default; -XRegistrationHandle XTriggerEngine::Allocate() +XRegistrationHandle XEngine::Allocate() { uint32_t slot; if (!free_slots.empty()) { @@ -53,21 +53,24 @@ XRegistrationHandle XTriggerEngine::Allocate() watcher.expected_wide.reset(); watcher.expected_bytes.clear(); watcher.expected_x_bytes.clear(); - watcher.condition_mode = XConditionMode::Enter; + watcher.program.mode = XConditionMode::Enter; watcher.last_condition = false; - watcher.expr_root = -1; - watcher.sequence_steps.clear(); + watcher.program.kind = XTriggerProgramKind::Expr; + watcher.program.root = -1; + watcher.program.overlap = false; + watcher.program.max_active = 1; + watcher.program.steps.clear(); watcher.sequence_index = 0; watcher.sequence_age = 0; watcher.sequence_held = 0; - watcher.fsm_transitions.clear(); - watcher.fsm_state_count = 0; - watcher.fsm_start_state = 0; + watcher.program.transitions.clear(); + watcher.program.state_count = 0; + watcher.program.start_state = 0; watcher.fsm_current_state = 0; return {slot, watcher.generation}; } -XRegistrationHandle XTriggerEngine::ArmEdge( +XRegistrationHandle XEngine::ArmEdge( XPhase phase, uint64_t source_id) { auto handle = Allocate(); @@ -79,7 +82,7 @@ XRegistrationHandle XTriggerEngine::ArmEdge( return handle; } -XRegistrationHandle XTriggerEngine::ArmClockCycles( +XRegistrationHandle XEngine::ArmClockCycles( uint64_t cycles, XPhase phase, uint64_t source_id) { if (cycles == 0) return {}; @@ -94,7 +97,7 @@ XRegistrationHandle XTriggerEngine::ArmClockCycles( return handle; } -XRegistrationHandle XTriggerEngine::ArmValueEq( +XRegistrationHandle XEngine::ArmValueEq( XData *signal, uint64_t expected, XPhase phase, XConditionMode mode, uint64_t source_id) { @@ -107,11 +110,11 @@ XRegistrationHandle XTriggerEngine::ArmValueEq( watcher.source_id = source_id ? source_id : signal->CSelf(); watcher.signal = signal; watcher.expected = expected; - watcher.condition_mode = mode; + watcher.program.mode = mode; return handle; } -XRegistrationHandle XTriggerEngine::ArmValueEqBytes( +XRegistrationHandle XEngine::ArmValueEqBytes( XData *signal, std::vector &expected, XPhase phase, XConditionMode mode, uint64_t source_id) { @@ -125,11 +128,11 @@ XRegistrationHandle XTriggerEngine::ArmValueEqBytes( watcher.signal = signal; watcher.expected_wide = std::make_shared(signal->W(), XData::InOut); watcher.expected_wide->SetVU8(expected); - watcher.condition_mode = mode; + watcher.program.mode = mode; return handle; } -XRegistrationHandle XTriggerEngine::ArmValueChange( +XRegistrationHandle XEngine::ArmValueChange( XData *signal, XPhase phase, uint64_t source_id) { if (signal == nullptr) return {}; @@ -150,7 +153,7 @@ XRegistrationHandle XTriggerEngine::ArmValueChange( return handle; } -XRegistrationHandle XTriggerEngine::ArmSample( +XRegistrationHandle XEngine::ArmSample( XPhase phase, uint64_t source_id) { auto handle = Allocate(); @@ -162,52 +165,52 @@ XRegistrationHandle XTriggerEngine::ArmSample( return handle; } -int XTriggerEngine::ExprNewConst(uint64_t value) +int XEngine::ExprNewConst(uint64_t value) { return expr_engine->NewConst(value); } -int XTriggerEngine::ExprNewSignal(XData *signal) +int XEngine::ExprNewSignal(XData *signal) { if (signal == nullptr || signal->W() > 64) return -1; return expr_engine->NewSignal(signal); } -int XTriggerEngine::ExprNewUnary(int op, int child) +int XEngine::ExprNewUnary(int op, int child) { return expr_engine->NewUnary(static_cast(op), child); } -int XTriggerEngine::ExprNewBinary(int op, int lhs, int rhs) +int XEngine::ExprNewBinary(int op, int lhs, int rhs) { return expr_engine->NewBinary(static_cast(op), lhs, rhs); } -int XTriggerEngine::ExprNewCompare(int op, int lhs, int rhs) +int XEngine::ExprNewCompare(int op, int lhs, int rhs) { return expr_engine->NewCompare(static_cast(op), lhs, rhs); } -int XTriggerEngine::ExprNewCompareSigSig(int op, XData *lhs, XData *rhs) +int XEngine::ExprNewCompareSigSig(int op, XData *lhs, XData *rhs) { return expr_engine->NewCompareSigSig(static_cast(op), lhs, rhs); } -int XTriggerEngine::ExprNewCompareSigConstBytes( +int XEngine::ExprNewCompareSigConstBytes( int op, XData *lhs, std::vector &rhs) { return expr_engine->NewCompareSigConstBytes( static_cast(op), lhs, rhs); } -int XTriggerEngine::ExprNewCompareConstBytesSig( +int XEngine::ExprNewCompareConstBytesSig( int op, std::vector &lhs, XData *rhs) { return expr_engine->NewCompareConstBytesSig( static_cast(op), lhs, rhs); } -int XTriggerEngine::ExprNewMaskedCompareSigConstBytes( +int XEngine::ExprNewMaskedCompareSigConstBytes( XData *lhs, std::vector &value, std::vector &mask) { const int root = expr_engine->NewMaskedCompareSigConstBytes(lhs, value, mask); @@ -215,73 +218,61 @@ int XTriggerEngine::ExprNewMaskedCompareSigConstBytes( return root; } -XRegistrationHandle XTriggerEngine::ArmExpr( +XRegistrationHandle XEngine::ArmExpr( int root, XPhase phase, XConditionMode mode, uint64_t source_id) { - if (root < 0) return {}; + XTriggerProgram program; + program.root = root; + program.mode = mode; + program.overlap = false; + if (!detail::ProgramValidator::Validate(program, *expr_engine)) return {}; auto handle = Allocate(); if (!handle.IsValid()) return handle; auto &watcher = watchers[handle.slot]; watcher.kind = WatcherKind::Expr; watcher.phase = phase; watcher.source_id = source_id; - watcher.expr_root = root; - watcher.condition_mode = mode; + watcher.program.root = root; + watcher.program.mode = mode; expr_engine->OptimizeShortCircuitOrder(root); return handle; } -XRegistrationHandle XTriggerEngine::ArmSequence( +XRegistrationHandle XEngine::ArmSequence( const std::vector &steps, XPhase phase, uint64_t source_id) { - if (steps.empty()) return {}; - for (const auto &step : steps) { - if (step.root < 0) return {}; - if (step.kind == XSequenceStepKind::Within && - step.maximum < step.minimum) { - return {}; - } - if (step.kind == XSequenceStepKind::Hold && step.cycles == 0) { - return {}; - } - } + if (!detail::ProgramValidator::Sequence(steps, *expr_engine)) return {}; auto handle = Allocate(); if (!handle.IsValid()) return handle; auto &watcher = watchers[handle.slot]; watcher.kind = WatcherKind::Sequence; watcher.phase = phase; watcher.source_id = source_id; - watcher.sequence_steps = steps; + watcher.program.kind = XTriggerProgramKind::Sequence; + watcher.program.steps = steps; for (const auto &step : steps) { expr_engine->OptimizeShortCircuitOrder(step.root); } return handle; } -XRegistrationHandle XTriggerEngine::ArmFsm( +XRegistrationHandle XEngine::ArmFsm( uint32_t state_count, uint32_t start_state, const std::vector &transitions, XPhase phase, uint64_t source_id) { - if (state_count == 0 || start_state >= state_count || transitions.empty()) { - return {}; - } - for (const auto &transition : transitions) { - if (transition.from_state >= state_count || - (!transition.trigger && transition.next_state >= state_count)) { - return {}; - } - } + if (!detail::ProgramValidator::Fsm(state_count, start_state, transitions, *expr_engine)) return {}; auto handle = Allocate(); if (!handle.IsValid()) return handle; auto &watcher = watchers[handle.slot]; watcher.kind = WatcherKind::Fsm; watcher.phase = phase; watcher.source_id = source_id; - watcher.fsm_transitions = transitions; - watcher.fsm_state_count = state_count; - watcher.fsm_start_state = start_state; + watcher.program.kind = XTriggerProgramKind::Fsm; + watcher.program.state_count = state_count; + watcher.program.start_state = start_state; + watcher.program.transitions = transitions; watcher.fsm_current_state = start_state; for (const auto &transition : transitions) { if (transition.root >= 0) { @@ -291,7 +282,7 @@ XRegistrationHandle XTriggerEngine::ArmFsm( return handle; } -bool XTriggerEngine::Disarm(XRegistrationHandle handle) +bool XEngine::Disarm(XRegistrationHandle handle) { if (!handle.IsValid() || handle.slot >= watchers.size()) return false; auto &watcher = watchers[handle.slot]; @@ -309,7 +300,7 @@ bool XTriggerEngine::Disarm(XRegistrationHandle handle) return true; } -bool XTriggerEngine::RearmSample(XRegistrationHandle handle) +bool XEngine::RearmSample(XRegistrationHandle handle) { if (!handle.IsValid() || handle.slot >= watchers.size()) return false; auto &watcher = watchers[handle.slot]; @@ -321,7 +312,7 @@ bool XTriggerEngine::RearmSample(XRegistrationHandle handle) return true; } -bool XTriggerEngine::Rearm(XRegistrationHandle handle) +bool XEngine::Rearm(XRegistrationHandle handle) { if (!handle.IsValid() || handle.slot >= watchers.size()) return false; auto &watcher = watchers[handle.slot]; @@ -337,7 +328,7 @@ bool XTriggerEngine::Rearm(XRegistrationHandle handle) watcher.sequence_age = 0; watcher.sequence_held = 0; } else if (watcher.kind == WatcherKind::Fsm) { - watcher.fsm_current_state = watcher.fsm_start_state; + watcher.fsm_current_state = watcher.program.start_state; } else if (watcher.kind == WatcherKind::ValueChange) { if (watcher.signal->W() > 64) { watcher.expected_bytes = watcher.signal->GetVU8(); @@ -350,7 +341,7 @@ bool XTriggerEngine::Rearm(XRegistrationHandle handle) return true; } -void XTriggerEngine::AppendHit( +void XEngine::AppendHit( uint32_t slot, Watcher &watcher, XHitKind kind, uint64_t value, uint64_t event_id, uint64_t x_mask) { @@ -375,7 +366,7 @@ void XTriggerEngine::AppendHit( watcher.armed = false; } -bool XTriggerEngine::HasArmedPhase(XPhase phase) const +bool XEngine::HasArmedPhase(XPhase phase) const { for (const auto &watcher : watchers) { if (watcher.occupied && watcher.armed && watcher.phase == phase) { @@ -386,7 +377,7 @@ bool XTriggerEngine::HasArmedPhase(XPhase phase) const } // Keep the sampling adapter visible to the phase loop so it can inline. -inline bool XTriggerEngine::EvaluateCoverage(Watcher &watcher) +inline bool XEngine::EvaluateCoverage(Watcher &watcher) { auto &coverage = *watcher.coverage; switch (coverage.BeginSample(*expr_engine, clock->GetHalfTick())) { @@ -407,7 +398,7 @@ inline bool XTriggerEngine::EvaluateCoverage(Watcher &watcher) return false; } -void XTriggerEngine::EvaluatePhase(XPhase phase) +void XEngine::EvaluatePhase(XPhase phase) { evaluation_phase = phase; uint64_t edge_event_id = 0; @@ -419,99 +410,26 @@ void XTriggerEngine::EvaluatePhase(XPhase phase) continue; } if (watcher.coverage && EvaluateCoverage(watcher)) continue; - switch (watcher.kind) { - case WatcherKind::Edge: { + const auto match = watcher.Evaluate(*expr_engine, phase); + if (!match.hit) continue; + uint64_t event_id = 0; + if (watcher.kind == WatcherKind::Edge) { if (edge_event_id == 0) edge_event_id = next_event_id++; - XHitKind kind = XHitKind::ClockFall; - if (phase == XPhase::RisingStable) { - kind = XHitKind::ClockRise; - } else if (phase == XPhase::DriveStable) { - kind = XHitKind::DriveStable; - } - AppendHit(slot, watcher, kind, 0, edge_event_id); - break; - } - case WatcherKind::ClockCycles: - watcher.remaining -= 1; - if (watcher.remaining == 0) { - AppendHit(slot, watcher, XHitKind::ClockCycles, 0); - } - break; - case WatcherKind::ValueEq: { - const uint64_t value = watcher.signal->W() > 64 - ? 0 : watcher.signal->U(); - const bool known = watcher.signal->DataValid(); - const bool current = known && - (watcher.expected_wide - ? watcher.signal->Equal(*watcher.expected_wide) - : value == watcher.expected); - const bool emit = detail::MatchCondition(known, current, - watcher.condition_mode, watcher.last_condition); - if (emit) { - AppendHit(slot, watcher, XHitKind::Value, value); - } - break; - } - case WatcherKind::ValueChange: { - if (watcher.signal->W() > 64) { - auto value = watcher.signal->GetVU8(); - auto x_value = watcher.signal->GetBvalBytes(); - if (value != watcher.expected_bytes || - x_value != watcher.expected_x_bytes) { - watcher.expected_bytes = std::move(value); - watcher.expected_x_bytes = std::move(x_value); - AppendHit(slot, watcher, XHitKind::ValueChange, 0); - } - break; - } - const uint64_t value = watcher.signal->U(); - const uint64_t x_mask = watcher.signal->XMask(); - if (value != watcher.expected || - x_mask != watcher.expected_x_mask) { - watcher.expected = value; - watcher.expected_x_mask = x_mask; - AppendHit(slot, watcher, XHitKind::ValueChange, value, 0, - x_mask); - } - break; - } - case WatcherKind::Sample: - AppendHit(slot, watcher, XHitKind::Condition, 0); - break; - case WatcherKind::Expr: { - const bool known = expr_engine->IsKnown(watcher.expr_root); - const bool current = known && - expr_engine->Eval(watcher.expr_root) != 0; - const bool emit = detail::MatchCondition(known, current, - watcher.condition_mode, watcher.last_condition); - if (emit) { - AppendHit(slot, watcher, XHitKind::Condition, current ? 1 : 0); - } - break; - } - case WatcherKind::Sequence: - if (detail::AdvancePattern({&watcher.sequence_steps}, watcher, *expr_engine).completed) { - AppendHit(slot, watcher, XHitKind::Fsm, 1); - } - break; - case WatcherKind::Fsm: { - const auto result = detail::AdvancePattern( - {nullptr, &watcher.fsm_transitions, watcher.fsm_start_state}, watcher, *expr_engine); - if (result.completed) AppendHit(slot, watcher, XHitKind::Fsm, result.terminal); - break; - } + event_id = edge_event_id; } + AppendHit(slot, watcher, match.kind, match.value, event_id, match.x_mask); } } -XRunResult XTriggerEngine::RunUntil( +XRunResult XEngine::RunUntil( uint64_t max_half_ticks, uint64_t max_wall_time_ns, uint32_t budget_check_interval) { XRunResult result; result.stopped_phase = clock->GetPhase(); hit_buffer.clear(); - const auto started = std::chrono::steady_clock::now(); + const auto started = max_wall_time_ns ? std::chrono::steady_clock::now() + : std::chrono::steady_clock::time_point{}; if (budget_check_interval == 0) budget_check_interval = 1; for (uint64_t i = 0; i < max_half_ticks; ++i) { @@ -558,7 +476,7 @@ XRunResult XTriggerEngine::RunUntil( return result; } -XRunResult XTriggerEngine::SamplePhase(XPhase phase) +XRunResult XEngine::SamplePhase(XPhase phase) { if (phase != XPhase::DriveStable || clock->GetPhase() != XPhase::FallingStable) { @@ -579,7 +497,7 @@ XRunResult XTriggerEngine::SamplePhase(XPhase phase) } -size_t XTriggerEngine::ActiveCount() const +size_t XEngine::ActiveCount() const { size_t count = 0; for (const auto &watcher : watchers) { @@ -588,7 +506,7 @@ size_t XTriggerEngine::ActiveCount() const return count; } -void XTriggerEngine::ClearExecutionState() +void XEngine::ClearExecutionState() { if (ActiveCount() != 0) { throw std::logic_error( @@ -599,15 +517,15 @@ void XTriggerEngine::ClearExecutionState() watcher.expected_wide.reset(); watcher.expected_bytes.clear(); watcher.expected_x_bytes.clear(); - watcher.sequence_steps.clear(); - watcher.fsm_transitions.clear(); - watcher.expr_root = -1; + watcher.program.steps.clear(); + watcher.program.transitions.clear(); + watcher.program.root = -1; } hit_buffer.clear(); expr_engine->Clear(); } -void XTriggerEngine::Clear() +void XEngine::Clear() { watchers.clear(); free_slots.clear(); diff --git a/src/trigger/matcher.cpp b/src/trigger/matcher.cpp index 6b2ce01..3b288c8 100644 --- a/src/trigger/matcher.cpp +++ b/src/trigger/matcher.cpp @@ -1,4 +1,4 @@ -#include "matcher.h" +#include "xspcomm/detail/trigger/matcher.h" #include "xspcomm/xexpr.h" #include @@ -19,16 +19,21 @@ PatternMatcher::PatternMatcher(PatternView program) : program(program) } } +uint64_t PatternMatcher::AdvanceCondition(ExprEngine &expr) +{ + const bool known = expr.IsKnown(program.root); + const bool current = known && expr.Eval(program.root) != 0; + completed = MatchCondition(known, current, program.mode, last_condition); + return completed; +} + MatchUpdate PatternMatcher::Advance(ExprEngine &expr, bool overlap, size_t max_active) { MatchUpdate update; completed = 0; std::fill(terminal_counts.begin(), terminal_counts.end(), 0); if (!program.sequence && !program.fsm) { - const bool known = expr.IsKnown(program.root); - const bool current = known && expr.Eval(program.root) != 0; - completed = MatchCondition(known, current, program.mode, last_condition); - update.completed = completed; + update.completed = AdvanceCondition(expr); return update; } auto record = [&](uint32_t terminal) { @@ -44,7 +49,7 @@ MatchUpdate PatternMatcher::Advance(ExprEngine &expr, bool overlap, size_t max_a size_t kept = 0; for (size_t i = 0; i < attempts.size(); ++i) { auto &state = attempts[i]; - const auto result = AdvancePattern(program, state, expr); + const auto result = PatternExecutor::Advance(program, state, expr); if (result.completed) record(result.terminal); else if (result.failed) { if (state.sequence_expired) ++update.expired; @@ -59,7 +64,7 @@ MatchUpdate PatternMatcher::Advance(ExprEngine &expr, bool overlap, size_t max_a if (overlap || !had_active) { PatternState candidate; candidate.fsm_current_state = program.start_state; - const auto result = AdvancePattern(program, candidate, expr); + const auto result = PatternExecutor::Advance(program, candidate, expr); const bool active = program.fsm ? candidate.fsm_current_state != program.start_state : candidate.sequence_index || candidate.sequence_age || candidate.sequence_held; if (result.completed || active) { diff --git a/src/trigger/pattern.cpp b/src/trigger/pattern.cpp new file mode 100644 index 0000000..00b6aaf --- /dev/null +++ b/src/trigger/pattern.cpp @@ -0,0 +1,118 @@ +#include "xspcomm/detail/trigger/program.h" +#include "xspcomm/xexpr.h" + +#include +#include + +namespace xspcomm { + +namespace detail { + +bool ProgramValidator::Sequence(const std::vector &steps, const ExprEngine &expr) +{ + if (steps.empty()) return false; + for (const auto &step : steps) + if (!expr.ValidRoot(step.root) || static_cast(step.kind) > 3 || + (step.kind == XSequenceStepKind::Within && step.minimum > step.maximum) || + (step.kind == XSequenceStepKind::Hold && !step.cycles)) return false; + return true; +} + +bool ProgramValidator::Fsm(uint32_t states, uint32_t start, + const std::vector &transitions, const ExprEngine &expr) +{ + if (!states || start >= states || transitions.empty()) return false; + for (const auto &transition : transitions) + if (transition.from_state >= states || (!transition.trigger && transition.next_state >= states) || + (transition.root != -1 && !expr.ValidRoot(transition.root))) return false; + return true; +} + +bool ProgramValidator::Validate(const XTriggerProgram &program, const ExprEngine &expr) +{ + if (!program.max_active || (!program.overlap && program.max_active != 1) || + static_cast(program.mode) > 2) return false; + switch (program.kind) { + case XTriggerProgramKind::Expr: + return expr.ValidRoot(program.root) && program.steps.empty() && + program.transitions.empty() && !program.state_count && !program.start_state && !program.overlap; + case XTriggerProgramKind::Sequence: + return program.root == -1 && program.transitions.empty() && + !program.state_count && !program.start_state && program.mode == XConditionMode::Enter && + Sequence(program.steps, expr) && + (!program.overlap || program.steps.front().kind == XSequenceStepKind::Wait); + case XTriggerProgramKind::Fsm: + return program.root == -1 && program.steps.empty() && program.mode == XConditionMode::Enter && + Fsm(program.state_count, program.start_state, program.transitions, expr); + } + return false; +} + +bool PatternExecutor::AdvanceSequence(const std::vector &steps, PatternState &watcher, ExprEngine &expr) +{ + const auto &step = steps[watcher.sequence_index]; + bool complete = false; + watcher.sequence_failed = watcher.sequence_expired = false; + switch (step.kind) { + case XSequenceStepKind::Wait: + complete = expr.IsKnown(step.root) && + expr.Eval(step.root) != 0; + break; + case XSequenceStepKind::Next: + complete = expr.IsKnown(step.root) && expr.Eval(step.root) != 0; + if (!complete) { + watcher.sequence_index = watcher.sequence_age = watcher.sequence_held = 0; + watcher.sequence_failed = true; + } + break; + case XSequenceStepKind::Within: + watcher.sequence_age += 1; + if (watcher.sequence_age > step.maximum) { + watcher.sequence_index = 0; + watcher.sequence_age = 0; + watcher.sequence_held = 0; + watcher.sequence_failed = watcher.sequence_expired = true; + return false; + } + complete = watcher.sequence_age >= step.minimum && + expr.IsKnown(step.root) && + expr.Eval(step.root) != 0; + break; + case XSequenceStepKind::Hold: + if (expr.IsKnown(step.root) && + expr.Eval(step.root) != 0) { + watcher.sequence_held += 1; + } else { + watcher.sequence_held = 0; + } + complete = watcher.sequence_held >= step.cycles; + break; + } + if (!complete) return false; + watcher.sequence_index += 1; + watcher.sequence_age = 0; + watcher.sequence_held = 0; + if (watcher.sequence_index == steps.size()) { + watcher.sequence_index = 0; + return true; + } + return false; +} + +bool PatternExecutor::AdvanceFsm(const std::vector &transitions, PatternState &state, ExprEngine &expr, uint32_t &terminal) +{ + for (const auto &transition : transitions) { + if (transition.from_state != state.fsm_current_state) continue; + if (transition.root >= 0 && (!expr.IsKnown(transition.root) || + expr.Eval(transition.root) == 0)) continue; + if (transition.trigger) { terminal = transition.terminal_id; return true; } + state.fsm_current_state = transition.next_state; + break; + } + return false; +} + + +} // namespace detail + +} // namespace xspcomm diff --git a/swig/xcomm.i b/swig/xcomm.i index 0079ffc..49734e2 100644 --- a/swig/xcomm.i +++ b/swig/xcomm.i @@ -58,12 +58,24 @@ namespace std { %template(DictStrBool) map; } +%include exception.i +%exception { + try { $action } + catch (const std::invalid_argument &error) { + SWIG_exception(SWIG_ValueError, error.what()); + } + catch (const std::exception &error) { + SWIG_exception(SWIG_RuntimeError, error.what()); + } +} %include "xspcomm/xclock.h" %include "xspcomm/xdata.h" %include "xspcomm/xport.h" %include "xspcomm/xutil.h" %include "xspcomm/xsignal_cfg.h" %include "xspcomm/xcomuse/callback.h" +%rename(ComUseCondCmp) xspcomm::CompareOp; +%include "xspcomm/common/operation.h" %include "xspcomm/xcomuse/condition.h" %include "xspcomm/xcomuse/range.h" %include "xspcomm/common/memory.h" @@ -71,18 +83,11 @@ namespace std { %include "xspcomm/xcomuse/expr.h" %include "xspcomm/xcomuse/fsm.h" %include "xspcomm/xcomuse.h" -%include exception.i -%exception { - try { $action } - catch (const std::invalid_argument &error) { - SWIG_exception(SWIG_ValueError, error.what()); - } - catch (const std::exception &error) { - SWIG_exception(SWIG_RuntimeError, error.what()); - } -} %include "xspcomm/xpattern.h" +%include "xspcomm/trigger/types.h" %include "xspcomm/xcoverage.h" +%rename(XTriggerEngine) xspcomm::XEngine; +%include "xspcomm/xengine.h" %include "xspcomm/xtrigger.h" %exception; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d227db8..6593f01 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,14 +2,13 @@ if(NOT XSPCOMM_WASM_LIB) foreach(component IN ITEMS xdata xclock xtrigger xcoverage xcomuse xexpr xvpi xfsm trigger_matcher) add_executable(test_${component} unit/test_${component}.cpp) target_include_directories(test_${component} PRIVATE - ${PROJECT_SOURCE_DIR}/third_party/catch2 - ${PROJECT_SOURCE_DIR}/src) + ${PROJECT_SOURCE_DIR}/third_party/catch2) target_link_libraries(test_${component} PRIVATE ${PROJECT_NAME}) add_test(NAME test_${component} COMMAND test_${component}) endforeach() add_executable(test_coverage_core unit/test_coverage_core.cpp) target_include_directories(test_coverage_core PRIVATE - ${PROJECT_SOURCE_DIR}/third_party/catch2 ${PROJECT_SOURCE_DIR}/src) + ${PROJECT_SOURCE_DIR}/third_party/catch2) target_link_libraries(test_coverage_core PRIVATE ${PROJECT_NAME}) add_test(NAME test_coverage_core COMMAND test_coverage_core) target_sources(test_xdata PRIVATE unit/xdata_cases.cpp) diff --git a/tests/unit/test_coverage_core.cpp b/tests/unit/test_coverage_core.cpp index e493eca..36ae5c2 100644 --- a/tests/unit/test_coverage_core.cpp +++ b/tests/unit/test_coverage_core.cpp @@ -1,7 +1,7 @@ #define CATCH_CONFIG_MAIN #include "catch.hpp" -#include "coverage/state.h" +#include "xspcomm/detail/coverage/state.h" #include "xspcomm/xexpr.h" using namespace xspcomm; @@ -16,7 +16,7 @@ TEST_CASE("Coverage samples independently of the trigger engine", "[coverage]") XCoverageItem item; item.signal = &value; XCoverageBin bin; - bin.root = expr.NewConst(1); + bin.program.root = expr.NewConst(1); CoverageState coverage(9, expr, {}, {item}, {bin}, expr.NewSignal(&gate), expr.NewSignal(&abort), true, 32, false, 1, true); @@ -55,7 +55,7 @@ TEST_CASE("Coverage abort clears overlapping pattern history", "[coverage][patte XCoverageItem item; item.signal = &value; XCoverageBin bin; - bin.root = expr.NewConst(1); + bin.program.root = expr.NewConst(1); CoverageState coverage(1, expr, source, {item}, {bin}, -1, expr.NewSignal(&abort), true, 32, true, 2, true); diff --git a/tests/unit/test_trigger_matcher.cpp b/tests/unit/test_trigger_matcher.cpp index f9ab35a..ae2ddda 100644 --- a/tests/unit/test_trigger_matcher.cpp +++ b/tests/unit/test_trigger_matcher.cpp @@ -1,7 +1,7 @@ #define CATCH_CONFIG_MAIN #include "catch.hpp" -#include "trigger/matcher.h" +#include "xspcomm/detail/trigger/matcher.h" #include "xspcomm/xexpr.h" using namespace xspcomm; diff --git a/tests/unit/test_xcomuse.cpp b/tests/unit/test_xcomuse.cpp index 70b7fe9..64d5129 100644 --- a/tests/unit/test_xcomuse.cpp +++ b/tests/unit/test_xcomuse.cpp @@ -370,3 +370,127 @@ TEST_CASE("XData equality preserves zero extension and four-state masks", "[xcom REQUIRE(checker.ListCondition().at("ne")); } } + +TEST_CASE("Managed ComUse callbacks detach with either lifetime order", "[xcomuse_base][lifetime]") { + XClock clock([](bool) { return 0; }); + { + auto callback = std::make_unique(); + callback->Attach(&clock); + callback->Attach(&clock); + REQUIRE(clock.StepRisQueueSize() == 1); + clock.Step(); + REQUIRE(callback->calls == 1); + } + REQUIRE(clock.StepRisQueueSize() == 0); + clock.Step(); + + StepCbProbe callback; + { + XClock temporary([](bool) { return 0; }); + callback.Attach(&temporary, false); + temporary.Step(); + REQUIRE(callback.calls == 1); + } + callback.Detach(); +} + +TEST_CASE("Managed callback removal during sampling preserves iteration", "[xcomuse_base][lifetime]") { + XClock clock([](bool) { return 0; }); + auto callback = std::make_unique(); + clock.StepRis([&](uint64_t, void *) { callback.reset(); }); + callback->Attach(&clock); + clock.Step(); + REQUIRE_FALSE(callback); + REQUIRE(clock.StepRisQueueSize() == 1); + clock.Step(); +} + +TEST_CASE("Copied clocks cannot retain destroyed managed callbacks", "[xcomuse_base][lifetime]") { + auto original = std::make_unique([](bool) { return 0; }); + auto callback = std::make_unique(); + callback->Attach(original.get()); + XClock copy = *original; + original.reset(); + copy.Step(); + REQUIRE(callback->calls == 1); + callback.reset(); + copy.Step(); +} + +TEST_CASE("Expression and comparison checkers share registration and hit handling", "[xcomuse_base][xexpr]") { + XClock clock([](bool) { return 0; }); + XData left(129, XData::InOut), right(129, XData::InOut); + left = right = "0x100000000000000000000000000000001"; + ComUseExprCheck checker(&clock); + checker.SetCondition("wide", &left, &right, ComUseCondCmp::EQ); + checker.SetExpr("expression", checker.ExprNewCompareSigSig(static_cast(ExprOp::EQ), &left, &right)); + checker.Call(); + REQUIRE(checker.GetTriggeredExprKeys().size() == 2); + REQUIRE(checker.GetCbCount() == 1); + REQUIRE(clock.IsDisable()); + + checker.SetExpr("wide", checker.ExprNewConst(0)); + checker.Call(); + REQUIRE_FALSE(checker.ListCondition().at("wide")); + REQUIRE(checker.GetTriggeredExprKeys().size() == 1); + + int8_t negative = -1, positive = 1; + checker.SetCondition("expression", reinterpret_cast(&negative), + reinterpret_cast(&positive), ComUseCondCmp::LT, 1); + checker.Call(); + REQUIRE(checker.ListExpr().at("expression")); + checker.RemoveExpr("expression"); + REQUIRE(checker.ListCondition().size() == 1); + checker.ClearExpr(); + REQUIRE(checker.ListCondition().empty()); +} + +TEST_CASE("Expression hits clear when evaluated again in the same cycle", "[xcomuse_base][xexpr]") { + ComUseExprCheck checker; + checker.SetExpr("condition", checker.ExprNewConst(1)); + checker.Call(); + REQUIRE(checker.ListExpr().at("condition")); + checker.SetExpr("condition", checker.ExprNewConst(0)); + checker.Call(); + REQUIRE_FALSE(checker.ListExpr().at("condition")); + REQUIRE(checker.GetTriggeredExprKeys().empty()); +} + +TEST_CASE("Unified checks retain four-state equality and first-hit clock behavior", "[xcomuse_base][xexpr]") { + XClock clock([](bool) { return 0; }); + XData left(129, XData::InOut), right(129, XData::InOut); + left = right = "x"; + ComUseExprCheck checker(&clock); + checker.SetCondition("compare", &left, &right, ComUseCondCmp::EQ); + checker.SetExpr("expression", checker.ExprNewCompareSigSig(static_cast(ExprOp::EQ), &left, &right)); + bool observed_disabled = false; + checker.SetCondition("callback", uint64_t(0), uint64_t(0), ComUseCondCmp::EQ, 0, 0, 0, 1, + [&](uint64_t, uint64_t, uint64_t) { observed_disabled = clock.IsDisable(); return true; }); + checker.Call(); + REQUIRE(checker.GetTriggeredExprKeys().size() == 3); + REQUIRE(observed_disabled); + REQUIRE(checker.GetCbCount() == 1); +} + +TEST_CASE("Invalid raw comparison registration is rejected before sampling", "[xcomuse_base]") { + ComUseCondCheck checker; + uint64_t left = 1, right = 2; + REQUIRE_THROWS_AS(checker.SetCondition("invalid", reinterpret_cast(&left), + reinterpret_cast(&right), ComUseCondCmp::EQ, 0), std::invalid_argument); + REQUIRE_THROWS_AS(checker.SetCondition("invalid", reinterpret_cast(&left), + reinterpret_cast(&right), static_cast(99), 8), std::invalid_argument); +} + +TEST_CASE("Raw array helpers preserve unaligned accesses and signed offsets", "[xcomuse_base][memory]") { + unsigned char storage[18]; + std::memset(storage, 0xA5, sizeof(storage)); + const auto middle = reinterpret_cast(storage + 9); + SetU64Array(middle, -1, UINT64_C(0x123456789ABCDEF0)); + REQUIRE(GetFromU64Array(middle, -1) == UINT64_C(0x123456789ABCDEF0)); + REQUIRE(storage[0] == 0xA5); + REQUIRE(storage[9] == 0xA5); + SetU32Array(middle, 0, 0x12345678); + REQUIRE(GetFromU32Array(middle, 0) == 0x12345678); + REQUIRE(storage[13] == 0xA5); + REQUIRE(storage[17] == 0xA5); +} diff --git a/tests/unit/test_xcoverage.cpp b/tests/unit/test_xcoverage.cpp index 2fefcaa..b30fd97 100644 --- a/tests/unit/test_xcoverage.cpp +++ b/tests/unit/test_xcoverage.cpp @@ -16,7 +16,7 @@ TEST_CASE("Coverage counts persist without returning ordinary hits", "[xtrigger] XCoverageItem item; item.signal = &value; XCoverageBin bin; - bin.root = one; + bin.program.root = one; auto handle = engine.ArmEdge(XPhase::RisingStable); engine.AttachCoverage(handle, {item}, {bin}); auto limit = engine.ArmClockCycles(10); @@ -51,7 +51,8 @@ TEST_CASE("Coverage transitions overlap and have isolated registration history", XCoverageItem item; item.signal = &value; XCoverageBin bin; - bin.steps = {{XSequenceStepKind::Wait, one}, {XSequenceStepKind::Next, one}}; + bin.program.kind = XTriggerProgramKind::Sequence; + bin.program.steps = {{XSequenceStepKind::Wait, one}, {XSequenceStepKind::Next, one}}; auto h = engine.ArmEdge(XPhase::RisingStable); engine.AttachCoverage(h, {item}, {bin}); REQUIRE(engine.RunUntil(6).hits.empty()); @@ -75,7 +76,7 @@ TEST_CASE("Coverage rejects malformed descriptors without changing the watcher", XCoverageBin bin; XData value(8, XData::InOut); item.signal = &value; - bin.root = 100000; + bin.program.root = 100000; REQUIRE_THROWS(engine.AttachCoverage(h, {item}, {bin})); REQUIRE(engine.RunUntil(2).hits.size() == 1); REQUIRE(engine.Disarm(h)); @@ -91,8 +92,8 @@ TEST_CASE("Coverage snapshots preserve full-width illegal values", "[xtrigger][c XCoverageItem item; item.signal = &value; XCoverageBin bin; - bin.kind = 2; - bin.root = engine.ExprNewCompareSigConstBytes(static_cast(ExprOp::EQ), &value, bytes); + bin.kind = XCoverageBinKind::Illegal; + bin.program.root = engine.ExprNewCompareSigConstBytes(static_cast(ExprOp::EQ), &value, bytes); auto handle = engine.ArmEdge(XPhase::RisingStable); engine.AttachCoverage(handle, {item}, {bin}, -1, -1, false); engine.RunUntil(2); @@ -118,7 +119,7 @@ TEST_CASE("Coverage overlapping sequences count simultaneous completions", "[xtr auto h = engine.ArmSequence({{XSequenceStepKind::Wait, start_root}, {XSequenceStepKind::Within, done_root, 1, 3}}); XCoverageItem item; item.signal = &value; - XCoverageBin bin; bin.root = engine.ExprNewConst(1); + XCoverageBin bin; bin.program.root = engine.ExprNewConst(1); engine.AttachCoverage(h, {item}, {bin}, -1, -1, true, 1024, true, 2, true); start = 1; REQUIRE(engine.RunUntil(4).hits.empty()); @@ -147,7 +148,7 @@ TEST_CASE("Coverage overlap capacity failure preserves pending state for cleanup auto h = engine.ArmSequence({{XSequenceStepKind::Wait, engine.ExprNewSignal(&start)}, {XSequenceStepKind::Wait, engine.ExprNewSignal(&done)}}); XCoverageItem item; item.signal = &value; - XCoverageBin bin; bin.root = engine.ExprNewConst(1); + XCoverageBin bin; bin.program.root = engine.ExprNewConst(1); REQUIRE_THROWS(engine.AttachCoverage(h, {item}, {bin}, -1, -1, true, 1024, true, 0, true)); engine.AttachCoverage(h, {item}, {bin}, -1, -1, true, 1024, true, 1, false); start = 1; @@ -170,11 +171,11 @@ TEST_CASE("Coverage bins run full independent temporal programs", "[xtrigger][co const auto req = engine.ExprNewSignal(&start), ack = engine.ExprNewSignal(&done); const auto ready = engine.ExprNewSignal(&stable); auto handle = engine.ArmSample(XPhase::RisingStable); - XCoverageItem item; item.pattern = true; - XCoverageBin long_bin; long_bin.program_kind = 1; long_bin.overlap = false; - long_bin.steps = {{XSequenceStepKind::Wait, req}, {XSequenceStepKind::Within, ack, 1, 4}, + XCoverageItem item; item.kind = XCoverageItemKind::Pattern; + XCoverageBin long_bin; long_bin.program.kind = XTriggerProgramKind::Sequence; long_bin.program.overlap = false; + long_bin.program.steps = {{XSequenceStepKind::Wait, req}, {XSequenceStepKind::Within, ack, 1, 4}, {XSequenceStepKind::Hold, ready, 0, 0, 2}}; - auto short_bin = long_bin; short_bin.steps[1].maximum = 1; + auto short_bin = long_bin; short_bin.program.steps[1].maximum = 1; engine.AttachCoverage(handle, {item}, {long_bin, short_bin}); start = 1; REQUIRE(engine.RunUntil(2).hits.empty()); start = 0; REQUIRE(engine.RunUntil(2).hits.empty()); @@ -192,9 +193,9 @@ TEST_CASE("Coverage FSM terminal filtering ends every terminal attempt", "[xtrig XClock clock([](bool) { return 0; }); XTriggerEngine engine(clock); XData start(1, XData::InOut), success(1, XData::InOut), failure(1, XData::InOut); - XCoverageItem item; item.pattern = true; - XCoverageBin bin; bin.program_kind = 2; bin.overlap = false; bin.state_count = 2; - bin.transitions = {{0, engine.ExprNewSignal(&start), 1, 0, false}, + XCoverageItem item; item.kind = XCoverageItemKind::Pattern; + XCoverageBin bin; bin.program.kind = XTriggerProgramKind::Fsm; bin.program.overlap = false; bin.program.state_count = 2; + bin.program.transitions = {{0, engine.ExprNewSignal(&start), 1, 0, false}, {1, engine.ExprNewSignal(&success), 0, 7, true}, {1, engine.ExprNewSignal(&failure), 0, 8, true}}; bin.terminals = {7}; @@ -219,9 +220,9 @@ TEST_CASE("Coverage pattern overlap preserves simultaneous completion counts", " XTriggerEngine engine(clock); XData start(1, XData::InOut), done(1, XData::InOut); auto h = engine.ArmSample(XPhase::RisingStable); - XCoverageItem item; item.pattern = true; - XCoverageBin bin; bin.program_kind = 1; bin.overlap = true; bin.max_active = 3; - bin.steps = {{XSequenceStepKind::Wait, engine.ExprNewSignal(&start)}, + XCoverageItem item; item.kind = XCoverageItemKind::Pattern; + XCoverageBin bin; bin.program.kind = XTriggerProgramKind::Sequence; bin.program.overlap = true; bin.program.max_active = 3; + bin.program.steps = {{XSequenceStepKind::Wait, engine.ExprNewSignal(&start)}, {XSequenceStepKind::Within, engine.ExprNewSignal(&done), 1, 5}}; engine.AttachCoverage(h, {item}, {bin}); start = 1; REQUIRE(engine.RunUntil(6).hits.empty()); @@ -234,14 +235,14 @@ TEST_CASE("Coverage rejects malformed pattern descriptors atomically", "[xtrigge XClock clock([](bool) { return 0; }); XTriggerEngine engine(clock); auto h = engine.ArmSample(XPhase::RisingStable); - XCoverageItem item; item.pattern = true; - XCoverageBin bin; bin.overlap = false; bin.root = engine.ExprNewConst(1); - auto invalid = bin; invalid.mode = static_cast(99); + XCoverageItem item; item.kind = XCoverageItemKind::Pattern; + XCoverageBin bin; bin.program.overlap = false; bin.program.root = engine.ExprNewConst(1); + auto invalid = bin; invalid.program.mode = static_cast(99); REQUIRE_THROWS(engine.AttachCoverage(h, {item}, {invalid})); - invalid = bin; invalid.max_active = 0; + invalid = bin; invalid.program.max_active = 0; REQUIRE_THROWS(engine.AttachCoverage(h, {item}, {invalid})); - invalid = bin; invalid.program_kind = 2; invalid.state_count = 1; - invalid.root = -1; invalid.terminals = {99}; + invalid = bin; invalid.program.kind = XTriggerProgramKind::Fsm; invalid.program.state_count = 1; + invalid.program.root = -1; invalid.terminals = {99}; REQUIRE_THROWS(engine.AttachCoverage(h, {item}, {invalid})); engine.AttachCoverage(h, {item}, {bin}); engine.RunUntil(4); @@ -256,11 +257,11 @@ TEST_CASE("Coverage shares an FSM and routes every terminal to its bins", "[xtri const auto req = engine.ExprNewSignal(&request), rsp = engine.ExprNewSignal(&response); const auto good = engine.ExprNewSignal(&correct); const auto ok = engine.ExprNewBinary(static_cast(ExprOp::LAND), rsp, good); - XCoverageItem item; item.pattern = true; - XCoverageBin first; first.program_kind = 2; first.state_count = 2; first.overlap = false; - first.transitions = {{0, req, 1, 0, false}, {1, ok, 0, 7, true}, {1, rsp, 0, 8, true}}; + XCoverageItem item; item.kind = XCoverageItemKind::Pattern; + XCoverageBin first; first.program.kind = XTriggerProgramKind::Fsm; first.program.state_count = 2; first.program.overlap = false; + first.program.transitions = {{0, req, 1, 0, false}, {1, ok, 0, 7, true}, {1, rsp, 0, 8, true}}; first.terminals = {7}; - auto second = first; second.terminals = {8}; second.kind = 2; + auto second = first; second.terminals = {8}; second.kind = XCoverageBinKind::Illegal; auto all = first; all.terminals.clear(); const auto handle = engine.ArmSample(XPhase::RisingStable); engine.AttachCoverage(handle, {item}, {first, second, all}, -1, -1, false, 1024, false, 1, true); @@ -295,10 +296,10 @@ TEST_CASE("Shared coverage FSM retains simultaneous counts for different termina XTriggerEngine engine(clock); XData request(1, XData::InOut), response(1, XData::InOut); const auto req = engine.ExprNewSignal(&request), rsp = engine.ExprNewSignal(&response); - XCoverageItem item; item.pattern = true; - XCoverageBin fast; fast.program_kind = 2; fast.state_count = 3; - fast.overlap = true; fast.max_active = 3; - fast.transitions = {{0, req, 1, 0, false}, {1, rsp, 0, 7, true}, + XCoverageItem item; item.kind = XCoverageItemKind::Pattern; + XCoverageBin fast; fast.program.kind = XTriggerProgramKind::Fsm; fast.program.state_count = 3; + fast.program.overlap = true; fast.program.max_active = 3; + fast.program.transitions = {{0, req, 1, 0, false}, {1, rsp, 0, 7, true}, {1, -1, 2, 0, false}, {2, rsp, 0, 8, true}}; fast.terminals = {7}; auto slow = fast; slow.terminals = {8}; @@ -325,13 +326,13 @@ TEST_CASE("Coverage shares only identical execution policies in the same point", XTriggerEngine engine(clock); XData request(1, XData::InOut), response(1, XData::InOut); const auto req = engine.ExprNewSignal(&request), rsp = engine.ExprNewSignal(&response); - XCoverageItem item; item.pattern = true; - XCoverageBin original; original.program_kind = 1; original.overlap = false; - original.steps = {{XSequenceStepKind::Wait, req}, {XSequenceStepKind::Within, rsp, 1, 4}}; + XCoverageItem item; item.kind = XCoverageItemKind::Pattern; + XCoverageBin original; original.program.kind = XTriggerProgramKind::Sequence; original.program.overlap = false; + original.program.steps = {{XSequenceStepKind::Wait, req}, {XSequenceStepKind::Within, rsp, 1, 4}}; auto alias = original; auto other_point = original; other_point.item = 1; - auto short_window = original; short_window.steps[1].maximum = 2; - auto overlapping = original; overlapping.overlap = true; overlapping.max_active = 3; + auto short_window = original; short_window.program.steps[1].maximum = 2; + auto overlapping = original; overlapping.program.overlap = true; overlapping.program.max_active = 3; auto h = engine.ArmSample(XPhase::RisingStable); engine.AttachCoverage(h, {item, item}, {original, alias, other_point, short_window, overlapping}); REQUIRE(engine.CoverageExecutionCount(h) == 4); @@ -342,3 +343,45 @@ TEST_CASE("Coverage shares only identical execution policies in the same point", for (size_t bin = 0; bin < 5; ++bin) REQUIRE(snapshot.counters[12 + bin] == 0); REQUIRE(engine.Disarm(h)); } + +TEST_CASE("Explicit coverage kinds reject mixed definitions", "[coverage][validation]") { + XClock clock([](bool) { return 0; }); + XEngine engine(clock); + XData signal(8, XData::InOut); + const auto root = engine.ExprNewConst(1); + auto handle = engine.ArmSample(XPhase::RisingStable); + XCoverageItem item; + XCoverageBin bin; + bin.program.root = root; + REQUIRE_THROWS_AS(engine.AttachCoverage(handle, {item}, {bin}), std::invalid_argument); + item.kind = XCoverageItemKind::Pattern; + item.signal = &signal; + REQUIRE_THROWS_AS(engine.AttachCoverage(handle, {item}, {bin}), std::invalid_argument); + item.signal = nullptr; + bin.program.overlap = false; + bin.program.transitions = {{0, root, 0, 0, true}}; + REQUIRE_THROWS_AS(engine.AttachCoverage(handle, {item}, {bin}), std::invalid_argument); + bin.program.transitions.clear(); + bin.program.kind = static_cast(99); + REQUIRE_THROWS_AS(engine.AttachCoverage(handle, {item}, {bin}), std::invalid_argument); + bin.program.kind = XTriggerProgramKind::Expr; + engine.AttachCoverage(handle, {item}, {bin}); + engine.RunUntil(2); + REQUIRE(engine.CoverageSnapshot(handle).counters.back() == 1); +} + +TEST_CASE("Trigger and coverage share program validation", "[coverage][trigger][validation]") { + XClock clock([](bool) { return 0; }); + XEngine engine(clock); + const auto root = engine.ExprNewConst(1); + const std::vector invalid{{static_cast(99), root}}; + REQUIRE_FALSE(engine.ArmSequence(invalid).IsValid()); + XCoverageItem item; + item.kind = XCoverageItemKind::Pattern; + XCoverageBin bin; + bin.program.kind = XTriggerProgramKind::Sequence; + bin.program.steps = invalid; + bin.program.overlap = false; + const auto handle = engine.ArmSample(XPhase::RisingStable); + REQUIRE_THROWS_AS(engine.AttachCoverage(handle, {item}, {bin}), std::invalid_argument); +} From 95801a4a1848186d8965a3e6f784f40f5bd44f14 Mon Sep 17 00:00:00 2001 From: Makiras <22570332+Makiras@users.noreply.github.com> Date: Sat, 10 Oct 2026 19:28:47 +0800 Subject: [PATCH 08/10] refactor!: clarify monitor APIs and separate SDK from implementation Rename xcomuse to xmonitor and replace ComUse classes with the XStepCallback, XConditionCheck, XExprCheck, XRangeCheck, XFsmMonitor, XEcho and XByteBuffer names across C++, SWIG bindings, tests and documentation. Expose CompareOp consistently and remove the old paths and names without compatibility aliases. Group common comparison, memory, callback-storage and lifetime helpers under include/xspcomm/common. Move trigger matching, coverage state and native-memory implementation headers beside their sources; keep source include paths private to the library, JavaScript source build and implementation tests. Extract phases, handles and sampling results into runtime/types.h independently of XClock. Forward-declare XEngine's private Watcher and define it under src/runtime, retaining contiguous registration storage and inline matching. Group XData implementations under src/data and align executor/state filenames with their responsibilities. Update SDK staging, migration and maintainer docs. Bump the native ABI from 4 to 5 for renamed exported symbols; retain coverage descriptor protocol 5 and document the coordinated 0.3.0.dev2 migration. Validation: - Release and ASan/UBSan native suites: 10/10 each. - Python binding checks and xreactor frontend suite: 1021 frontend tests passed. - 70 standalone C++17/C++20 header checks; all six SWIG interfaces generated. - Installed-SDK consumer and TLM wrapper compiled; SDK consumer ran successfully. - Public type sizes/alignment and randomized trigger/coverage outputs unchanged. - Five local matching/coverage benchmarks changed by -1.36% to +2.19%. BREAKING CHANGE: Native consumers must rebuild against ABI 5, and source and binding clients must migrate the removed ComUse names and header paths. --- CMakeLists.txt | 2 +- cmake/Sources.cmake | 24 +-- docs/APIs.cn.md | 90 +++++----- docs/architecture.md | 91 +++++++--- docs/maintainer-guidance.md | 20 ++- docs/xdata_xclock_optimizations.md | 12 +- .../function.h => common/callback_storage.h} | 4 +- include/xspcomm/common/compare.h | 4 +- .../common/{operation.h => compare_op.h} | 5 +- include/xspcomm/{detail => common}/lifetime.h | 4 +- include/xspcomm/common/memory.h | 16 +- .../{memory/access.h => memory_access.h} | 0 .../xspcomm/{xcomuse => monitor}/callback.h | 20 +-- .../xspcomm/{xcomuse => monitor}/condition.h | 14 +- include/xspcomm/{xcomuse => monitor}/expr.h | 10 +- include/xspcomm/{xcomuse => monitor}/fsm.h | 20 +-- include/xspcomm/{xcomuse => monitor}/range.h | 10 +- include/xspcomm/{trigger => runtime}/types.h | 11 +- include/xspcomm/xcallback.h | 2 +- include/xspcomm/xclock.h | 11 +- include/xspcomm/xcomm.h | 1 + include/xspcomm/xcomuse.h | 12 -- include/xspcomm/xcoverage.h | 1 - include/xspcomm/xengine.h | 21 +-- include/xspcomm/xmonitor.h | 12 ++ include/xspcomm/xtrigger.h | 2 + src/CMakeLists.txt | 4 +- src/common/lifetime.cpp | 2 +- src/coverage/registration.cpp | 7 +- src/coverage/{engine.cpp => state.cpp} | 2 +- .../xspcomm/detail => src}/coverage/state.h | 2 +- .../data_backends.cpp => data/backends.cpp} | 2 +- src/{core => data}/data.cpp | 0 .../detail => src}/data/native_memory.h | 6 +- src/{core => data}/pin.cpp | 0 src/{core/data_value.cpp => data/value.cpp} | 0 src/{core/data_vpi.cpp => data/vpi.cpp} | 0 src/{comuse => monitor}/callback.cpp | 38 ++--- src/{comuse => monitor}/condition.cpp | 76 ++++----- src/{comuse => monitor}/expr.cpp | 38 ++--- src/{comuse => monitor}/fsm.cpp | 32 ++-- src/{comuse => monitor}/range.cpp | 16 +- src/runtime/engine.cpp | 13 +- src/runtime/registration.h | 24 +++ src/trigger/{pattern.cpp => executor.cpp} | 2 +- .../program.h => src/trigger/executor.h | 4 +- src/trigger/matcher.cpp | 2 +- .../xspcomm/detail => src}/trigger/matcher.h | 6 +- .../detail => src}/trigger/registration.h | 8 +- swig/javascript/CMakeLists.txt | 4 +- swig/xcomm.i | 23 ++- tests/CMakeLists.txt | 5 +- tests/bindings/python/test_python.py | 2 +- tests/unit/test_coverage_core.cpp | 2 +- tests/unit/test_trigger_matcher.cpp | 2 +- tests/unit/test_xexpr.cpp | 6 +- tests/unit/test_xfsm.cpp | 12 +- .../{test_xcomuse.cpp => test_xmonitor.cpp} | 156 +++++++++--------- tests/unit/xdata_cases.cpp | 24 +-- 59 files changed, 513 insertions(+), 426 deletions(-) rename include/xspcomm/{detail/function.h => common/callback_storage.h} (65%) rename include/xspcomm/common/{operation.h => compare_op.h} (55%) rename include/xspcomm/{detail => common}/lifetime.h (85%) rename include/xspcomm/common/{memory/access.h => memory_access.h} (100%) rename include/xspcomm/{xcomuse => monitor}/callback.h (79%) rename include/xspcomm/{xcomuse => monitor}/condition.h (92%) rename include/xspcomm/{xcomuse => monitor}/expr.h (84%) rename include/xspcomm/{xcomuse => monitor}/fsm.h (74%) rename include/xspcomm/{xcomuse => monitor}/range.h (87%) rename include/xspcomm/{trigger => runtime}/types.h (89%) delete mode 100644 include/xspcomm/xcomuse.h create mode 100644 include/xspcomm/xmonitor.h rename src/coverage/{engine.cpp => state.cpp} (99%) rename {include/xspcomm/detail => src}/coverage/state.h (98%) rename src/{core/data_backends.cpp => data/backends.cpp} (99%) rename src/{core => data}/data.cpp (100%) rename {include/xspcomm/detail => src}/data/native_memory.h (95%) rename src/{core => data}/pin.cpp (100%) rename src/{core/data_value.cpp => data/value.cpp} (100%) rename src/{core/data_vpi.cpp => data/vpi.cpp} (100%) rename src/{comuse => monitor}/callback.cpp (77%) rename src/{comuse => monitor}/condition.cpp (74%) rename src/{comuse => monitor}/expr.cpp (51%) rename src/{comuse => monitor}/fsm.cpp (95%) rename src/{comuse => monitor}/range.cpp (76%) create mode 100644 src/runtime/registration.h rename src/trigger/{pattern.cpp => executor.cpp} (99%) rename include/xspcomm/detail/trigger/program.h => src/trigger/executor.h (96%) rename {include/xspcomm/detail => src}/trigger/matcher.h (93%) rename {include/xspcomm/detail => src}/trigger/registration.h (95%) rename tests/unit/{test_xcomuse.cpp => test_xmonitor.cpp} (77%) diff --git a/CMakeLists.txt b/CMakeLists.txt index ddcb1af..326abc1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ else() endif() # Increment only when the native C++ ABI becomes incompatible. -set(XSPCOMM_ABI_VERSION 4) +set(XSPCOMM_ABI_VERSION 5) list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) diff --git a/cmake/Sources.cmake b/cmake/Sources.cmake index ebe6c70..ecc6714 100644 --- a/cmake/Sources.cmake +++ b/cmake/Sources.cmake @@ -1,25 +1,25 @@ set(XSPCOMM_SOURCES common/lifetime.cpp common/memory.cpp - comuse/callback.cpp - comuse/condition.cpp - comuse/expr.cpp - comuse/fsm.cpp - comuse/range.cpp + monitor/callback.cpp + monitor/condition.cpp + monitor/expr.cpp + monitor/fsm.cpp + monitor/range.cpp core/clock.cpp core/coroutine.cpp - core/data.cpp - core/data_backends.cpp - core/data_value.cpp - core/data_vpi.cpp - core/pin.cpp + data/data.cpp + data/backends.cpp + data/value.cpp + data/vpi.cpp + data/pin.cpp core/port.cpp core/signal_cfg.cpp core/util.cpp - coverage/engine.cpp + coverage/state.cpp expr/engine.cpp expr/parser.cpp - trigger/pattern.cpp + trigger/executor.cpp coverage/registration.cpp runtime/engine.cpp trigger/matcher.cpp diff --git a/docs/APIs.cn.md b/docs/APIs.cn.md index 0f2a15f..424a736 100644 --- a/docs/APIs.cn.md +++ b/docs/APIs.cn.md @@ -3,7 +3,7 @@ 本文档基于当前项目源码整理,主要对应以下文件: - 核心 C++ API:`include/xspcomm/xdata.h`、`xport.h`、`xclock.h`、`xsignal_cfg.h` -- 组合检查与表达式/FSM:`include/xspcomm/xcomuse.h`、`xcomuse/`、`xexpr.h`、`xcomuse/expr.h`、`xcomuse/fsm.h` +- 组合检查与表达式/FSM:`include/xspcomm/xmonitor.h`、`monitor/`、`xexpr.h`、`monitor/expr.h`、`monitor/fsm.h` - 多语言封装:`swig/python/xcomm.py`、`swig/java/java.i`、`swig/scala/xsp.scala`、`swig/golang/golang.i`、`swig/lua/lua.i`、`swig/javascript/xspcomm.js` xspcomm 的核心抽象是: @@ -12,7 +12,7 @@ xspcomm 的核心抽象是: - `XPort`:一组 `XData` 的命名集合,用于批量连接、批量 IO 类型/写入模式设置,以及被 `XClock` 统一刷新。 - `XClock`:仿真时钟驱动器,负责拉高/拉低时钟引脚、调用 DUT step 函数、在上升沿/下降沿刷新端口并执行回调。 - `XSignalCFG`:从 YAML 配置或 YAML 字符串创建绑定到 native memory 的 `XData`。 -- `ComUse*`、`ExprEngine`、`ComUseFsmTrigger`:用于通用仿真辅助,例如条件触发、表达式触发、FSM 触发、数据数组和字符串辅助。 +- `XConditionCheck`、`XExprCheck`、`XFsmMonitor`:时钟驱动的条件、表达式及 FSM 检查器。`ExprEngine` 提供表达式求值,`XByteBuffer` 和 `CString` 提供内存与字符串工具。 ## 通用约定 @@ -22,10 +22,10 @@ C++ API 位于 `xspcomm` 命名空间。常用头文件: ```c++ #include "xspcomm/xcomm.h" // 常用核心 API 汇总 -#include "xspcomm/xcomuse.h" // ComUse、Expr、FSM 汇总 +#include "xspcomm/xmonitor.h" // Monitor、Expr、FSM 汇总 ``` -ComUse 组件位于 `xspcomm/xcomuse/`,可以按需包含 `callback.h`、`condition.h`、`range.h`、`expr.h` 和 `fsm.h`。独立的表达式引擎位于 `xexpr.h`。`common/compare.h` 提供 XData、表达式和检查器共用的比较算法;`common/memory.h` 提供数组、指针及字符串工具。 +Monitor 组件位于 `xspcomm/monitor/`,可以按需包含 `callback.h`、`condition.h`、`range.h`、`expr.h` 和 `fsm.h`。独立的表达式引擎位于 `xexpr.h`。`common/compare.h` 提供 XData、表达式和检查器共用的比较算法;`common/memory.h` 提供数组、指针及字符串工具。 多语言 SWIG 包通常复用 C++ 名称,部分语言会增加更符合本语言习惯的包装方法,见“多语言差异”。 @@ -494,11 +494,11 @@ if (eng.Eval(root)) { } ``` -## ComUse 系列 +## Monitor 系列 -### ComUseStepCb +### XStepCallback -`ComUseStepCb` 是可挂到 `XClock::StepRis/StepFal` 的基类。派生类覆写 `Call()` 实现逻辑。 +`XStepCallback` 是可挂到 `XClock::StepRis/StepFal` 的基类。派生类覆写 `Call()` 实现逻辑。 | API | 说明 | | --- | --- | @@ -512,13 +512,13 @@ if (eng.Eval(root)) { | `uint64_t CSelf()` | 返回自身地址。 | | `uint64_t cycle` | 当前回调周期,由 `Cb()` 设置。 | -### ComUseEcho +### XEcho 当 `valid != 0` 时打印 `data`。 | API | 说明 | | --- | --- | -| `ComUseEcho(uint64_t valid, uint64_t data, bool stderr_echo = true, std::string fmt = "%c", int convert = 0)` | `valid` 和 `data` 是 `XData*` 地址,通常传 `xdata.CSelf()`。 | +| `XEcho(uint64_t valid, uint64_t data, bool stderr_echo = true, std::string fmt = "%c", int convert = 0)` | `valid` 和 `data` 是 `XData*` 地址,通常传 `xdata.CSelf()`。 | | `void Call()` | valid 为真时按格式输出。 | `convert` 取值: @@ -531,32 +531,32 @@ if (eng.Eval(root)) { | `3` | `double` | | `4` | `data->String().c_str()` | -### ComUseCondCheck +### XConditionCheck -`ComUseCondCheck` 检查一组条件;任一条件触发时会 `Disable()` 绑定的 clocks,并记录触发 key。 +`XConditionCheck` 检查一组条件;任一条件触发时会 `Disable()` 绑定的 clocks,并记录触发 key。 比较模式: | 枚举 | 说明 | | --- | --- | -| `ComUseCondCmp::EQ` | 等于 | -| `ComUseCondCmp::NE` | 不等于 | -| `ComUseCondCmp::GT` | 大于 | -| `ComUseCondCmp::GE` | 大于等于 | -| `ComUseCondCmp::LT` | 小于 | -| `ComUseCondCmp::LE` | 小于等于 | +| `CompareOp::EQ` | 等于 | +| `CompareOp::NE` | 不等于 | +| `CompareOp::GT` | 大于 | +| `CompareOp::GE` | 大于等于 | +| `CompareOp::LT` | 小于 | +| `CompareOp::LE` | 小于等于 | | API | 说明 | | --- | --- | -| `ComUseCondCheck(XClock *clk = nullptr)` | 可选绑定 clock。 | +| `XConditionCheck(XClock *clk = nullptr)` | 可选绑定 clock。 | | `void BindXClock(XClock *clk)` | 添加触发后需要 disable 的 clock。 | -| `void SetCondition(std::string unique_name, XData *pin, XData *val, ComUseCondCmp cmp, XData *valid = nullptr, XData *valid_value = nullptr, xfunction func = nullptr, uint64_t arg = 0)` | 添加/更新 XData 条件。valid 非空时先检查 valid 与 valid_value。当前源码对 XData 自定义 `func` 有 assert 限制,常规用法应传 `nullptr`。 | -| `void SetCondition(std::string unique_name, uint64_t pin_ptr, uint64_t val_ptr, ComUseCondCmp cmp, int bytes, uint64_t valid_ptr = 0, uint64_t valid_value_ptr = 0, int valid_bytes = 1, xfunction func = nullptr, uint64_t arg = 0)` | 添加/更新内存指针条件。指针比较按 `bytes` 做有符号比较。 | +| `void SetCondition(std::string unique_name, XData *pin, XData *val, CompareOp cmp, XData *valid = nullptr, XData *valid_value = nullptr, xfunction func = nullptr, uint64_t arg = 0)` | 添加/更新 XData 条件。valid 非空时先检查 valid 与 valid_value。当前源码对 XData 自定义 `func` 有 assert 限制,常规用法应传 `nullptr`。 | +| `void SetCondition(std::string unique_name, uint64_t pin_ptr, uint64_t val_ptr, CompareOp cmp, int bytes, uint64_t valid_ptr = 0, uint64_t valid_value_ptr = 0, int valid_bytes = 1, xfunction func = nullptr, uint64_t arg = 0)` | 添加/更新内存指针条件。指针比较按 `bytes` 做有符号比较。 | | `void RemoveCondition(std::string unique_name)` | 删除条件。 | | `std::map ListCondition()` | 返回所有条件及本轮是否触发。 | | `std::vector GetTriggeredConditionKeys()` | 返回本轮触发的 key。 | -| `ComUseCondCmp GetValidCmpMode(std::string unique_name)` | 获取 valid gating 的比较模式。 | -| `void SetValidCmpMode(std::string unique_name, ComUseCondCmp cmp)` | 设置 valid gating 比较模式,默认 `EQ`。 | +| `CompareOp GetValidCmpMode(std::string unique_name)` | 获取 valid gating 的比较模式。 | +| `void SetValidCmpMode(std::string unique_name, CompareOp cmp)` | 设置 valid gating 比较模式,默认 `EQ`。 | | `void ClearClock()` / `ClearCondition()` / `ClearAll()` | 清空绑定 clock、条件或全部。 | | `xfunction AsXDataXFunc(uint64_t func)` | 把函数地址转成 XData 自定义比较 callback。 | | `xfunction AsPtrXFunc(uint64_t func)` | 把函数地址转成 pointer 自定义比较 callback。 | @@ -565,21 +565,21 @@ if (eng.Eval(root)) { 示例: ```c++ -ComUseCondCheck check(&clk); +XConditionCheck check(&clk); clk.StepRis(check.GetCb(), check.CSelf(), "cond-check"); -check.SetCondition("data_eq", &data, &expect, ComUseCondCmp::EQ, &valid, &one); +check.SetCondition("data_eq", &data, &expect, CompareOp::EQ, &valid, &one); clk.Step(1000); // 触发后 clk 会被 Disable() auto keys = check.GetTriggeredConditionKeys(); ``` -### ComUseExprCheck +### XExprCheck -`ComUseExprCheck` 是基于 `ExprEngine` 的表达式触发器。任一表达式求值为非 0 时会 disable 绑定 clocks,并记录触发 key。 +`XExprCheck` 是基于 `ExprEngine` 的表达式触发器。任一表达式求值为非 0 时会 disable 绑定 clocks,并记录触发 key。 | API | 说明 | | --- | --- | -| `ComUseExprCheck(XClock *clk = nullptr)` | 可选绑定 clock。 | +| `XExprCheck(XClock *clk = nullptr)` | 可选绑定 clock。 | | `void BindXClock(XClock *clk)` | 添加触发后需要 disable 的 clock。 | | `int CompileExpr(std::string expr, XSignalCFG *cfg)` | 编译表达式。失败返回 `-1` 并打印错误。 | | `void SetExpr(std::string name, int root)` | 添加/更新表达式 root。 | @@ -593,7 +593,7 @@ auto keys = check.GetTriggeredConditionKeys(); 示例: ```c++ -ComUseExprCheck checker(&clk); +XExprCheck checker(&clk); clk.StepRis(checker.GetCb(), checker.CSelf(), "expr-check"); XData valid(1, XData::InOut); @@ -604,13 +604,13 @@ int root = checker.ExprNewBinary((int)ExprOp::LAND, valid_node, data_eq); checker.SetExpr("hit_data", root); ``` -### ComUseFsmTrigger +### XFsmMonitor -`ComUseFsmTrigger` 加载一个简易 FSM 脚本。触发 `trigger` 后会 disable 绑定 clocks。 +`XFsmMonitor` 加载一个简易 FSM 脚本。触发 `trigger` 后会 disable 绑定 clocks。 | API | 说明 | | --- | --- | -| `ComUseFsmTrigger(XClock *clk = nullptr)` | 可选绑定 clock。 | +| `XFsmMonitor(XClock *clk = nullptr)` | 可选绑定 clock。 | | `void BindXClock(XClock *clk)` | 添加触发后需要 disable 的 clock。 | | `void LoadProgram(std::string program, XSignalCFG *cfg)` | 加载 FSM 脚本。解析失败会打印错误并清空 FSM,不抛异常给调用者。 | | `void Reset()` | 回到 start state,清空 `$flag*` 和 `$counter*`。 | @@ -658,15 +658,15 @@ state S2: 表达式语法同 `ExprEngine`。`$flag*` 和 `$counter*` 是 FSM 内部变量,也可在表达式中使用。 -### ComUseDataArray +### XByteBuffer -`ComUseDataArray` 是 byte buffer 包装,可拥有内存,也可引用外部地址。 +`XByteBuffer` 是 byte buffer 包装,可拥有内存,也可引用外部地址。 | API | 说明 | | --- | --- | -| `ComUseDataArray(int byte_size)` | 创建自有 buffer。 | -| `ComUseDataArray(uint64_t base, int byte_size)` | 引用外部地址。 | -| `ComUseDataArray *Copy()` | 拷贝一份自有 buffer。 | +| `XByteBuffer(int byte_size)` | 创建自有 buffer。 | +| `XByteBuffer(uint64_t base, int byte_size)` | 引用外部地址。 | +| `XByteBuffer *Copy()` | 拷贝一份自有 buffer。 | | `void SyncFrom(uint64_t addr, int size)` | 从地址拷贝到内部 buffer。 | | `void SyncTo(uint64_t addr, int size)` | 从内部 buffer 拷贝到地址。 | | `void SetZero()` | 清零。 | @@ -676,19 +676,19 @@ state S2: | `int FromBytes(std::vector &input)` | 从 bytes 写入,返回实际写入长度。 | | `operator==` | 比较 size 和内容。 | -### ComUseRangeCheck +### XRangeCheck -`ComUseRangeCheck` 生成可传给 `ComUseCondCheck` 的范围比较函数。 +`XRangeCheck` 生成可传给 `XConditionCheck` 的范围比较函数。 | API | 说明 | | --- | --- | -| `ComUseRangeCheck(int range, int bytes)` | `bytes >= 1`;pointer 比较按 little-endian 读取指定的字节数,支持未对齐地址和超过 64 bit 的值。 | +| `XRangeCheck(int range, int bytes)` | `bytes >= 1`;pointer 比较按 little-endian 读取指定的字节数,支持未对齐地址和超过 64 bit 的值。 | | `static bool cmp(uint64_t t, uint64_t c, int r)` | 当 `r >= 0` 时检查 `c-r <= t <= c`;当 `r < 0` 时检查 `c <= t <= c-r`。按数学整数判断,不在零或最大值处回绕。 | | `uint64_t CSelf()` | 返回自身地址,用作 callback arg。 | | `GetArrayCmp()` | 返回 pointer 比较 callback。 | | `GetXDataCmp()` | 返回 XData 比较 callback,使用信号的完整位宽;不同位宽按无符号零扩展,含 X/Z 时返回 false。`bytes` 不截断 XData。 | -`range` 是有符号 `int` 容差,比较值的位宽不受其限制。例如 `ComUseRangeCheck(1, 16)` +`range` 是有符号 `int` 容差,比较值的位宽不受其限制。例如 `XRangeCheck(1, 16)` 可以比较两个 128-bit buffer;`0xffffffffffffffff` 位于 `0x10000000000000000` 下方 1 的范围内。 宽比较直接逐 32-bit word 计算差值,不分配临时大整数;每个 XData 操作数只刷新一次,不写回。 @@ -1021,7 +1021,7 @@ JS wrapper 增加: ```c++ #include "xspcomm/xcomm.h" -#include "xspcomm/xcomuse.h" +#include "xspcomm/xmonitor.h" using namespace xspcomm; @@ -1041,9 +1041,9 @@ int main() { XClock clk([](bool dump) { return 0; }, {&clk_pin}, {&port}); - ComUseCondCheck check(&clk); + XConditionCheck check(&clk); clk.StepRis(check.GetCb(), check.CSelf(), "cond"); - check.SetCondition("data_hit", &data, &expect, ComUseCondCmp::EQ, + check.SetCondition("data_hit", &data, &expect, CompareOp::EQ, &valid, &one); clk.Step(1000); @@ -1056,7 +1056,7 @@ int main() { ### C++:表达式触发 ```c++ -ComUseExprCheck check(&clk); +XExprCheck check(&clk); clk.StepRis(check.GetCb(), check.CSelf(), "expr"); XData valid(1, XData::InOut); @@ -1069,4 +1069,4 @@ check.SetExpr("hit_data", root); clk.Step(1000); ``` -若要使用带名字的信号表达式,`ExprEngine` 更适合配合 `XSignalCFG` 或手工 `RegisterExternalSignal()`;`ComUseExprCheck` 这层更偏向“把已经构建好的表达式 root 挂到 clock 上”。 +若要使用带名字的信号表达式,`ExprEngine` 更适合配合 `XSignalCFG` 或手工 `RegisterExternalSignal()`;`XExprCheck` 这层更偏向“把已经构建好的表达式 root 挂到 clock 上”。 diff --git a/docs/architecture.md b/docs/architecture.md index f7f2c62..65d8840 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -2,23 +2,28 @@ Public interfaces are installed below `include/xspcomm`. Common class entry headers (`xdata.h`, `xclock.h`, `xexpr.h`, `xtrigger.h`) describe their own modules; -`xcomm.h` and `xcomuse.h` are the aggregate entry points. +`xcomm.h` and `xmonitor.h` are the aggregate entry points. - `common/`: comparison operations and algorithms, plus memory/string utilities. - `common/memory/access.h` provides non-owning reads and writes with exact access + `compare_op.h` defines the shared comparison enum; `memory_access.h` + provides non-owning reads and writes with exact access widths. It depends only on standard headers and is shared by comparison and - native signal backends. `ComUseDataArray` retains storage ownership/copy duties. -- `xcomuse/`: clock callbacks, condition/range checkers, expression construction - and the text-program FSM adapter. `ComUseExprCheck` uses `ComUseCondCheck`'s + native signal backends. `XByteBuffer` retains storage ownership/copy duties. + `callback_storage.h` and `lifetime.h` supply callback storage and ownership + helpers used by the public callback templates. +- `monitor/`: clock callbacks, condition/range checkers, expression construction + and the text-program FSM adapter. `XExprCheck` uses `XConditionCheck`'s registration namespace, hit processing and clock-stop behavior. Native pointer, XData and expression evaluation keep their own comparison semantics. -- `xpattern.h` and `trigger/types.h`: common trigger program descriptions, event - modes, sequence/FSM descriptors, handles and sampling results. -- `detail/trigger/`: program validation, single-execution state advancement, - overlapping matchers and trigger registrations. The matching kernel has no - coverage dependency. Implementations live together under `src/trigger`. -- `xcoverage.h` and `detail/coverage/`: explicitly typed points/bins and snapshots, - plus coverage execution history and statistical policy. Bins contain a shared +- `xpattern.h`: trigger program descriptions, event modes and sequence/FSM + descriptors. `runtime/types.h` contains phases, handles, hits and run results; + it depends only on standard headers and does not pull in `XClock`. +- `src/trigger/`: program validation, single-execution state advancement + (`executor.h/.cpp`), overlapping matchers (`matcher.h/.cpp`) and trigger + registrations (`registration.h`). The matching kernel has no coverage dependency. +- `xcoverage.h`: explicitly typed points/bins and snapshots. Coverage execution + history and statistical policy live in `src/coverage/state.h/.cpp`. + Bins contain a shared `XTriggerProgram` description and select completion results. Coverage owns crosses, counters, classification and terminal selection. - `xengine.h` and `src/runtime`: the shared clock/phase schedule and registration @@ -26,10 +31,17 @@ headers (`xdata.h`, `xclock.h`, `xexpr.h`, `xtrigger.h`) describe their own modu statistics. `xtrigger.h` retains the `XTriggerEngine` entry name as an alias of `XEngine`; language bindings retain their existing class name. Coverage-specific registration operations live under `src/coverage`, not the trigger kernel. -- `detail/data/native_memory.h`: the templated native-to-vector storage adapter. +- `src/runtime/registration.h`: the private `XEngine::Watcher` definition, + combining trigger state with optional coverage state. `xengine.h` only declares + this type. The existing vector storage and inline execution path are retained; + hiding the definition adds no indirection or per-registration allocation. +- `src/data/`: signal storage (`data.cpp`), values and comparisons (`value.cpp`), + backend bindings (`backends.cpp`), VPI access (`vpi.cpp`) and individual + bit references (`pin.cpp`). + `native_memory.h` is the templated native-to-vector storage adapter. Fixed word counts stay expanded at compile time; widths are selected at binding. -- `src/core`: signal storage/value operations, backend bindings, ports, clocks, - coroutines and configuration. YAML parsing stays in the configuration source; +- `src/core/`: clocks, port collections, coroutines and configuration. + YAML parsing stays in the configuration source; third-party YAML headers are not part of the public header dependency graph. - `swig/`: common binding definitions and language-specific wrappers. Native and JavaScript builds use the source list in `cmake/Sources.cmake`. @@ -38,24 +50,59 @@ headers (`xdata.h`, `xclock.h`, `xexpr.h`, `xtrigger.h`) describe their own modu Run native tests with `ctest --test-dir build --output-on-failure`. - `third_party/`: vendored fkYAML and Catch2 headers, with their existing licenses. +`include/xspcomm/` is the installed SDK. Pure implementation headers live beside +their sources under `src/` and are not installed. Public templates keep their +required helpers in `include/xspcomm/common/`; the `xspcomm::detail` namespace +marks those helper types. Native and JavaScript targets use `src/` as a private +include directory. Only tests of implementation types use it directly. + ## Header migration Update includes directly; removed paths do not have forwarding headers: | Previous include | Current include | | --- | --- | +| `xspcomm/xcomuse.h` | `xspcomm/xmonitor.h` | +| `xspcomm/xcomuse/{callback,condition,range,expr,fsm}.h` | `xspcomm/monitor/{callback,condition,range,expr,fsm}.h` | | `xspcomm/xcomuse/compare.h` | `xspcomm/common/compare.h` | | `xspcomm/xcomuse/utils.h` | `xspcomm/common/memory.h` | -| `xspcomm/xfsm.h` | `xspcomm/xcomuse/fsm.h` | -| `xspcomm/xexpr.h` for `ComUseExprCheck` | `xspcomm/xcomuse/expr.h` | +| `xspcomm/xfsm.h` | `xspcomm/monitor/fsm.h` | +| `xspcomm/xexpr.h` for `ComUseExprCheck` | `xspcomm/monitor/expr.h` | +| `xspcomm/common/operation.h` | `xspcomm/common/compare_op.h` | +| `xspcomm/detail/function.h` | `xspcomm/common/callback_storage.h` | +| `xspcomm/detail/lifetime.h` | `xspcomm/common/lifetime.h` | +| `xspcomm/common/memory/access.h` | `xspcomm/common/memory_access.h` | +| `xspcomm/trigger/types.h` | `xspcomm/runtime/types.h` | | `xspcomm/tlm_msg.h` | `xspcomm/tlm/message.h` | | `xspcomm/tlm_pbsb.h` | `xspcomm/tlm/pubsub.h` | +The former `xspcomm/detail/{trigger,coverage,data}/*.h` implementation headers +are no longer installed. Use `xpattern.h`, `xcoverage.h` and `xengine.h` for +program descriptions, coverage descriptors and registration. Matching and +native-memory implementation headers belong to `src/trigger/`, `src/coverage/` +and `src/data/`; they are not replacement SDK include paths. + +Monitor and utility names migrate in C++, SWIG bindings and Python together: + +| Previous name | Current name | +| --- | --- | +| `ComUseStepCb` | `XStepCallback` | +| `ComUseCondCheck` | `XConditionCheck` | +| `ComUseExprCheck` | `XExprCheck` | +| `ComUseRangeCheck` | `XRangeCheck` | +| `ComUseFsmTrigger` | `XFsmMonitor` | +| `ComUseEcho` | `XEcho` | +| `ComUseDataArray` | `XByteBuffer` | +| `ComUseCondCmp` | `CompareOp` | + +Removed names have no compatibility aliases. `CompareOp` is the existing shared +comparison enum, now exposed with the same name in language bindings. + `xinstance.h` and its `test_xdata()` helper are removed from the production SDK; that test implementation is compiled into `test_xdata` instead. Python-only third-call declarations live beside the Python wrapper. Existing signal access and clock stepping interfaces are retained. Coverage descriptors and -clock/checker/runtime layouts have changed, so native clients must rebuild against ABI 4; +clock/checker/runtime layouts have changed, so native clients must rebuild against ABI 5; source clients must also update the includes above. ## Trigger programs and coverage @@ -71,7 +118,7 @@ value points. `XCoverageBin.kind` selects Normal, Ignore, Illegal or Default, while `XCoverageBin.program` supplies matching and `terminals` selects FSM completion results. Impossible field combinations are rejected at registration. `CoverageVersion() == 5` identifies this descriptor protocol; the native library -ABI is independently versioned as 4. Protocol 4 frontends must migrate flat bin +ABI is independently versioned as 5. Protocol 4 frontends must migrate flat bin program fields to `bin.program` and replace `item.pattern` with an explicit kind. Equivalent programs within one point share an execution when their roots, @@ -90,14 +137,14 @@ Crosses containing pattern points are rejected until completion correlation semantics are defined. Program reclamation during a long execution and complete expression width/signed/cast/four-state semantics remain separate work. -## ComUse callback ownership +## Monitor callback ownership -`ComUseStepCb.Attach(clock, rising)` registers a managed clock callback; `Detach` +`XStepCallback.Attach(clock, rising)` registers a managed clock callback; `Detach` and destruction remove its registrations. Destroying the clock first is safe. Removal during another callback defers vector erasure until iteration finishes. The callback base has a virtual destructor and cannot be copied with registrations. -`ComUseCondCheck.BindXClock` specifies clocks to stop on a hit, separately from +`XConditionCheck.BindXClock` specifies clocks to stop on a hit, separately from sampling registration. `ClearClock` clears that stop list. Existing `GetCb` / `CSelf` raw callback registration remains available and requires the caller to unregister before destroying the checker; use `Attach` for managed ownership. diff --git a/docs/maintainer-guidance.md b/docs/maintainer-guidance.md index e91b74c..2bc7ed7 100644 --- a/docs/maintainer-guidance.md +++ b/docs/maintainer-guidance.md @@ -57,7 +57,9 @@ extensions; a Python version specifier alone cannot verify C++ ABI identity. The native trigger engine changes the C++ layouts of `XClock`, `XData`, and `ExprNode`, which introduced native ABI 2. Complete per-bin trigger programs now -reorganize `XCoverageBin` and checker/runtime layouts, so the current native ABI is 4 (`libxspcomm.so.4`). Rebuild +reorganize `XCoverageBin` and checker/runtime layouts (ABI 4). Renaming the +monitor classes changes exported C++ symbols, so the current native ABI is 5 +(`libxspcomm.so.5`). Rebuild Picker-generated DUT extensions and other native bindings against these headers and library, and update the consumer's ABI requirement together. Keep each generated DUT and its runtime on a matching native ABI. @@ -72,6 +74,16 @@ bundled native runtime. The PR #30 review migration introduces `XEngine` with the existing `XTriggerEngine` entry alias, explicit coverage kinds and `bin.program`. -Use the `0.3.0.dev1` preview for ABI 4 / descriptor protocol 5; `0.3.0.dev0` -identified the earlier ABI 3 / protocol 4 preview. Update frontend lowering and -rebuild native clients together. +The next preview is `0.3.0.dev2` for ABI 5 / descriptor protocol 5, with +`xmonitor.h`, `monitor/` and the `XConditionCheck` family of names. The earlier +`0.3.0.dev1` preview identifies ABI 4 / protocol 5, and `0.3.0.dev0` identifies +ABI 3 / protocol 4. Update source names and frontend lowering, then rebuild +native clients together. See [the migration tables](architecture.md#header-migration). + +The installed SDK consists of `include/xspcomm/` and the generated `xconfig.h`. +Keep pure matching, coverage-state and native-memory implementation headers under +`src/`; do not add them to the installed include tree or the common SWIG interface. +Public templates may use helpers from `include/xspcomm/common/`. Keep +`runtime/types.h` independent of clock and engine implementations. The internal +matcher and coverage tests use the private source include directory explicitly; +ordinary SDK consumers should compile with only the installed include directory. diff --git a/docs/xdata_xclock_optimizations.md b/docs/xdata_xclock_optimizations.md index 041658e..c520bc0 100644 --- a/docs/xdata_xclock_optimizations.md +++ b/docs/xdata_xclock_optimizations.md @@ -7,7 +7,7 @@ No changes for divider algorithm (4) or runtime fast_mode advice (6). --- ## 1) XData::_update_shadow lower cost when no callbacks -Location: src/core/data.cpp : XData::_update_shadow +Location: src/data/data.cpp : XData::_update_shadow Current: Every read/write does shadow compare/copy even when no callbacks. Idea: - When has_on_change_cbs == false, skip per-element compare and callback logic. @@ -25,7 +25,7 @@ Expected gain: --- ## 2) XData::_need_write use memcmp/memcpy -Location: src/core/data.cpp : XData::_need_write / _update_last_write +Location: src/data/data.cpp : XData::_need_write / _update_last_write Current: Element-by-element compare and copy for vec data. Idea: - Use memcmp for change detection on xsvLogicVecVal arrays. @@ -72,7 +72,7 @@ Expected gain: --- ## Correctness Fix: Reset last_write state on ReInit -Location: src/core/data.cpp : XData::ReInit, XData::~XData +Location: src/data/data.cpp : XData::ReInit, XData::~XData Reason: - ReInit does not reset last_is_write/last_pVecData, which can leave stale pointers and sizes after width changes. @@ -89,7 +89,7 @@ Behavior impact: --- ## Correctness Fix: ReInit memory management -Location: src/core/data.cpp : XData::ReInit / ~XData +Location: src/data/data.cpp : XData::ReInit / ~XData Reason: - ReInit allocates pVecData/__pVecData/pinbind_vec with calloc but does not free old allocations on repeated ReInit (leak). @@ -103,7 +103,7 @@ Behavior impact: --- ## Correctness Fix: validate checks all bval words -Location: src/core/data.cpp : XData::_update_shadow +Location: src/data/data.cpp : XData::_update_shadow Reason: - validate only checks pVecData[0].bval, missing X/Z in higher words. Fix: @@ -115,7 +115,7 @@ Behavior impact: ## Follow-up: BindNativeData X/Z semantics -Location: src/core/data_backends.cpp : XData::BindNativeData +Location: src/data/backends.cpp : XData::BindNativeData Current behavior: diff --git a/include/xspcomm/detail/function.h b/include/xspcomm/common/callback_storage.h similarity index 65% rename from include/xspcomm/detail/function.h rename to include/xspcomm/common/callback_storage.h index 1ec4394..331e3c5 100644 --- a/include/xspcomm/detail/function.h +++ b/include/xspcomm/common/callback_storage.h @@ -1,5 +1,5 @@ -#ifndef XSPCOMM_DETAIL_FUNCTION_H -#define XSPCOMM_DETAIL_FUNCTION_H +#ifndef XSPCOMM_COMMON_CALLBACK_STORAGE_H +#define XSPCOMM_COMMON_CALLBACK_STORAGE_H #include diff --git a/include/xspcomm/common/compare.h b/include/xspcomm/common/compare.h index b88458b..634b64f 100644 --- a/include/xspcomm/common/compare.h +++ b/include/xspcomm/common/compare.h @@ -4,8 +4,8 @@ #include #include #include -#include "xspcomm/common/memory/access.h" -#include "xspcomm/common/operation.h" +#include "xspcomm/common/memory_access.h" +#include "xspcomm/common/compare_op.h" namespace xspcomm::compare { diff --git a/include/xspcomm/common/operation.h b/include/xspcomm/common/compare_op.h similarity index 55% rename from include/xspcomm/common/operation.h rename to include/xspcomm/common/compare_op.h index ca68bb1..5642e4f 100644 --- a/include/xspcomm/common/operation.h +++ b/include/xspcomm/common/compare_op.h @@ -1,10 +1,9 @@ -#ifndef XSPCOMM_COMMON_OPERATION_H -#define XSPCOMM_COMMON_OPERATION_H +#ifndef XSPCOMM_COMMON_COMPARE_OP_H +#define XSPCOMM_COMMON_COMPARE_OP_H namespace xspcomm { enum class CompareOp { EQ = 0, NE = 1, GT = 2, GE = 3, LT = 4, LE = 5 }; -typedef CompareOp ComUseCondCmp; } // namespace xspcomm diff --git a/include/xspcomm/detail/lifetime.h b/include/xspcomm/common/lifetime.h similarity index 85% rename from include/xspcomm/detail/lifetime.h rename to include/xspcomm/common/lifetime.h index c95a68d..774270c 100644 --- a/include/xspcomm/detail/lifetime.h +++ b/include/xspcomm/common/lifetime.h @@ -1,5 +1,5 @@ -#ifndef XSPCOMM_DETAIL_LIFETIME_H -#define XSPCOMM_DETAIL_LIFETIME_H +#ifndef XSPCOMM_COMMON_LIFETIME_H +#define XSPCOMM_COMMON_LIFETIME_H #include diff --git a/include/xspcomm/common/memory.h b/include/xspcomm/common/memory.h index a99e51d..dd8fe09 100644 --- a/include/xspcomm/common/memory.h +++ b/include/xspcomm/common/memory.h @@ -2,7 +2,7 @@ #define XSPCOMM_COMMON_MEMORY_H #include "xspcomm/xutil.h" -#include "xspcomm/common/memory/access.h" +#include "xspcomm/common/memory_access.h" #include #include #include @@ -36,28 +36,28 @@ namespace xspcomm { unsigned int *U64AsU32Ptr(uint64_t p); unsigned char *U64AsU8Ptr(uint64_t p); - class ComUseDataArray{ + class XByteBuffer{ bool is_ref = false; int byte_size; int *buffer = nullptr; public: - ComUseDataArray(int byte_size):byte_size(byte_size){ + XByteBuffer(int byte_size):byte_size(byte_size){ Assert(byte_size > 0, "Need size > 0"); int n = byte_size/4 + (byte_size % 4 == 0 ? 0:1); this->buffer = new int[n]; memset(this->buffer, 0, this->byte_size); } - ComUseDataArray(uint64_t base, int byte_size):byte_size(byte_size){ + XByteBuffer(uint64_t base, int byte_size):byte_size(byte_size){ this->buffer = (int*)base; this->is_ref = true; } - ~ComUseDataArray(){if(!this->is_ref)delete[] this->buffer;} - bool operator==(const ComUseDataArray & t) const{ + ~XByteBuffer(){if(!this->is_ref)delete[] this->buffer;} + bool operator==(const XByteBuffer & t) const{ if(this->byte_size != t.byte_size)return false; return memcmp(this->buffer, t.buffer, this->byte_size) == 0; } - ComUseDataArray * Copy(){ - auto ret = new ComUseDataArray(this->byte_size); + XByteBuffer * Copy(){ + auto ret = new XByteBuffer(this->byte_size); ret->SyncFrom(this->BaseAddr(), this->byte_size); return ret; } diff --git a/include/xspcomm/common/memory/access.h b/include/xspcomm/common/memory_access.h similarity index 100% rename from include/xspcomm/common/memory/access.h rename to include/xspcomm/common/memory_access.h diff --git a/include/xspcomm/xcomuse/callback.h b/include/xspcomm/monitor/callback.h similarity index 79% rename from include/xspcomm/xcomuse/callback.h rename to include/xspcomm/monitor/callback.h index b97c501..6e6dbc9 100644 --- a/include/xspcomm/xcomuse/callback.h +++ b/include/xspcomm/monitor/callback.h @@ -1,8 +1,8 @@ -#ifndef XSPCOMM_XCOMUSE_CALLBACK_H -#define XSPCOMM_XCOMUSE_CALLBACK_H +#ifndef XSPCOMM_MONITOR_CALLBACK_H +#define XSPCOMM_MONITOR_CALLBACK_H #include "xspcomm/xutil.h" -#include "xspcomm/detail/lifetime.h" +#include "xspcomm/common/lifetime.h" #include #include #include @@ -12,7 +12,7 @@ namespace xspcomm { class XData; class XClock; - class ComUseStepCb{ + class XStepCallback{ int cb_maxcts = -1; int cb_counts = 0; bool cb_enable = true; @@ -26,10 +26,10 @@ namespace xspcomm { std::vector registrations; public: uint64_t cycle = 0; - ComUseStepCb() = default; - ComUseStepCb(const ComUseStepCb &) = delete; - ComUseStepCb &operator=(const ComUseStepCb &) = delete; - virtual ~ComUseStepCb(); + XStepCallback() = default; + XStepCallback(const XStepCallback &) = delete; + XStepCallback &operator=(const XStepCallback &) = delete; + virtual ~XStepCallback(); void Attach(XClock *clock, bool rising = true); void Detach(); void Disable(); @@ -47,14 +47,14 @@ namespace xspcomm { }; // Echo data when valid != 0 - class ComUseEcho: public ComUseStepCb{ + class XEcho: public XStepCallback{ public: bool stderr_echo; XData* valid = NULL; XData* data = NULL; std::string fmt; int convert; // 0 (char), 1 (int), 2 (float), 3 (double), 4 (string) - ComUseEcho(u_int64_t valid, u_int64_t data, + XEcho(u_int64_t valid, u_int64_t data, bool stderr_echo = true, std::string fmt="%c", int convert = 0): diff --git a/include/xspcomm/xcomuse/condition.h b/include/xspcomm/monitor/condition.h similarity index 92% rename from include/xspcomm/xcomuse/condition.h rename to include/xspcomm/monitor/condition.h index e706a53..ac963e7 100644 --- a/include/xspcomm/xcomuse/condition.h +++ b/include/xspcomm/monitor/condition.h @@ -1,7 +1,7 @@ -#ifndef XSPCOMM_XCOMUSE_CONDITION_H -#define XSPCOMM_XCOMUSE_CONDITION_H +#ifndef XSPCOMM_MONITOR_CONDITION_H +#define XSPCOMM_MONITOR_CONDITION_H -#include "xspcomm/xcomuse/callback.h" +#include "xspcomm/monitor/callback.h" #include "xspcomm/common/compare.h" #include "xspcomm/xdata.h" #include @@ -15,15 +15,15 @@ namespace xspcomm { class ExprEngine; // Condition Checker - class ComUseCondCheck: public ComUseStepCb{ + class XConditionCheck: public XStepCallback{ using XDataCmpFn = bool (*)(XData*, XData*); - using PtrCmpFn = bool (*)(ComUseCondCheck*, uint64_t, uint64_t, int); + using PtrCmpFn = bool (*)(XConditionCheck*, uint64_t, uint64_t, int); template static bool XDataCmp(XData* a, XData* b) { return compare::Compare(*a, *b); } template - static bool PtrCmp(ComUseCondCheck*, uint64_t a, uint64_t b, int bytes) { + static bool PtrCmp(XConditionCheck*, uint64_t a, uint64_t b, int bytes) { const int order = compare::CompareSignedBytes( reinterpret_cast(a), reinterpret_cast(b), bytes); @@ -87,7 +87,7 @@ namespace xspcomm { protected: void SetExpression(std::string name, ExprEngine &engine, int root); public: - ComUseCondCheck(XClock* clk=nullptr){if(clk)this->clk_list.push_back(clk);} + XConditionCheck(XClock* clk=nullptr){if(clk)this->clk_list.push_back(clk);} void BindXClock(XClock *clk); void SetCondition(std::string unique_name, XData* pin, XData* val, CompareOp cmp, XData *valid = nullptr, XData *valid_value = nullptr, xfunction func = nullptr, uint64_t arg=0); void SetCondition(std::string unique_name, uint64_t pin_ptr, uint64_t val_ptr, CompareOp cmp, int bytes, uint64_t valid_ptr = 0, uint64_t valid_value_ptr = 0, int valid_bytes = 1, xfunction func = nullptr, uint64_t arg=0); diff --git a/include/xspcomm/xcomuse/expr.h b/include/xspcomm/monitor/expr.h similarity index 84% rename from include/xspcomm/xcomuse/expr.h rename to include/xspcomm/monitor/expr.h index 88c04f3..668819b 100644 --- a/include/xspcomm/xcomuse/expr.h +++ b/include/xspcomm/monitor/expr.h @@ -1,7 +1,7 @@ -#ifndef XSPCOMM_XCOMUSE_EXPR_H -#define XSPCOMM_XCOMUSE_EXPR_H +#ifndef XSPCOMM_MONITOR_EXPR_H +#define XSPCOMM_MONITOR_EXPR_H -#include "xspcomm/xcomuse/condition.h" +#include "xspcomm/monitor/condition.h" #include "xspcomm/xexpr.h" @@ -9,10 +9,10 @@ namespace xspcomm { class XClock; - class ComUseExprCheck: public ComUseCondCheck{ + class XExprCheck: public XConditionCheck{ ExprEngine engine; public: - ComUseExprCheck(XClock* clk=nullptr) : ComUseCondCheck(clk) {} + XExprCheck(XClock* clk=nullptr) : XConditionCheck(clk) {} int ExprNewConst(uint64_t v); int ExprNewSignal(XData* sig); int ExprNewUnary(int op, int child); diff --git a/include/xspcomm/xcomuse/fsm.h b/include/xspcomm/monitor/fsm.h similarity index 74% rename from include/xspcomm/xcomuse/fsm.h rename to include/xspcomm/monitor/fsm.h index 2c5ab42..ac58f24 100644 --- a/include/xspcomm/xcomuse/fsm.h +++ b/include/xspcomm/monitor/fsm.h @@ -1,14 +1,14 @@ -#ifndef XSPCOMM_XCOMUSE_FSM_H -#define XSPCOMM_XCOMUSE_FSM_H +#ifndef XSPCOMM_MONITOR_FSM_H +#define XSPCOMM_MONITOR_FSM_H -#include "xspcomm/xcomuse/callback.h" +#include "xspcomm/monitor/callback.h" #include "xspcomm/xexpr.h" namespace xspcomm { class XClock; - class ComUseFsmTrigger: public ComUseStepCb{ + class XFsmMonitor: public XStepCallback{ struct FsmTransition{ int cond_root = -1; std::string next_name; @@ -25,7 +25,7 @@ namespace xspcomm { }; Type type = Type::SetFlag; int index = -1; - void (*exec)(ComUseFsmTrigger*, int) = nullptr; + void (*exec)(XFsmMonitor*, int) = nullptr; }; struct FsmState{ std::string name; @@ -49,12 +49,12 @@ namespace xspcomm { int current_state = -1; bool triggered = false; std::string triggered_state; - static void ExecSetFlag(ComUseFsmTrigger* self, int idx); - static void ExecClearFlag(ComUseFsmTrigger* self, int idx); - static void ExecIncCounter(ComUseFsmTrigger* self, int idx); - static void ExecResetCounter(ComUseFsmTrigger* self, int idx); + static void ExecSetFlag(XFsmMonitor* self, int idx); + static void ExecClearFlag(XFsmMonitor* self, int idx); + static void ExecIncCounter(XFsmMonitor* self, int idx); + static void ExecResetCounter(XFsmMonitor* self, int idx); public: - ComUseFsmTrigger(XClock* clk=nullptr){if(clk)this->clk_list.push_back(clk);} + XFsmMonitor(XClock* clk=nullptr){if(clk)this->clk_list.push_back(clk);} void BindXClock(XClock *clk); void LoadProgram(std::string program, XSignalCFG* cfg); void Reset(); diff --git a/include/xspcomm/xcomuse/range.h b/include/xspcomm/monitor/range.h similarity index 87% rename from include/xspcomm/xcomuse/range.h rename to include/xspcomm/monitor/range.h index 7f511ca..13f79fd 100644 --- a/include/xspcomm/xcomuse/range.h +++ b/include/xspcomm/monitor/range.h @@ -1,5 +1,5 @@ -#ifndef XSPCOMM_XCOMUSE_RANGE_H -#define XSPCOMM_XCOMUSE_RANGE_H +#ifndef XSPCOMM_MONITOR_RANGE_H +#define XSPCOMM_MONITOR_RANGE_H #include "xspcomm/xcallback.h" #include "xspcomm/common/compare.h" @@ -10,7 +10,7 @@ namespace xspcomm { class XData; - class ComUseRangeCheck { + class XRangeCheck { int bytes; int range; @@ -23,7 +23,7 @@ namespace xspcomm { return bytes <= 8 ? comparisons[bytes - 1] : ArrayWideCmp; } public: - ComUseRangeCheck(int range, int bytes) : bytes(bytes), range(range) { + XRangeCheck(int range, int bytes) : bytes(bytes), range(range) { Assert(bytes >= 1, "Need bytes >= 1"); } @@ -32,7 +32,7 @@ namespace xspcomm { } static bool ArrayCmp(uint64_t a, uint64_t b, uint64_t self) { - const auto* check = reinterpret_cast(self); + const auto* check = reinterpret_cast(self); if (check->bytes == sizeof(uint64_t)) { uint64_t target, center; std::memcpy(&target, reinterpret_cast(a), sizeof(target)); diff --git a/include/xspcomm/trigger/types.h b/include/xspcomm/runtime/types.h similarity index 89% rename from include/xspcomm/trigger/types.h rename to include/xspcomm/runtime/types.h index f2ca2f2..aef3ddb 100644 --- a/include/xspcomm/trigger/types.h +++ b/include/xspcomm/runtime/types.h @@ -1,13 +1,18 @@ -#ifndef XSPCOMM_TRIGGER_TYPES_H -#define XSPCOMM_TRIGGER_TYPES_H +#ifndef XSPCOMM_RUNTIME_TYPES_H +#define XSPCOMM_RUNTIME_TYPES_H -#include "xspcomm/xclock.h" #include #include #include namespace xspcomm { +enum class XPhase { + FallingStable = 0, + RisingStable = 1, + DriveStable = 2, +}; + enum class XHitKind : uint8_t { ClockFall = 0, ClockRise = 1, diff --git a/include/xspcomm/xcallback.h b/include/xspcomm/xcallback.h index 32a6857..a551876 100644 --- a/include/xspcomm/xcallback.h +++ b/include/xspcomm/xcallback.h @@ -2,7 +2,7 @@ #define __xspcomm_xcallback__ //#include "xspcomm/xutil.h" -#include "xspcomm/detail/function.h" +#include "xspcomm/common/callback_storage.h" #include #include #include diff --git a/include/xspcomm/xclock.h b/include/xspcomm/xclock.h index 708658e..c81aefd 100644 --- a/include/xspcomm/xclock.h +++ b/include/xspcomm/xclock.h @@ -4,7 +4,8 @@ #include "xspcomm/xdata.h" #include "xspcomm/xport.h" #include "xspcomm/xutil.h" -#include "xspcomm/detail/lifetime.h" +#include "xspcomm/common/lifetime.h" +#include "xspcomm/runtime/types.h" #if ENABLE_XCOROUTINE #include "xspcomm/xcoroutine.h" @@ -54,15 +55,9 @@ enum class FastMode { ONLY_STEP_FAL = -2, // Only step on fall edges (half cycle) }; -enum class XPhase { - FallingStable = 0, - RisingStable = 1, - DriveStable = 2, -}; - class XClock { - friend class ComUseStepCb; + friend class XStepCallback; detail::Lifetime callback_lifetime; xfunction step_fc; #if ENABLE_XCOROUTINE diff --git a/include/xspcomm/xcomm.h b/include/xspcomm/xcomm.h index 201e295..be54825 100644 --- a/include/xspcomm/xcomm.h +++ b/include/xspcomm/xcomm.h @@ -7,6 +7,7 @@ #include "xspcomm/xport.h" #include "xspcomm/xdata.h" #include "xspcomm/xclock.h" +#include "xspcomm/xexpr.h" #include "xspcomm/xtrigger.h" #include "xspcomm/xsignal_cfg.h" diff --git a/include/xspcomm/xcomuse.h b/include/xspcomm/xcomuse.h deleted file mode 100644 index 2466478..0000000 --- a/include/xspcomm/xcomuse.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef __xspcomm_xcomuse_h__ -#define __xspcomm_xcomuse_h__ - -#include "xspcomm/common/compare.h" -#include "xspcomm/xcomuse/callback.h" -#include "xspcomm/xcomuse/condition.h" -#include "xspcomm/xcomuse/range.h" -#include "xspcomm/common/memory.h" -#include "xspcomm/xcomuse/expr.h" -#include "xspcomm/xcomuse/fsm.h" - -#endif diff --git a/include/xspcomm/xcoverage.h b/include/xspcomm/xcoverage.h index bb878e0..55fc964 100644 --- a/include/xspcomm/xcoverage.h +++ b/include/xspcomm/xcoverage.h @@ -9,7 +9,6 @@ namespace xspcomm { class XData; -namespace detail { class CoverageState; } enum class XCoverageItemKind : uint8_t { Value = 0, Pattern = 1, Cross = 2 }; enum class XCoverageBinKind : uint8_t { Normal = 0, Ignore = 1, Illegal = 2, Default = 3 }; diff --git a/include/xspcomm/xengine.h b/include/xspcomm/xengine.h index 65ed09f..7c8224a 100644 --- a/include/xspcomm/xengine.h +++ b/include/xspcomm/xengine.h @@ -1,27 +1,24 @@ #ifndef XSPCOMM_XENGINE_H #define XSPCOMM_XENGINE_H -#include "xspcomm/trigger/types.h" -#include "xspcomm/xdata.h" +#include "xspcomm/runtime/types.h" #include "xspcomm/xcoverage.h" -#include "xspcomm/detail/trigger/registration.h" #include -#include +#include #include #include namespace xspcomm { +class XClock; +class XData; class ExprEngine; // Shared sampling runtime. Trigger matches and coverage statistics are clients // of the same clock/phase schedule; the matching kernel has no coverage policy. class XEngine { - using WatcherKind = detail::TriggerRegistration::Kind; - struct Watcher : detail::TriggerRegistration { - std::shared_ptr coverage; - }; + struct Watcher; XClock *clock = nullptr; size_t capacity = 0; @@ -39,17 +36,11 @@ class XEngine { uint64_t value, uint64_t event_id = 0, uint64_t x_mask = 0); bool EvaluateCoverage(Watcher &watcher); - detail::PatternView CoverageSource(const Watcher &watcher) const { - if (watcher.kind == WatcherKind::Sequence || watcher.kind == WatcherKind::Fsm) - return detail::PatternView::From(watcher.program); - return {}; - } void CheckCoverageHandle(XRegistrationHandle handle) const; public: explicit XEngine(XClock *clock, size_t capacity = 1024); - explicit XEngine(XClock &clock, size_t capacity = 1024) - : XEngine(&clock, capacity) {} + explicit XEngine(XClock &clock, size_t capacity = 1024); ~XEngine(); XRegistrationHandle ArmEdge(XPhase phase, uint64_t source_id = 0); diff --git a/include/xspcomm/xmonitor.h b/include/xspcomm/xmonitor.h new file mode 100644 index 0000000..0c1111a --- /dev/null +++ b/include/xspcomm/xmonitor.h @@ -0,0 +1,12 @@ +#ifndef XSPCOMM_XMONITOR_H +#define XSPCOMM_XMONITOR_H + +#include "xspcomm/common/compare.h" +#include "xspcomm/monitor/callback.h" +#include "xspcomm/monitor/condition.h" +#include "xspcomm/monitor/range.h" +#include "xspcomm/common/memory.h" +#include "xspcomm/monitor/expr.h" +#include "xspcomm/monitor/fsm.h" + +#endif diff --git a/include/xspcomm/xtrigger.h b/include/xspcomm/xtrigger.h index 557fe29..d1b3eb4 100644 --- a/include/xspcomm/xtrigger.h +++ b/include/xspcomm/xtrigger.h @@ -1,6 +1,8 @@ #ifndef XSPCOMM_XTRIGGER_H #define XSPCOMM_XTRIGGER_H +#include "xspcomm/xclock.h" +#include "xspcomm/xexpr.h" #include "xspcomm/xengine.h" namespace xspcomm { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5ef2d89..1af6912 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,7 +7,9 @@ if(XSPCOMM_WASM_LIB) endif() add_library(${PROJECT_NAME} SHARED ${XSPCOMM_SOURCES}) -target_include_directories(${PROJECT_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/third_party) +target_include_directories(${PROJECT_NAME} PRIVATE + ${PROJECT_SOURCE_DIR}/src + ${PROJECT_SOURCE_DIR}/third_party) set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${PROJECT_NAME} VERSION ${PROJECT_VERSION} diff --git a/src/common/lifetime.cpp b/src/common/lifetime.cpp index 38c4e94..91bf757 100644 --- a/src/common/lifetime.cpp +++ b/src/common/lifetime.cpp @@ -1,4 +1,4 @@ -#include "xspcomm/detail/lifetime.h" +#include "xspcomm/common/lifetime.h" namespace xspcomm::detail { diff --git a/src/coverage/registration.cpp b/src/coverage/registration.cpp index c8880d7..d3030e7 100644 --- a/src/coverage/registration.cpp +++ b/src/coverage/registration.cpp @@ -1,6 +1,7 @@ -#include "xspcomm/xengine.h" +#include "runtime/registration.h" +#include "xspcomm/xclock.h" #include "xspcomm/xexpr.h" -#include "xspcomm/detail/coverage/state.h" +#include "coverage/state.h" #include #include @@ -24,7 +25,7 @@ void XEngine::AttachCoverage(XRegistrationHandle handle, watchers[handle.slot].coverage) throw std::invalid_argument("invalid coverage registration"); auto &watcher = watchers[handle.slot]; watcher.coverage = std::make_shared( - handle.generation, *expr_engine, CoverageSource(watcher), items, bins, + handle.generation, *expr_engine, watcher.CoverageSource(), items, bins, gate, abort, raise_illegal, diagnostic_capacity, overlap, max_active, diagnostics); } diff --git a/src/coverage/engine.cpp b/src/coverage/state.cpp similarity index 99% rename from src/coverage/engine.cpp rename to src/coverage/state.cpp index d468d4e..bbfc8d6 100644 --- a/src/coverage/engine.cpp +++ b/src/coverage/state.cpp @@ -1,4 +1,4 @@ -#include "xspcomm/detail/coverage/state.h" +#include "coverage/state.h" #include "xspcomm/xexpr.h" #include diff --git a/include/xspcomm/detail/coverage/state.h b/src/coverage/state.h similarity index 98% rename from include/xspcomm/detail/coverage/state.h rename to src/coverage/state.h index 571a3d6..745ba14 100644 --- a/include/xspcomm/detail/coverage/state.h +++ b/src/coverage/state.h @@ -2,7 +2,7 @@ #define XSPCOMM_COVERAGE_STATE_H #include "xspcomm/xcoverage.h" -#include "xspcomm/detail/trigger/matcher.h" +#include "trigger/matcher.h" #include "xspcomm/xexpr.h" #include #include diff --git a/src/core/data_backends.cpp b/src/data/backends.cpp similarity index 99% rename from src/core/data_backends.cpp rename to src/data/backends.cpp index 37a7e9c..b818863 100644 --- a/src/core/data_backends.cpp +++ b/src/data/backends.cpp @@ -1,7 +1,7 @@ #include "xspcomm/xdata.h" #include "xspcomm/xexpr.h" -#include "xspcomm/detail/data/native_memory.h" +#include "data/native_memory.h" namespace xspcomm { diff --git a/src/core/data.cpp b/src/data/data.cpp similarity index 100% rename from src/core/data.cpp rename to src/data/data.cpp diff --git a/include/xspcomm/detail/data/native_memory.h b/src/data/native_memory.h similarity index 95% rename from include/xspcomm/detail/data/native_memory.h rename to src/data/native_memory.h index 9516a02..a1bf837 100644 --- a/include/xspcomm/detail/data/native_memory.h +++ b/src/data/native_memory.h @@ -1,8 +1,8 @@ -#ifndef XSPCOMM_DETAIL_DATA_NATIVE_MEMORY_H -#define XSPCOMM_DETAIL_DATA_NATIVE_MEMORY_H +#ifndef XSPCOMM_DATA_NATIVE_MEMORY_H +#define XSPCOMM_DATA_NATIVE_MEMORY_H #include "xspcomm/xdata.h" -#include "xspcomm/common/memory/access.h" +#include "xspcomm/common/memory_access.h" #include #include diff --git a/src/core/pin.cpp b/src/data/pin.cpp similarity index 100% rename from src/core/pin.cpp rename to src/data/pin.cpp diff --git a/src/core/data_value.cpp b/src/data/value.cpp similarity index 100% rename from src/core/data_value.cpp rename to src/data/value.cpp diff --git a/src/core/data_vpi.cpp b/src/data/vpi.cpp similarity index 100% rename from src/core/data_vpi.cpp rename to src/data/vpi.cpp diff --git a/src/comuse/callback.cpp b/src/monitor/callback.cpp similarity index 77% rename from src/comuse/callback.cpp rename to src/monitor/callback.cpp index b784710..50e2c76 100644 --- a/src/comuse/callback.cpp +++ b/src/monitor/callback.cpp @@ -1,17 +1,17 @@ -#include "xspcomm/xcomuse/callback.h" +#include "xspcomm/monitor/callback.h" #include "xspcomm/xclock.h" #include namespace xspcomm { -ComUseStepCb::~ComUseStepCb() { Detach(); } +XStepCallback::~XStepCallback() { Detach(); } -void ComUseStepCb::Attach(XClock *clock, bool rising) { +void XStepCallback::Attach(XClock *clock, bool rising) { if (!clock) throw std::invalid_argument("callback clock must not be null"); if (clock->in_callback) throw std::logic_error("cannot attach while clock callbacks are running"); for (const auto ®istration : registrations) if (registration.clock == clock && registration.rising == rising && !registration.lifetime.expired()) return; - const std::string description = "ComUseStepCb:" + std::to_string(CSelf()) + (rising ? ":rise" : ":fall"); + const std::string description = "XStepCallback:" + std::to_string(CSelf()) + (rising ? ":rise" : ":fall"); Registration registration{clock, clock->callback_lifetime.Observe(), rising, description}; registrations.push_back(registration); try { @@ -27,7 +27,7 @@ void ComUseStepCb::Attach(XClock *clock, bool rising) { } } -void ComUseStepCb::Detach() { +void XStepCallback::Detach() { for (const auto ®istration : registrations) { if (registration.lifetime.expired()) continue; if (registration.rising) registration.clock->RemoveStepRisCbByDesc(registration.description); @@ -36,51 +36,51 @@ void ComUseStepCb::Detach() { registrations.clear(); } -u_int64_t ComUseStepCb::GetCb(){ - return (u_int64_t)ComUseStepCb::Cb; +u_int64_t XStepCallback::GetCb(){ + return (u_int64_t)XStepCallback::Cb; } -void ComUseStepCb::Cb(uint64_t c, void *self){ - ComUseStepCb *p = (ComUseStepCb*)self; +void XStepCallback::Cb(uint64_t c, void *self){ + XStepCallback *p = (XStepCallback*)self; if (unlikely(!p->cb_enable)) return; p->cycle = c; p->Call(); if (unlikely(p->cb_maxcts > 0 && p->cb_counts >= p->cb_maxcts)) p->Disable(); } -void ComUseStepCb::Disable(){ +void XStepCallback::Disable(){ this->cb_enable = false; } -void ComUseStepCb::Enable(){ +void XStepCallback::Enable(){ this->cb_enable = true; } -bool ComUseStepCb::IsDisable(){ +bool XStepCallback::IsDisable(){ return !this->cb_enable; } -int ComUseStepCb::GetCbCount(){ +int XStepCallback::GetCbCount(){ return this->cb_counts; } -int ComUseStepCb::IncCbCount(){ +int XStepCallback::IncCbCount(){ this->cb_counts += 1; return this->cb_counts; } -int ComUseStepCb::DecCbCount(){ +int XStepCallback::DecCbCount(){ this->cb_counts -= 1; return this->cb_counts; } -void ComUseStepCb::SetMaxCbs(int c){ +void XStepCallback::SetMaxCbs(int c){ this->cb_maxcts = c; } -void ComUseStepCb::Reset(){ +void XStepCallback::Reset(){ this->cb_enable = true; this->cb_counts = 0; } -void ComUseStepCb::Call(){ +void XStepCallback::Call(){ fprintf(stderr, "Error, This is a virtual Call!\n"); } -void ComUseEcho::Call(){ +void XEcho::Call(){ //convert; // 0 (char), 1 (int), 2 (float), 3 (double), 4 (string) Assert(this->valid != NULL, "Pin[valid] is Null"); Assert(this->data != NULL, "Pin[data] is Null"); diff --git a/src/comuse/condition.cpp b/src/monitor/condition.cpp similarity index 74% rename from src/comuse/condition.cpp rename to src/monitor/condition.cpp index 7bc8c65..9a08367 100644 --- a/src/comuse/condition.cpp +++ b/src/monitor/condition.cpp @@ -1,13 +1,13 @@ -#include "xspcomm/xcomuse/condition.h" +#include "xspcomm/monitor/condition.h" #include "xspcomm/xclock.h" #include "xspcomm/xexpr.h" namespace xspcomm { -void ComUseCondCheck::BindXClock(XClock *clk){ +void XConditionCheck::BindXClock(XClock *clk){ this->clk_list.push_back(clk); } -void ComUseCondCheck::SetCondition(std::string unique_name, XData* pin, XData* val, ComUseCondCmp cmp, XData *valid, XData *valid_value, xfunction func, uint64_t arg){ +void XConditionCheck::SetCondition(std::string unique_name, XData* pin, XData* val, CompareOp cmp, XData *valid, XData *valid_value, xfunction func, uint64_t arg){ Assert(func == nullptr, "func type error: %d", func.func == nullptr); RemoveExpression(unique_name); if (!pin || !val || !SelectXDataCmpFn(cmp) || (valid && !valid_value)) @@ -24,7 +24,7 @@ void ComUseCondCheck::SetCondition(std::string unique_name, XData* pin, XData* v entry.valid_value = valid_value; entry.func = func; entry.arg = arg; - entry.valid_cmp = ComUseCondCmp::EQ; + entry.valid_cmp = CompareOp::EQ; entry.cmp_fn = SelectXDataCmpFn(cmp); entry.valid_cmp_fn = SelectXDataCmpFn(entry.valid_cmp); this->cond_vec_xdata.push_back(std::move(entry)); @@ -42,7 +42,7 @@ void ComUseCondCheck::SetCondition(std::string unique_name, XData* pin, XData* v entry.valid_cmp_fn = SelectXDataCmpFn(entry.valid_cmp); } }; -void ComUseCondCheck::SetCondition(std::string unique_name, uint64_t pin_ptr, uint64_t val_ptr, ComUseCondCmp cmp, int bytes, uint64_t valid_ptr, uint64_t valid_value_ptr, int valid_bytes, xfunction func, uint64_t arg){ +void XConditionCheck::SetCondition(std::string unique_name, uint64_t pin_ptr, uint64_t val_ptr, CompareOp cmp, int bytes, uint64_t valid_ptr, uint64_t valid_value_ptr, int valid_bytes, xfunction func, uint64_t arg){ RemoveExpression(unique_name); if (!SelectPtrCmpFn(cmp) || (!func && (!pin_ptr || !val_ptr || bytes <= 0)) || (valid_ptr && (!valid_value_ptr || valid_bytes <= 0))) @@ -61,7 +61,7 @@ void ComUseCondCheck::SetCondition(std::string unique_name, uint64_t pin_ptr, ui entry.valid_bytes = valid_bytes; entry.func = func; entry.arg = arg; - entry.valid_cmp = ComUseCondCmp::EQ; + entry.valid_cmp = CompareOp::EQ; entry.cmp_fn = SelectPtrCmpFn(cmp); entry.valid_cmp_fn = SelectPtrCmpFn(entry.valid_cmp); this->cond_vec_uint64.push_back(std::move(entry)); @@ -81,12 +81,12 @@ void ComUseCondCheck::SetCondition(std::string unique_name, uint64_t pin_ptr, ui entry.valid_cmp_fn = SelectPtrCmpFn(entry.valid_cmp); } }; -void ComUseCondCheck::RemoveCondition(std::string unique_name){ +void XConditionCheck::RemoveCondition(std::string unique_name){ RemoveExpression(unique_name); RemoveXDataCond(this->cond_idx_xdata, this->cond_vec_xdata, unique_name); RemoveUint64Cond(this->cond_idx_uint64, this->cond_vec_uint64, unique_name); }; -std::vector ComUseCondCheck::GetTriggeredConditionKeys(){ +std::vector XConditionCheck::GetTriggeredConditionKeys(){ std::vector ret; for(auto &e : this->cond_vec_xdata){ if(e.triggered)ret.push_back(e.name); @@ -97,7 +97,7 @@ std::vector ComUseCondCheck::GetTriggeredConditionKeys(){ for (const auto &entry : expressions) if (entry.triggered) ret.push_back(entry.name); return ret; } -std::map ComUseCondCheck::ListCondition(){ +std::map XConditionCheck::ListCondition(){ std::map ret; for(auto &e : this->cond_vec_xdata){ ret[e.name] = e.triggered ? true : false; @@ -108,7 +108,7 @@ std::map ComUseCondCheck::ListCondition(){ for (const auto &entry : expressions) ret[entry.name] = entry.triggered; return ret; } -ComUseCondCmp ComUseCondCheck::GetValidCmpMode(std::string unique_name){ +CompareOp XConditionCheck::GetValidCmpMode(std::string unique_name){ auto it = this->cond_idx_xdata.find(unique_name); if(it != this->cond_idx_xdata.end()){ return this->cond_vec_xdata[it->second].valid_cmp; @@ -118,9 +118,9 @@ ComUseCondCmp ComUseCondCheck::GetValidCmpMode(std::string unique_name){ return this->cond_vec_uint64[it2->second].valid_cmp; } Error("Condition not found: %s", unique_name.c_str()); - return ComUseCondCmp::EQ; // default + return CompareOp::EQ; // default } -void ComUseCondCheck::SetValidCmpMode(std::string unique_name, ComUseCondCmp cmp){ +void XConditionCheck::SetValidCmpMode(std::string unique_name, CompareOp cmp){ if (!SelectXDataCmpFn(cmp)) throw std::invalid_argument("invalid comparison operation"); auto it = this->cond_idx_xdata.find(unique_name); if(it != this->cond_idx_xdata.end()){ @@ -138,8 +138,8 @@ void ComUseCondCheck::SetValidCmpMode(std::string unique_name, ComUseCondCmp cmp } Error("Condition not found: %s", unique_name.c_str()); } -void ComUseCondCheck::ClearClock(){this->clk_list.clear();} -void ComUseCondCheck::ClearCondition(){ +void XConditionCheck::ClearClock(){this->clk_list.clear();} +void XConditionCheck::ClearCondition(){ expressions.clear(); expression_index.clear(); this->cond_idx_xdata.clear(); @@ -147,11 +147,11 @@ void ComUseCondCheck::ClearCondition(){ this->cond_vec_xdata.clear(); this->cond_vec_uint64.clear(); }; -xfunction ComUseCondCheck::AsXDataXFunc(uint64_t func){ +xfunction XConditionCheck::AsXDataXFunc(uint64_t func){ xfunction ret = (bool (*)(XData*, XData*, uint64_t))func; return ret; } -xfunction ComUseCondCheck::AsPtrXFunc(uint64_t func){ +xfunction XConditionCheck::AsPtrXFunc(uint64_t func){ xfunction ret = (bool (*)(uint64_t, uint64_t, uint64_t))func; return ret; } @@ -179,24 +179,24 @@ void CheckEntries(Entries &entries, Evaluate evaluate, OnHit on_hit) { } } -inline bool ComUseCondCheck::Evaluate(CondXDataEntry &entry) { +inline bool XConditionCheck::Evaluate(CondXDataEntry &entry) { if (entry.valid && !entry.valid_cmp_fn(entry.valid, entry.valid_value)) return false; return entry.func ? entry.func(entry.pin, entry.val, entry.arg) : entry.cmp_fn(entry.pin, entry.val); } -inline bool ComUseCondCheck::Evaluate(CondUint64Entry &entry) { +inline bool XConditionCheck::Evaluate(CondUint64Entry &entry) { if (entry.valid_ptr && !entry.valid_cmp_fn(this, entry.valid_ptr, entry.valid_value_ptr, entry.valid_bytes)) return false; return entry.func ? entry.func(entry.pin_ptr, entry.val_ptr, entry.arg) : entry.cmp_fn(this, entry.pin_ptr, entry.val_ptr, entry.bytes); } -inline bool ComUseCondCheck::Evaluate(ExprEntry &entry) { +inline bool XConditionCheck::Evaluate(ExprEntry &entry) { return entry.root >= 0 && entry.engine->Eval(entry.root) != 0; } -void ComUseCondCheck::Call() { +void XConditionCheck::Call() { // Keep comparisons specialized at registration; no per-sample allocation. auto evaluate = [this](auto &entry) { return Evaluate(entry); }; bool triggered = false; @@ -211,7 +211,7 @@ void ComUseCondCheck::Call() { CheckEntries(expressions, evaluate, on_hit); } -void ComUseCondCheck::SetExpression(std::string name, ExprEngine &engine, int root) { +void XConditionCheck::SetExpression(std::string name, ExprEngine &engine, int root) { RemoveXDataCond(cond_idx_xdata, cond_vec_xdata, name); RemoveUint64Cond(cond_idx_uint64, cond_vec_uint64, name); const auto found = expression_index.find(name); @@ -225,40 +225,40 @@ void ComUseCondCheck::SetExpression(std::string name, ExprEngine &engine, int ro } } -void ComUseCondCheck::RemoveExpression(const std::string &name) { +void XConditionCheck::RemoveExpression(const std::string &name) { RemoveEntry(expression_index, expressions, name); } -ComUseCondCheck::XDataCmpFn ComUseCondCheck::SelectXDataCmpFn(ComUseCondCmp cmp){ +XConditionCheck::XDataCmpFn XConditionCheck::SelectXDataCmpFn(CompareOp cmp){ switch (cmp) { - case ComUseCondCmp::EQ: return &ComUseCondCheck::XDataCmp; - case ComUseCondCmp::NE: return &ComUseCondCheck::XDataCmp; - case ComUseCondCmp::GT: return &ComUseCondCheck::XDataCmp; - case ComUseCondCmp::GE: return &ComUseCondCheck::XDataCmp; - case ComUseCondCmp::LT: return &ComUseCondCheck::XDataCmp; - case ComUseCondCmp::LE: return &ComUseCondCheck::XDataCmp; + case CompareOp::EQ: return &XConditionCheck::XDataCmp; + case CompareOp::NE: return &XConditionCheck::XDataCmp; + case CompareOp::GT: return &XConditionCheck::XDataCmp; + case CompareOp::GE: return &XConditionCheck::XDataCmp; + case CompareOp::LT: return &XConditionCheck::XDataCmp; + case CompareOp::LE: return &XConditionCheck::XDataCmp; default: return nullptr; } } -ComUseCondCheck::PtrCmpFn ComUseCondCheck::SelectPtrCmpFn(ComUseCondCmp cmp){ +XConditionCheck::PtrCmpFn XConditionCheck::SelectPtrCmpFn(CompareOp cmp){ switch (cmp) { - case ComUseCondCmp::EQ: return &ComUseCondCheck::PtrCmp; - case ComUseCondCmp::NE: return &ComUseCondCheck::PtrCmp; - case ComUseCondCmp::GT: return &ComUseCondCheck::PtrCmp; - case ComUseCondCmp::GE: return &ComUseCondCheck::PtrCmp; - case ComUseCondCmp::LT: return &ComUseCondCheck::PtrCmp; - case ComUseCondCmp::LE: return &ComUseCondCheck::PtrCmp; + case CompareOp::EQ: return &XConditionCheck::PtrCmp; + case CompareOp::NE: return &XConditionCheck::PtrCmp; + case CompareOp::GT: return &XConditionCheck::PtrCmp; + case CompareOp::GE: return &XConditionCheck::PtrCmp; + case CompareOp::LT: return &XConditionCheck::PtrCmp; + case CompareOp::LE: return &XConditionCheck::PtrCmp; default: return nullptr; } } -bool ComUseCondCheck::RemoveXDataCond(std::unordered_map &index, +bool XConditionCheck::RemoveXDataCond(std::unordered_map &index, std::vector &entries, const std::string &name) { return RemoveEntry(index, entries, name); } -bool ComUseCondCheck::RemoveUint64Cond(std::unordered_map &index, +bool XConditionCheck::RemoveUint64Cond(std::unordered_map &index, std::vector &entries, const std::string &name) { return RemoveEntry(index, entries, name); } diff --git a/src/comuse/expr.cpp b/src/monitor/expr.cpp similarity index 51% rename from src/comuse/expr.cpp rename to src/monitor/expr.cpp index 17010da..a3a07f7 100644 --- a/src/comuse/expr.cpp +++ b/src/monitor/expr.cpp @@ -1,51 +1,51 @@ -#include "xspcomm/xcomuse/expr.h" +#include "xspcomm/monitor/expr.h" #include "xspcomm/xclock.h" namespace xspcomm { -int ComUseExprCheck::ExprNewConst(uint64_t v){ +int XExprCheck::ExprNewConst(uint64_t v){ return this->engine.NewConst(v); } -int ComUseExprCheck::ExprNewSignal(XData* sig){ +int XExprCheck::ExprNewSignal(XData* sig){ return this->engine.NewSignal(sig); } -int ComUseExprCheck::ExprNewUnary(int op, int child){ +int XExprCheck::ExprNewUnary(int op, int child){ return this->engine.NewUnary(static_cast(op), child); } -int ComUseExprCheck::ExprNewBinary(int op, int lhs, int rhs){ +int XExprCheck::ExprNewBinary(int op, int lhs, int rhs){ return this->engine.NewBinary(static_cast(op), lhs, rhs); } -int ComUseExprCheck::ExprNewCompare(int op, int lhs, int rhs){ +int XExprCheck::ExprNewCompare(int op, int lhs, int rhs){ return this->engine.NewCompare(static_cast(op), lhs, rhs); } -int ComUseExprCheck::ExprNewCompareSigSig(int op, XData* lhs, XData* rhs){ +int XExprCheck::ExprNewCompareSigSig(int op, XData* lhs, XData* rhs){ return this->engine.NewCompareSigSig(static_cast(op), lhs, rhs); } -int ComUseExprCheck::ExprNewCompareSigConst(int op, XData* lhs, uint64_t rhs){ +int XExprCheck::ExprNewCompareSigConst(int op, XData* lhs, uint64_t rhs){ return this->engine.NewCompareSigConst(static_cast(op), lhs, rhs); } -int ComUseExprCheck::ExprNewCompareConstSig(int op, uint64_t lhs, XData* rhs){ +int XExprCheck::ExprNewCompareConstSig(int op, uint64_t lhs, XData* rhs){ return this->engine.NewCompareConstSig(static_cast(op), lhs, rhs); } -int ComUseExprCheck::ExprNewCompareSigConstBytes( +int XExprCheck::ExprNewCompareSigConstBytes( int op, XData* lhs, std::vector &rhs){ return this->engine.NewCompareSigConstBytes(static_cast(op), lhs, rhs); } -int ComUseExprCheck::ExprNewCompareConstBytesSig( +int XExprCheck::ExprNewCompareConstBytesSig( int op, std::vector &lhs, XData* rhs){ return this->engine.NewCompareConstBytesSig(static_cast(op), lhs, rhs); } -int ComUseExprCheck::CompileExpr(std::string expr, XSignalCFG* cfg){ +int XExprCheck::CompileExpr(std::string expr, XSignalCFG* cfg){ try{ return this->engine.CompileExpr(expr, cfg); }catch(const std::exception &e){ @@ -54,22 +54,22 @@ int ComUseExprCheck::CompileExpr(std::string expr, XSignalCFG* cfg){ } } -void ComUseExprCheck::SetExpr(std::string name, int root) { +void XExprCheck::SetExpr(std::string name, int root) { SetExpression(std::move(name), engine, root); } -void ComUseExprCheck::RemoveExpr(std::string name) { RemoveCondition(std::move(name)); } -std::map ComUseExprCheck::ListExpr() { return ListCondition(); } -std::vector ComUseExprCheck::GetTriggeredExprKeys() { return GetTriggeredConditionKeys(); } +void XExprCheck::RemoveExpr(std::string name) { RemoveCondition(std::move(name)); } +std::map XExprCheck::ListExpr() { return ListCondition(); } +std::vector XExprCheck::GetTriggeredExprKeys() { return GetTriggeredConditionKeys(); } -void ComUseExprCheck::ClearExpr() { +void XExprCheck::ClearExpr() { ClearCondition(); engine.Clear(); } -void ComUseExprCheck::Call() { +void XExprCheck::Call() { engine.SetCycle(cycle); - ComUseCondCheck::Call(); + XConditionCheck::Call(); } } // namespace xspcomm diff --git a/src/comuse/fsm.cpp b/src/monitor/fsm.cpp similarity index 95% rename from src/comuse/fsm.cpp rename to src/monitor/fsm.cpp index 2c2efd2..1d822d5 100644 --- a/src/comuse/fsm.cpp +++ b/src/monitor/fsm.cpp @@ -1,4 +1,4 @@ -#include "xspcomm/xcomuse/fsm.h" +#include "xspcomm/monitor/fsm.h" #include "xspcomm/xclock.h" #include "xspcomm/xsignal_cfg.h" @@ -112,39 +112,39 @@ void CollectSpecialNames(const std::string &expr, } } } // namespace -void ComUseFsmTrigger::ExecSetFlag(ComUseFsmTrigger* self, int idx){ +void XFsmMonitor::ExecSetFlag(XFsmMonitor* self, int idx){ if(idx >= 0 && idx < (int)self->flags.size()){ self->flags[idx].value = 1; (*self->flags[idx].xdata) = (uint64_t)1; } } -void ComUseFsmTrigger::ExecClearFlag(ComUseFsmTrigger* self, int idx){ +void XFsmMonitor::ExecClearFlag(XFsmMonitor* self, int idx){ if(idx >= 0 && idx < (int)self->flags.size()){ self->flags[idx].value = 0; (*self->flags[idx].xdata) = (uint64_t)0; } } -void ComUseFsmTrigger::ExecIncCounter(ComUseFsmTrigger* self, int idx){ +void XFsmMonitor::ExecIncCounter(XFsmMonitor* self, int idx){ if(idx >= 0 && idx < (int)self->counters.size()){ self->counters[idx].value += 1; (*self->counters[idx].xdata) = self->counters[idx].value; } } -void ComUseFsmTrigger::ExecResetCounter(ComUseFsmTrigger* self, int idx){ +void XFsmMonitor::ExecResetCounter(XFsmMonitor* self, int idx){ if(idx >= 0 && idx < (int)self->counters.size()){ self->counters[idx].value = 0; (*self->counters[idx].xdata) = (uint64_t)0; } } -void ComUseFsmTrigger::BindXClock(XClock *clk){ +void XFsmMonitor::BindXClock(XClock *clk){ this->clk_list.push_back(clk); } -void ComUseFsmTrigger::LoadProgram(std::string program, XSignalCFG* cfg){ +void XFsmMonitor::LoadProgram(std::string program, XSignalCFG* cfg){ this->Clear(); try{ if(program.empty()){ @@ -220,7 +220,7 @@ void ComUseFsmTrigger::LoadProgram(std::string program, XSignalCFG* cfg){ FsmAction act; act.type = FsmAction::Type::SetFlag; act.index = idx; - act.exec = &ComUseFsmTrigger::ExecSetFlag; + act.exec = &XFsmMonitor::ExecSetFlag; cur_state->actions.push_back(act); continue; } @@ -245,7 +245,7 @@ void ComUseFsmTrigger::LoadProgram(std::string program, XSignalCFG* cfg){ FsmAction act; act.type = FsmAction::Type::ClearFlag; act.index = idx; - act.exec = &ComUseFsmTrigger::ExecClearFlag; + act.exec = &XFsmMonitor::ExecClearFlag; cur_state->actions.push_back(act); continue; } @@ -270,7 +270,7 @@ void ComUseFsmTrigger::LoadProgram(std::string program, XSignalCFG* cfg){ FsmAction act; act.type = FsmAction::Type::IncCounter; act.index = idx; - act.exec = &ComUseFsmTrigger::ExecIncCounter; + act.exec = &XFsmMonitor::ExecIncCounter; cur_state->actions.push_back(act); continue; } @@ -295,7 +295,7 @@ void ComUseFsmTrigger::LoadProgram(std::string program, XSignalCFG* cfg){ FsmAction act; act.type = FsmAction::Type::ResetCounter; act.index = idx; - act.exec = &ComUseFsmTrigger::ExecResetCounter; + act.exec = &XFsmMonitor::ExecResetCounter; cur_state->actions.push_back(act); continue; } @@ -439,7 +439,7 @@ void ComUseFsmTrigger::LoadProgram(std::string program, XSignalCFG* cfg){ } } -void ComUseFsmTrigger::Reset(){ +void XFsmMonitor::Reset(){ this->triggered = false; this->triggered_state.clear(); this->current_state = this->start_state; @@ -458,7 +458,7 @@ void ComUseFsmTrigger::Reset(){ this->engine.ResetState(); } -void ComUseFsmTrigger::Clear(){ +void XFsmMonitor::Clear(){ this->states.clear(); this->state_map.clear(); this->flag_map.clear(); @@ -472,14 +472,14 @@ void ComUseFsmTrigger::Clear(){ this->engine.Clear(); } -std::string ComUseFsmTrigger::GetCurrentState(){ +std::string XFsmMonitor::GetCurrentState(){ if(this->current_state < 0 || this->current_state >= (int)this->states.size()){ return ""; } return this->states[this->current_state].name; } -std::vector ComUseFsmTrigger::ListStates(){ +std::vector XFsmMonitor::ListStates(){ std::vector ret; for(const auto &st : this->states){ ret.push_back(st.name); @@ -487,7 +487,7 @@ std::vector ComUseFsmTrigger::ListStates(){ return ret; } -void ComUseFsmTrigger::Call(){ +void XFsmMonitor::Call(){ if(this->triggered){ return; } diff --git a/src/comuse/range.cpp b/src/monitor/range.cpp similarity index 76% rename from src/comuse/range.cpp rename to src/monitor/range.cpp index 419e3ad..4368655 100644 --- a/src/comuse/range.cpp +++ b/src/monitor/range.cpp @@ -1,4 +1,4 @@ -#include "xspcomm/xcomuse/range.h" +#include "xspcomm/monitor/range.h" #include "xspcomm/xdata.h" #include @@ -6,32 +6,32 @@ namespace xspcomm { template -bool ComUseRangeCheck::Compare(uint64_t a, uint64_t b, uint64_t self) { - const auto* check = reinterpret_cast(self); +bool XRangeCheck::Compare(uint64_t a, uint64_t b, uint64_t self) { + const auto* check = reinterpret_cast(self); const auto target = memory::UIntBytes::Load(reinterpret_cast(a)); const auto center = memory::UIntBytes::Load(reinterpret_cast(b)); return cmp(target, center, check->range); } -const ComUseRangeCheck::ArrayCompare ComUseRangeCheck::comparisons[] = { +const XRangeCheck::ArrayCompare XRangeCheck::comparisons[] = { Compare<1>, Compare<2>, Compare<3>, Compare<4>, Compare<5>, Compare<6>, Compare<7>, Compare<8>, }; -bool ComUseRangeCheck::ArrayWideCmp(uint64_t a, uint64_t b, uint64_t self) { - const auto* check = reinterpret_cast(self); +bool XRangeCheck::ArrayWideCmp(uint64_t a, uint64_t b, uint64_t self) { + const auto* check = reinterpret_cast(self); const size_t bytes = check->bytes; const memory::ByteWords target{reinterpret_cast(a), bytes}; const memory::ByteWords center{reinterpret_cast(b), bytes}; return compare::WithinRange(target.Count(), check->range, target, center); } -bool ComUseRangeCheck::XDataCmp(XData* a, XData* b, uint64_t self) { +bool XRangeCheck::XDataCmp(XData* a, XData* b, uint64_t self) { // Refresh each signal once; subsequent word reads use its existing cache. const uint64_t av = a->U(); const uint64_t bv = b->U(); if (!a->DataValid() || !b->DataValid()) return false; - const int range = reinterpret_cast(self)->range; + const int range = reinterpret_cast(self)->range; if (a->W() <= 64 && b->W() <= 64) return cmp(av, bv, range); const size_t awords = (static_cast(a->W()) + 31) / 32; const size_t bwords = (static_cast(b->W()) + 31) / 32; diff --git a/src/runtime/engine.cpp b/src/runtime/engine.cpp index 1b4328d..c4053e7 100644 --- a/src/runtime/engine.cpp +++ b/src/runtime/engine.cpp @@ -1,7 +1,8 @@ -#include "xspcomm/xengine.h" +#include "runtime/registration.h" +#include "xspcomm/xclock.h" #include "xspcomm/xexpr.h" -#include "xspcomm/detail/coverage/state.h" -#include "xspcomm/detail/trigger/matcher.h" +#include "coverage/state.h" +#include "trigger/matcher.h" #include #include @@ -9,6 +10,8 @@ namespace xspcomm { +using WatcherKind = detail::TriggerRegistration::Kind; + XEngine::XEngine(XClock *clock, size_t capacity) : clock(clock), capacity(capacity), expr_engine(std::make_unique()) { @@ -23,6 +26,8 @@ XEngine::XEngine(XClock *clock, size_t capacity) hit_buffer.reserve(capacity); } +XEngine::XEngine(XClock &clock, size_t capacity) : XEngine(&clock, capacity) {} + XEngine::~XEngine() = default; XRegistrationHandle XEngine::Allocate() @@ -390,7 +395,7 @@ inline bool XEngine::EvaluateCoverage(Watcher &watcher) case detail::CoverageState::SampleStatus::Ready: break; } - const auto source = CoverageSource(watcher); + const auto source = watcher.CoverageSource(); if (source.sequence || source.fsm) { coverage.SamplePattern(*expr_engine, clock->GetHalfTick()); return true; diff --git a/src/runtime/registration.h b/src/runtime/registration.h new file mode 100644 index 0000000..b6ad326 --- /dev/null +++ b/src/runtime/registration.h @@ -0,0 +1,24 @@ +#ifndef XSPCOMM_RUNTIME_REGISTRATION_H +#define XSPCOMM_RUNTIME_REGISTRATION_H + +#include "xspcomm/xengine.h" +#include "trigger/registration.h" + +namespace xspcomm { + +namespace detail { class CoverageState; } + +// Keep registrations in the engine's existing contiguous storage. +struct XEngine::Watcher : detail::TriggerRegistration { + std::shared_ptr coverage; + + detail::PatternView CoverageSource() const { + if (kind == Kind::Sequence || kind == Kind::Fsm) + return detail::PatternView::From(program); + return {}; + } +}; + +} // namespace xspcomm + +#endif diff --git a/src/trigger/pattern.cpp b/src/trigger/executor.cpp similarity index 99% rename from src/trigger/pattern.cpp rename to src/trigger/executor.cpp index 00b6aaf..f8ca20c 100644 --- a/src/trigger/pattern.cpp +++ b/src/trigger/executor.cpp @@ -1,4 +1,4 @@ -#include "xspcomm/detail/trigger/program.h" +#include "trigger/executor.h" #include "xspcomm/xexpr.h" #include diff --git a/include/xspcomm/detail/trigger/program.h b/src/trigger/executor.h similarity index 96% rename from include/xspcomm/detail/trigger/program.h rename to src/trigger/executor.h index fd7e725..4255066 100644 --- a/include/xspcomm/detail/trigger/program.h +++ b/src/trigger/executor.h @@ -1,5 +1,5 @@ -#ifndef XSPCOMM_DETAIL_TRIGGER_PROGRAM_H -#define XSPCOMM_DETAIL_TRIGGER_PROGRAM_H +#ifndef XSPCOMM_TRIGGER_EXECUTOR_H +#define XSPCOMM_TRIGGER_EXECUTOR_H #include "xspcomm/xpattern.h" #include diff --git a/src/trigger/matcher.cpp b/src/trigger/matcher.cpp index 3b288c8..2be5c4e 100644 --- a/src/trigger/matcher.cpp +++ b/src/trigger/matcher.cpp @@ -1,4 +1,4 @@ -#include "xspcomm/detail/trigger/matcher.h" +#include "trigger/matcher.h" #include "xspcomm/xexpr.h" #include diff --git a/include/xspcomm/detail/trigger/matcher.h b/src/trigger/matcher.h similarity index 93% rename from include/xspcomm/detail/trigger/matcher.h rename to src/trigger/matcher.h index d2151f5..9b37499 100644 --- a/include/xspcomm/detail/trigger/matcher.h +++ b/src/trigger/matcher.h @@ -1,7 +1,7 @@ -#ifndef XSPCOMM_DETAIL_TRIGGER_MATCHER_H -#define XSPCOMM_DETAIL_TRIGGER_MATCHER_H +#ifndef XSPCOMM_TRIGGER_MATCHER_H +#define XSPCOMM_TRIGGER_MATCHER_H -#include "xspcomm/detail/trigger/program.h" +#include "trigger/executor.h" #include namespace xspcomm::detail { diff --git a/include/xspcomm/detail/trigger/registration.h b/src/trigger/registration.h similarity index 95% rename from include/xspcomm/detail/trigger/registration.h rename to src/trigger/registration.h index faddeff..51e23fe 100644 --- a/include/xspcomm/detail/trigger/registration.h +++ b/src/trigger/registration.h @@ -1,8 +1,8 @@ -#ifndef XSPCOMM_DETAIL_TRIGGER_REGISTRATION_H -#define XSPCOMM_DETAIL_TRIGGER_REGISTRATION_H +#ifndef XSPCOMM_TRIGGER_REGISTRATION_H +#define XSPCOMM_TRIGGER_REGISTRATION_H -#include "xspcomm/trigger/types.h" -#include "xspcomm/detail/trigger/matcher.h" +#include "xspcomm/runtime/types.h" +#include "trigger/matcher.h" #include "xspcomm/xexpr.h" #include diff --git a/swig/javascript/CMakeLists.txt b/swig/javascript/CMakeLists.txt index 6a641d4..c355bce 100644 --- a/swig/javascript/CMakeLists.txt +++ b/swig/javascript/CMakeLists.txt @@ -7,7 +7,9 @@ add_compile_options(-fexceptions -sMODULARIZE=1 -sEXPORT_NAME=xspcomm) include(${PROJECT_SOURCE_DIR}/cmake/Sources.cmake) swig_add_library(jsxspcomm LANGUAGE javascript SOURCES javascript.i ${XSPCOMM_SOURCES}) -target_include_directories(jsxspcomm PRIVATE ${PROJECT_SOURCE_DIR}/third_party) +target_include_directories(jsxspcomm PRIVATE + ${PROJECT_SOURCE_DIR}/src + ${PROJECT_SOURCE_DIR}/third_party) # set buld output directory set(XCOMM_LIB_DIR ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/../javascript/xspcomm) diff --git a/swig/xcomm.i b/swig/xcomm.i index 49734e2..2b86208 100644 --- a/swig/xcomm.i +++ b/swig/xcomm.i @@ -5,7 +5,7 @@ #include "xspcomm/xport.h" #include "xspcomm/xcallback.h" #include "xspcomm/xsignal_cfg.h" -#include "xspcomm/xcomuse.h" +#include "xspcomm/xmonitor.h" #include "xspcomm/xtrigger.h" #include %} @@ -68,23 +68,22 @@ namespace std { SWIG_exception(SWIG_RuntimeError, error.what()); } } +%include "xspcomm/runtime/types.h" %include "xspcomm/xclock.h" %include "xspcomm/xdata.h" %include "xspcomm/xport.h" %include "xspcomm/xutil.h" %include "xspcomm/xsignal_cfg.h" -%include "xspcomm/xcomuse/callback.h" -%rename(ComUseCondCmp) xspcomm::CompareOp; -%include "xspcomm/common/operation.h" -%include "xspcomm/xcomuse/condition.h" -%include "xspcomm/xcomuse/range.h" +%include "xspcomm/monitor/callback.h" +%include "xspcomm/common/compare_op.h" +%include "xspcomm/monitor/condition.h" +%include "xspcomm/monitor/range.h" %include "xspcomm/common/memory.h" %include "xspcomm/xexpr.h" -%include "xspcomm/xcomuse/expr.h" -%include "xspcomm/xcomuse/fsm.h" -%include "xspcomm/xcomuse.h" +%include "xspcomm/monitor/expr.h" +%include "xspcomm/monitor/fsm.h" +%include "xspcomm/xmonitor.h" %include "xspcomm/xpattern.h" -%include "xspcomm/trigger/types.h" %include "xspcomm/xcoverage.h" %rename(XTriggerEngine) xspcomm::XEngine; %include "xspcomm/xengine.h" @@ -141,7 +140,7 @@ namespace std { %d_callback(cb_int_bool, int, bool); %d_callback(cb_void_u64_voidp, void, u_int64_t, void *); // StepRis, StepFal -// ComUseCondCheck +// XConditionCheck %d_callback(cb_bool_XData_XData_uint64, bool, xspcomm::XData*, xspcomm::XData*, uint64_t); %d_callback(cb_bool_uint64_uint64_uint64, bool, uint64_t, uint64_t, uint64_t); @@ -159,6 +158,6 @@ namespace std { %x_callback(cb_int_bool, int, bool); %x_callback(cb_void_u64_voidp, void, u_int64_t, void *); // StepRis, StepFal -// ComUseCondCheck +// XConditionCheck %x_callback(cb_bool_XData_XData_uint64, bool, xspcomm::XData*, xspcomm::XData*, uint64_t); %x_callback(cb_bool_uint64_uint64_uint64, bool, uint64_t, uint64_t, uint64_t); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6593f01..aa3a6ed 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,5 +1,5 @@ if(NOT XSPCOMM_WASM_LIB) - foreach(component IN ITEMS xdata xclock xtrigger xcoverage xcomuse xexpr xvpi xfsm trigger_matcher) + foreach(component IN ITEMS xdata xclock xtrigger xcoverage xmonitor xexpr xvpi xfsm trigger_matcher) add_executable(test_${component} unit/test_${component}.cpp) target_include_directories(test_${component} PRIVATE ${PROJECT_SOURCE_DIR}/third_party/catch2) @@ -12,4 +12,7 @@ if(NOT XSPCOMM_WASM_LIB) target_link_libraries(test_coverage_core PRIVATE ${PROJECT_NAME}) add_test(NAME test_coverage_core COMMAND test_coverage_core) target_sources(test_xdata PRIVATE unit/xdata_cases.cpp) + foreach(component IN ITEMS trigger_matcher coverage_core) + target_include_directories(test_${component} PRIVATE ${PROJECT_SOURCE_DIR}/src) + endforeach() endif() diff --git a/tests/bindings/python/test_python.py b/tests/bindings/python/test_python.py index 1dd9528..aad9d79 100644 --- a/tests/bindings/python/test_python.py +++ b/tests/bindings/python/test_python.py @@ -116,7 +116,7 @@ def test_xdata(): clk.StepRis(lambda c, x, y: print("lambda ris: ", c, x, y), (1, 2)) clk.StepRis(lambda c, x, y: print("lambda fal: ", c, x, y), (3, 4)) - echo = ComUseEcho(a.CSelf(), b.CSelf()) + echo = XEcho(a.CSelf(), b.CSelf()) print("echo:", a.value, b.value) a.value = 1 b.value = b'A' diff --git a/tests/unit/test_coverage_core.cpp b/tests/unit/test_coverage_core.cpp index 36ae5c2..5b564c9 100644 --- a/tests/unit/test_coverage_core.cpp +++ b/tests/unit/test_coverage_core.cpp @@ -1,7 +1,7 @@ #define CATCH_CONFIG_MAIN #include "catch.hpp" -#include "xspcomm/detail/coverage/state.h" +#include "coverage/state.h" #include "xspcomm/xexpr.h" using namespace xspcomm; diff --git a/tests/unit/test_trigger_matcher.cpp b/tests/unit/test_trigger_matcher.cpp index ae2ddda..f9ab35a 100644 --- a/tests/unit/test_trigger_matcher.cpp +++ b/tests/unit/test_trigger_matcher.cpp @@ -1,7 +1,7 @@ #define CATCH_CONFIG_MAIN #include "catch.hpp" -#include "xspcomm/detail/trigger/matcher.h" +#include "trigger/matcher.h" #include "xspcomm/xexpr.h" using namespace xspcomm; diff --git a/tests/unit/test_xexpr.cpp b/tests/unit/test_xexpr.cpp index f21b3af..646bad7 100644 --- a/tests/unit/test_xexpr.cpp +++ b/tests/unit/test_xexpr.cpp @@ -2,7 +2,7 @@ #include "catch.hpp" #include "xspcomm/xexpr.h" -#include "xspcomm/xcomuse/expr.h" +#include "xspcomm/monitor/expr.h" #include "xspcomm/xdata.h" #include "xspcomm/xsignal_cfg.h" @@ -137,9 +137,9 @@ TEST_CASE("ExprEngine parser errors", "[xexpr]") { REQUIRE_THROWS(eng.CompileExpr("a @ b", nullptr)); } -TEST_CASE("ComUseExprCheck reports every expression hit in one call", +TEST_CASE("XExprCheck reports every expression hit in one call", "[xexpr][trigger]") { - ComUseExprCheck checker; + XExprCheck checker; checker.SetExpr("first", checker.ExprNewConst(1)); checker.SetExpr("second", checker.ExprNewConst(1)); diff --git a/tests/unit/test_xfsm.cpp b/tests/unit/test_xfsm.cpp index 5dc9a6f..19686d3 100644 --- a/tests/unit/test_xfsm.cpp +++ b/tests/unit/test_xfsm.cpp @@ -1,7 +1,7 @@ #define CATCH_CONFIG_MAIN #include "catch.hpp" -#include "xspcomm/xcomuse/fsm.h" +#include "xspcomm/monitor/fsm.h" #include "xspcomm/xclock.h" using namespace xspcomm; @@ -17,11 +17,11 @@ TEST_CASE("FSM basic transitions and trigger", "[xfsm]") { " if $counter0 >= 2 trigger\n"; XClock clk([](bool){ return 0; }); - ComUseFsmTrigger fsm(&clk); + XFsmMonitor fsm(&clk); fsm.LoadProgram(prog, nullptr); REQUIRE(fsm.GetCurrentState() == "S0"); - auto fn = (void (*)(uint64_t, void*))ComUseStepCb::GetCb(); + auto fn = (void (*)(uint64_t, void*))XStepCallback::GetCb(); fn(1, &fsm); REQUIRE(fsm.GetCurrentState() == "S1"); REQUIRE(fsm.IsTriggered() == false); @@ -45,17 +45,17 @@ TEST_CASE("FSM else/goto", "[xfsm]") { "state C:\n" " trigger\n"; - ComUseFsmTrigger fsm; + XFsmMonitor fsm; fsm.LoadProgram(prog, nullptr); REQUIRE(fsm.GetCurrentState() == "A"); - auto fn = (void (*)(uint64_t, void*))ComUseStepCb::GetCb(); + auto fn = (void (*)(uint64_t, void*))XStepCallback::GetCb(); fn(1, &fsm); REQUIRE(fsm.GetCurrentState() == "C"); } TEST_CASE("FSM parse errors", "[xfsm]") { - ComUseFsmTrigger fsm; + XFsmMonitor fsm; REQUIRE_NOTHROW(fsm.LoadProgram("", nullptr)); REQUIRE(fsm.ListStates().empty()); diff --git a/tests/unit/test_xcomuse.cpp b/tests/unit/test_xmonitor.cpp similarity index 77% rename from tests/unit/test_xcomuse.cpp rename to tests/unit/test_xmonitor.cpp index 64d5129..5f63d8d 100644 --- a/tests/unit/test_xcomuse.cpp +++ b/tests/unit/test_xmonitor.cpp @@ -1,7 +1,7 @@ #define CATCH_CONFIG_MAIN #include "catch.hpp" -#include "xspcomm/xcomuse.h" +#include "xspcomm/xmonitor.h" #include "xspcomm/xclock.h" #include "xspcomm/xdata.h" #include @@ -13,7 +13,7 @@ using namespace xspcomm; namespace { -struct StepCbProbe : public ComUseStepCb { +struct StepCbProbe : public XStepCallback { int calls = 0; void Call() override { calls++; @@ -22,7 +22,7 @@ struct StepCbProbe : public ComUseStepCb { }; } -TEST_CASE("ComUseStepCb basic behavior", "[xcomuse_base]") { +TEST_CASE("XStepCallback basic behavior", "[xmonitor_base]") { StepCbProbe cb; REQUIRE(cb.IsDisable() == false); REQUIRE(cb.GetCbCount() == 0); @@ -33,7 +33,7 @@ TEST_CASE("ComUseStepCb basic behavior", "[xcomuse_base]") { REQUIRE(cb.IsDisable() == false); cb.SetMaxCbs(2); - auto fn = (void (*)(uint64_t, void*))ComUseStepCb::GetCb(); + auto fn = (void (*)(uint64_t, void*))XStepCallback::GetCb(); fn(1, &cb); REQUIRE(cb.calls == 1); REQUIRE(cb.GetCbCount() == 1); @@ -49,16 +49,16 @@ TEST_CASE("ComUseStepCb basic behavior", "[xcomuse_base]") { REQUIRE(cb.GetCbCount() == 2); } -TEST_CASE("ComUseCondCheck xdata conditions", "[xcomuse_base]") { +TEST_CASE("XConditionCheck xdata conditions", "[xmonitor_base]") { XData a(8, XData::InOut); XData b(8, XData::InOut); XClock clk([](bool){ return 0; }); a = 1; b = 1; - ComUseCondCheck checker(&clk); - checker.SetCondition("eq", &a, &b, ComUseCondCmp::EQ); - auto fn = (void (*)(uint64_t, void*))ComUseStepCb::GetCb(); + XConditionCheck checker(&clk); + checker.SetCondition("eq", &a, &b, CompareOp::EQ); + auto fn = (void (*)(uint64_t, void*))XStepCallback::GetCb(); fn(1, &checker); auto keys = checker.GetTriggeredConditionKeys(); @@ -68,7 +68,7 @@ TEST_CASE("ComUseCondCheck xdata conditions", "[xcomuse_base]") { REQUIRE(clk.IsDisable() == true); } -TEST_CASE("ComUseCondCheck valid gating", "[xcomuse_base]") { +TEST_CASE("XConditionCheck valid gating", "[xmonitor_base]") { XData a(8, XData::InOut); XData b(8, XData::InOut); XData valid(1, XData::InOut); @@ -79,9 +79,9 @@ TEST_CASE("ComUseCondCheck valid gating", "[xcomuse_base]") { valid = 0; valid_val = 1; - ComUseCondCheck checker; - checker.SetCondition("eq", &a, &b, ComUseCondCmp::EQ, &valid, &valid_val); - auto fn = (void (*)(uint64_t, void*))ComUseStepCb::GetCb(); + XConditionCheck checker; + checker.SetCondition("eq", &a, &b, CompareOp::EQ, &valid, &valid_val); + auto fn = (void (*)(uint64_t, void*))XStepCallback::GetCb(); fn(1, &checker); REQUIRE(checker.GetTriggeredConditionKeys().empty()); @@ -91,26 +91,26 @@ TEST_CASE("ComUseCondCheck valid gating", "[xcomuse_base]") { REQUIRE(checker.GetTriggeredConditionKeys().size() == 1); } -TEST_CASE("ComUseCondCheck pointer conditions", "[xcomuse_base]") { +TEST_CASE("XConditionCheck pointer conditions", "[xmonitor_base]") { uint32_t lhs = 5; uint32_t rhs = 7; - ComUseCondCheck checker; - checker.SetCondition("gt", (uint64_t)&rhs, (uint64_t)&lhs, ComUseCondCmp::GT, sizeof(uint32_t)); + XConditionCheck checker; + checker.SetCondition("gt", (uint64_t)&rhs, (uint64_t)&lhs, CompareOp::GT, sizeof(uint32_t)); - auto fn = (void (*)(uint64_t, void*))ComUseStepCb::GetCb(); + auto fn = (void (*)(uint64_t, void*))XStepCallback::GetCb(); fn(1, &checker); REQUIRE(checker.GetTriggeredConditionKeys().size() == 1); int8_t neg = -1; int8_t pos = 1; - ComUseCondCheck checker2; - checker2.SetCondition("lt", (uint64_t)&neg, (uint64_t)&pos, ComUseCondCmp::LT, sizeof(int8_t)); + XConditionCheck checker2; + checker2.SetCondition("lt", (uint64_t)&neg, (uint64_t)&pos, CompareOp::LT, sizeof(int8_t)); fn(2, &checker2); REQUIRE(checker2.GetTriggeredConditionKeys().size() == 1); } -TEST_CASE("ComUseDataArray and helpers", "[xcomuse_base]") { - ComUseDataArray arr(10); +TEST_CASE("XByteBuffer and helpers", "[xmonitor_base]") { + XByteBuffer arr(10); REQUIRE(arr.Size() == 10); arr.SetZero(); auto bytes = arr.AsBytes(); @@ -122,7 +122,7 @@ TEST_CASE("ComUseDataArray and helpers", "[xcomuse_base]") { REQUIRE(out[0] == 1); REQUIRE(out[4] == 5); - auto copy = std::unique_ptr(arr.Copy()); + auto copy = std::unique_ptr(arr.Copy()); REQUIRE((*copy) == arr); unsigned char buf[4] = {9, 8, 7, 6}; @@ -135,18 +135,18 @@ TEST_CASE("ComUseDataArray and helpers", "[xcomuse_base]") { REQUIRE(GetFromU32Array((uint64_t)u32s, 1) == 0x1234); } -TEST_CASE("ComUseRangeCheck", "[xcomuse_base]") { - REQUIRE(ComUseRangeCheck::cmp(10, 12, 2) == true); - REQUIRE(ComUseRangeCheck::cmp(10, 12, -2) == false); +TEST_CASE("XRangeCheck", "[xmonitor_base]") { + REQUIRE(XRangeCheck::cmp(10, 12, 2) == true); + REQUIRE(XRangeCheck::cmp(10, 12, -2) == false); uint64_t a = 10; uint64_t b = 12; - ComUseRangeCheck rc(2, 8); + XRangeCheck rc(2, 8); auto fn = rc.GetArrayCmp(); REQUIRE(fn((uint64_t)&a, (uint64_t)&b, rc.CSelf()) == true); } -TEST_CASE("ComUseRangeCheck reads exactly its byte count", "[xcomuse_base]") { +TEST_CASE("XRangeCheck reads exactly its byte count", "[xmonitor_base]") { for (int bytes = 1; bytes <= 8; ++bytes) { // Exact allocations expose overreads; the offset also tests alignment. auto a = std::make_unique(bytes + 1); @@ -157,29 +157,29 @@ TEST_CASE("ComUseRangeCheck reads exactly its byte count", "[xcomuse_base]") { b[1] = 6; auto lhs = reinterpret_cast(a.get() + 1); auto rhs = reinterpret_cast(b.get() + 1); - ComUseRangeCheck rc(2, bytes); + XRangeCheck rc(2, bytes); auto compare = rc.GetArrayCmp(); REQUIRE(compare(lhs, rhs, rc.CSelf())); - REQUIRE(ComUseRangeCheck::ArrayCmp(lhs, rhs, rc.CSelf())); + REQUIRE(XRangeCheck::ArrayCmp(lhs, rhs, rc.CSelf())); b[1] = 7; REQUIRE_FALSE(compare(lhs, rhs, rc.CSelf())); - REQUIRE_FALSE(ComUseRangeCheck::ArrayCmp(lhs, rhs, rc.CSelf())); + REQUIRE_FALSE(XRangeCheck::ArrayCmp(lhs, rhs, rc.CSelf())); b[1] = 4; REQUIRE(compare(lhs, rhs, rc.CSelf())); } } -TEST_CASE("ComUseRangeCheck does not wrap at integer boundaries", "[xcomuse_base]") { - REQUIRE(ComUseRangeCheck::cmp(0, 0, 2)); - REQUIRE(ComUseRangeCheck::cmp(0, 1, 2)); - REQUIRE_FALSE(ComUseRangeCheck::cmp(UINT64_MAX, 0, 2)); - REQUIRE(ComUseRangeCheck::cmp(UINT64_MAX, UINT64_MAX, -2)); - REQUIRE_FALSE(ComUseRangeCheck::cmp(0, UINT64_MAX, -2)); - REQUIRE(ComUseRangeCheck::cmp(uint64_t(1) << 31, 0, std::numeric_limits::min())); - REQUIRE_FALSE(ComUseRangeCheck::cmp((uint64_t(1) << 31) + 1, 0, std::numeric_limits::min())); +TEST_CASE("XRangeCheck does not wrap at integer boundaries", "[xmonitor_base]") { + REQUIRE(XRangeCheck::cmp(0, 0, 2)); + REQUIRE(XRangeCheck::cmp(0, 1, 2)); + REQUIRE_FALSE(XRangeCheck::cmp(UINT64_MAX, 0, 2)); + REQUIRE(XRangeCheck::cmp(UINT64_MAX, UINT64_MAX, -2)); + REQUIRE_FALSE(XRangeCheck::cmp(0, UINT64_MAX, -2)); + REQUIRE(XRangeCheck::cmp(uint64_t(1) << 31, 0, std::numeric_limits::min())); + REQUIRE_FALSE(XRangeCheck::cmp((uint64_t(1) << 31) + 1, 0, std::numeric_limits::min())); } -TEST_CASE("ComUseRangeCheck compares full wide buffers and signals", "[xcomuse_base]") { +TEST_CASE("XRangeCheck compares full wide buffers and signals", "[xmonitor_base]") { struct Case { const char *target; const char *center; int range; bool expected; }; const Case cases[] = { {"0x0", "0x10000000000000000", 0, false}, @@ -203,13 +203,13 @@ TEST_CASE("ComUseRangeCheck compares full wide buffers and signals", "[xcomuse_b const auto av = a.GetBytes(), bv = b.GetBytes(); std::copy_n(av.begin(), bytes, lhs.get() + 1); std::copy_n(bv.begin(), bytes, rhs.get() + 1); - ComUseRangeCheck check(test.range, bytes); + XRangeCheck check(test.range, bytes); const auto x = reinterpret_cast(lhs.get() + 1); const auto y = reinterpret_cast(rhs.get() + 1); auto array_compare = check.GetArrayCmp(); auto signal_compare = check.GetXDataCmp(); REQUIRE(array_compare(x, y, check.CSelf()) == test.expected); - REQUIRE(ComUseRangeCheck::ArrayCmp(x, y, check.CSelf()) == test.expected); + REQUIRE(XRangeCheck::ArrayCmp(x, y, check.CSelf()) == test.expected); REQUIRE(signal_compare(&a, &b, check.CSelf()) == test.expected); } // Borrow must propagate through every word, including a partial last word. @@ -217,7 +217,7 @@ TEST_CASE("ComUseRangeCheck compares full wide buffers and signals", "[xcomuse_b std::memset(rhs.get() + 1, 0, bytes); lhs[bytes] = 0; rhs[bytes] = 1; - ComUseRangeCheck check(1, bytes); + XRangeCheck check(1, bytes); auto compare = check.GetArrayCmp(); const auto x = reinterpret_cast(lhs.get() + 1); const auto y = reinterpret_cast(rhs.get() + 1); @@ -227,14 +227,14 @@ TEST_CASE("ComUseRangeCheck compares full wide buffers and signals", "[xcomuse_b std::memset(lhs.get() + 1, 0xFF, bytes); std::memset(rhs.get() + 1, 0, bytes); REQUIRE_FALSE(compare(x, y, check.CSelf())); - ComUseRangeCheck above(-1, bytes); - REQUIRE_FALSE(ComUseRangeCheck::ArrayCmp(y, x, above.CSelf())); + XRangeCheck above(-1, bytes); + REQUIRE_FALSE(XRangeCheck::ArrayCmp(y, x, above.CSelf())); } } -TEST_CASE("ComUseRangeCheck uses signal width and rejects unknowns", "[xcomuse_base]") { +TEST_CASE("XRangeCheck uses signal width and rejects unknowns", "[xmonitor_base]") { // The byte count configures pointer comparisons; XData uses its actual width. - ComUseRangeCheck check(1, 8); + XRangeCheck check(1, 8); auto compare = check.GetXDataCmp(); XData narrow(8, XData::InOut); narrow.Set(1); @@ -262,7 +262,7 @@ TEST_CASE("ComUseRangeCheck uses signal width and rejects unknowns", "[xcomuse_b REQUIRE_FALSE(compare(&narrow, &narrow, check.CSelf())); } -TEST_CASE("ComUseRangeCheck refreshes each wide backend once without writing", "[xcomuse_base]") { +TEST_CASE("XRangeCheck refreshes each wide backend once without writing", "[xmonitor_base]") { XData a(129, XData::InOut), b(129, XData::InOut); uint32_t av[] = {UINT32_MAX, UINT32_MAX, 0, 0, 0}; uint32_t bv[] = {0, 0, 1, 0, 0}; @@ -277,7 +277,7 @@ TEST_CASE("ComUseRangeCheck refreshes each wide backend once without writing", " bind(a, av, reads_a); bind(b, bv, reads_b); reads_a = reads_b = 0; - ComUseRangeCheck check(1, 17); + XRangeCheck check(1, 17); auto compare = check.GetXDataCmp(); REQUIRE(compare(&a, &b, check.CSelf())); REQUIRE(reads_a == 1); @@ -290,7 +290,7 @@ TEST_CASE("ComUseRangeCheck refreshes each wide backend once without writing", " REQUIRE(writes == 0); } -TEST_CASE("CString basic", "[xcomuse_base]") { +TEST_CASE("CString basic", "[xmonitor_base]") { CString s("abc"); REQUIRE(s.Get() == "abc"); s.Set("def"); @@ -300,17 +300,17 @@ TEST_CASE("CString basic", "[xcomuse_base]") { REQUIRE(s.CharAddress() != 0); } -TEST_CASE("ComUseEcho smoke", "[xcomuse_base]") { +TEST_CASE("XEcho smoke", "[xmonitor_base]") { XData valid(1, XData::InOut); XData data(8, XData::InOut); valid = 1; data = 'A'; - ComUseEcho echo(valid.CSelf(), data.CSelf(), false, "%c", 0); + XEcho echo(valid.CSelf(), data.CSelf(), false, "%c", 0); echo.Call(); } -TEST_CASE("Pointer conditions order signed values at every buffer width", "[xcomuse_base][compare]") { +TEST_CASE("Pointer conditions order signed values at every buffer width", "[xmonitor_base][compare]") { for (int bytes : {1, 2, 3, 4, 5, 7, 8, 9, 16, 33, 65}) { auto lhs = std::make_unique(bytes + 1); auto rhs = std::make_unique(bytes + 1); @@ -319,15 +319,15 @@ TEST_CASE("Pointer conditions order signed values at every buffer width", "[xcom const uint64_t bits = static_cast(static_cast(value)); std::memcpy(data, &bits, std::min(static_cast(bytes), sizeof(bits))); }; - ComUseCondCheck checker; + XConditionCheck checker; for (int a : {-128, -2, -1, 0, 1, 127}) { for (int b : {-128, -2, -1, 0, 1, 127}) { store(lhs.get() + 1, a); store(rhs.get() + 1, b); - const std::pair cases[] = { - {ComUseCondCmp::EQ, a == b}, {ComUseCondCmp::NE, a != b}, - {ComUseCondCmp::GT, a > b}, {ComUseCondCmp::GE, a >= b}, - {ComUseCondCmp::LT, a < b}, {ComUseCondCmp::LE, a <= b}, + const std::pair cases[] = { + {CompareOp::EQ, a == b}, {CompareOp::NE, a != b}, + {CompareOp::GT, a > b}, {CompareOp::GE, a >= b}, + {CompareOp::LT, a < b}, {CompareOp::LE, a <= b}, }; for (auto [operation, expected] : cases) { INFO("bytes=" << bytes << ", a=" << a << ", b=" << b << ", op=" << static_cast(operation)); @@ -341,7 +341,7 @@ TEST_CASE("Pointer conditions order signed values at every buffer width", "[xcom } } -TEST_CASE("XData equality preserves zero extension and four-state masks", "[xcomuse_base][compare]") { +TEST_CASE("XData equality preserves zero extension and four-state masks", "[xmonitor_base][compare]") { for (unsigned width : {64U, 65U, 127U, 129U, 513U}) { XData wide(width, XData::InOut), peer(width, XData::InOut), narrow(8, XData::InOut); wide.Set(0x5a); peer.Set(0x5a); narrow.Set(0x5a); @@ -358,9 +358,9 @@ TEST_CASE("XData equality preserves zero extension and four-state masks", "[xcom REQUIRE((wide == peer)); REQUIRE_FALSE((wide == narrow)); REQUIRE_FALSE((wide == uint64_t(0x5a))); - ComUseCondCheck checker; - checker.SetCondition("eq", &wide, &peer, ComUseCondCmp::EQ); - checker.SetCondition("ne", &wide, &peer, ComUseCondCmp::NE); + XConditionCheck checker; + checker.SetCondition("eq", &wide, &peer, CompareOp::EQ); + checker.SetCondition("ne", &wide, &peer, CompareOp::NE); checker.Call(); REQUIRE(checker.ListCondition().at("eq")); REQUIRE_FALSE(checker.ListCondition().at("ne")); @@ -371,7 +371,7 @@ TEST_CASE("XData equality preserves zero extension and four-state masks", "[xcom } } -TEST_CASE("Managed ComUse callbacks detach with either lifetime order", "[xcomuse_base][lifetime]") { +TEST_CASE("Managed Monitor callbacks detach with either lifetime order", "[xmonitor_base][lifetime]") { XClock clock([](bool) { return 0; }); { auto callback = std::make_unique(); @@ -394,7 +394,7 @@ TEST_CASE("Managed ComUse callbacks detach with either lifetime order", "[xcomus callback.Detach(); } -TEST_CASE("Managed callback removal during sampling preserves iteration", "[xcomuse_base][lifetime]") { +TEST_CASE("Managed callback removal during sampling preserves iteration", "[xmonitor_base][lifetime]") { XClock clock([](bool) { return 0; }); auto callback = std::make_unique(); clock.StepRis([&](uint64_t, void *) { callback.reset(); }); @@ -405,7 +405,7 @@ TEST_CASE("Managed callback removal during sampling preserves iteration", "[xcom clock.Step(); } -TEST_CASE("Copied clocks cannot retain destroyed managed callbacks", "[xcomuse_base][lifetime]") { +TEST_CASE("Copied clocks cannot retain destroyed managed callbacks", "[xmonitor_base][lifetime]") { auto original = std::make_unique([](bool) { return 0; }); auto callback = std::make_unique(); callback->Attach(original.get()); @@ -417,12 +417,12 @@ TEST_CASE("Copied clocks cannot retain destroyed managed callbacks", "[xcomuse_b copy.Step(); } -TEST_CASE("Expression and comparison checkers share registration and hit handling", "[xcomuse_base][xexpr]") { +TEST_CASE("Expression and comparison checkers share registration and hit handling", "[xmonitor_base][xexpr]") { XClock clock([](bool) { return 0; }); XData left(129, XData::InOut), right(129, XData::InOut); left = right = "0x100000000000000000000000000000001"; - ComUseExprCheck checker(&clock); - checker.SetCondition("wide", &left, &right, ComUseCondCmp::EQ); + XExprCheck checker(&clock); + checker.SetCondition("wide", &left, &right, CompareOp::EQ); checker.SetExpr("expression", checker.ExprNewCompareSigSig(static_cast(ExprOp::EQ), &left, &right)); checker.Call(); REQUIRE(checker.GetTriggeredExprKeys().size() == 2); @@ -436,7 +436,7 @@ TEST_CASE("Expression and comparison checkers share registration and hit handlin int8_t negative = -1, positive = 1; checker.SetCondition("expression", reinterpret_cast(&negative), - reinterpret_cast(&positive), ComUseCondCmp::LT, 1); + reinterpret_cast(&positive), CompareOp::LT, 1); checker.Call(); REQUIRE(checker.ListExpr().at("expression")); checker.RemoveExpr("expression"); @@ -445,8 +445,8 @@ TEST_CASE("Expression and comparison checkers share registration and hit handlin REQUIRE(checker.ListCondition().empty()); } -TEST_CASE("Expression hits clear when evaluated again in the same cycle", "[xcomuse_base][xexpr]") { - ComUseExprCheck checker; +TEST_CASE("Expression hits clear when evaluated again in the same cycle", "[xmonitor_base][xexpr]") { + XExprCheck checker; checker.SetExpr("condition", checker.ExprNewConst(1)); checker.Call(); REQUIRE(checker.ListExpr().at("condition")); @@ -456,15 +456,15 @@ TEST_CASE("Expression hits clear when evaluated again in the same cycle", "[xcom REQUIRE(checker.GetTriggeredExprKeys().empty()); } -TEST_CASE("Unified checks retain four-state equality and first-hit clock behavior", "[xcomuse_base][xexpr]") { +TEST_CASE("Unified checks retain four-state equality and first-hit clock behavior", "[xmonitor_base][xexpr]") { XClock clock([](bool) { return 0; }); XData left(129, XData::InOut), right(129, XData::InOut); left = right = "x"; - ComUseExprCheck checker(&clock); - checker.SetCondition("compare", &left, &right, ComUseCondCmp::EQ); + XExprCheck checker(&clock); + checker.SetCondition("compare", &left, &right, CompareOp::EQ); checker.SetExpr("expression", checker.ExprNewCompareSigSig(static_cast(ExprOp::EQ), &left, &right)); bool observed_disabled = false; - checker.SetCondition("callback", uint64_t(0), uint64_t(0), ComUseCondCmp::EQ, 0, 0, 0, 1, + checker.SetCondition("callback", uint64_t(0), uint64_t(0), CompareOp::EQ, 0, 0, 0, 1, [&](uint64_t, uint64_t, uint64_t) { observed_disabled = clock.IsDisable(); return true; }); checker.Call(); REQUIRE(checker.GetTriggeredExprKeys().size() == 3); @@ -472,16 +472,16 @@ TEST_CASE("Unified checks retain four-state equality and first-hit clock behavio REQUIRE(checker.GetCbCount() == 1); } -TEST_CASE("Invalid raw comparison registration is rejected before sampling", "[xcomuse_base]") { - ComUseCondCheck checker; +TEST_CASE("Invalid raw comparison registration is rejected before sampling", "[xmonitor_base]") { + XConditionCheck checker; uint64_t left = 1, right = 2; REQUIRE_THROWS_AS(checker.SetCondition("invalid", reinterpret_cast(&left), - reinterpret_cast(&right), ComUseCondCmp::EQ, 0), std::invalid_argument); + reinterpret_cast(&right), CompareOp::EQ, 0), std::invalid_argument); REQUIRE_THROWS_AS(checker.SetCondition("invalid", reinterpret_cast(&left), - reinterpret_cast(&right), static_cast(99), 8), std::invalid_argument); + reinterpret_cast(&right), static_cast(99), 8), std::invalid_argument); } -TEST_CASE("Raw array helpers preserve unaligned accesses and signed offsets", "[xcomuse_base][memory]") { +TEST_CASE("Raw array helpers preserve unaligned accesses and signed offsets", "[xmonitor_base][memory]") { unsigned char storage[18]; std::memset(storage, 0xA5, sizeof(storage)); const auto middle = reinterpret_cast(storage + 9); diff --git a/tests/unit/xdata_cases.cpp b/tests/unit/xdata_cases.cpp index 63df2eb..4fa5ada 100644 --- a/tests/unit/xdata_cases.cpp +++ b/tests/unit/xdata_cases.cpp @@ -1,5 +1,5 @@ #include "xspcomm/xcomm.h" -#include "xspcomm/xcomuse.h" +#include "xspcomm/xmonitor.h" namespace xspcomm { @@ -349,16 +349,16 @@ int test_xdata() clk1.Step(10); test_assert(clk1.clk == clk_old+10, "clk1 enable fail: %lld", clk1.clk); - // ComUseCondCheck - ComUseCondCheck check(&clk1); - clk1.StepRis(check.GetCb(), check.CSelf(),"ComUseCondCheck"); - check.SetCondition("key_EQ", &x64, &x64, ComUseCondCmp::EQ); - check.SetCondition("key_GT", &x64, &d1, ComUseCondCmp::GT); + // XConditionCheck + XConditionCheck check(&clk1); + clk1.StepRis(check.GetCb(), check.CSelf(),"XConditionCheck"); + check.SetCondition("key_EQ", &x64, &x64, CompareOp::EQ); + check.SetCondition("key_GT", &x64, &d1, CompareOp::GT); int px1[1] = {2}; int px2[1] = {2}; - check.SetCondition("key_EQ_1", (uint64_t)px1, (uint64_t)px2, ComUseCondCmp::EQ, 4); - check.SetCondition("key_GT_2", (uint64_t)px1, (uint64_t)px2, ComUseCondCmp::GT, 4); - check.SetValidCmpMode("key_EQ_1", ComUseCondCmp::NE); + check.SetCondition("key_EQ_1", (uint64_t)px1, (uint64_t)px2, CompareOp::EQ, 4); + check.SetCondition("key_GT_2", (uint64_t)px1, (uint64_t)px2, CompareOp::GT, 4); + check.SetValidCmpMode("key_EQ_1", CompareOp::NE); clk_old = clk1.clk; clk1.Step(10); @@ -368,12 +368,12 @@ int test_xdata() test_assert(1 == clk1.RemoveStepFalCbByDesc("Fal-lambda"), "check remove step cb fail"); px2[0] = 6; check.ClearCondition(); - ComUseRangeCheck rcheck(6, 4); - check.SetCondition("key_RG", (uint64_t)px1, (uint64_t)px2, ComUseCondCmp::GT, 4, + XRangeCheck rcheck(6, 4); + check.SetCondition("key_RG", (uint64_t)px1, (uint64_t)px2, CompareOp::GT, 4, 0, 0, 0, rcheck.GetArrayCmp(), rcheck.CSelf()); clk1.Enable(); clk1.Step(10); - test_assert(clk1.IsDisable(), "ComUseRangeCheck check fail"); + test_assert(clk1.IsDisable(), "XRangeCheck check fail"); Info("test fails: %d, success: %d\n", fails, success); return fails; } From e64d8622f7c50fc32a9f9447415672a470103496 Mon Sep 17 00:00:00 2001 From: Makiras <22570332+Makiras@users.noreply.github.com> Date: Mon, 12 Oct 2026 13:22:57 +0800 Subject: [PATCH 09/10] refactor!: name coverage points and give engines exclusive clock control Rename XCoverageItem, its kind enum and binding vector to XCoveragePoint, and identify each bin's parent with point_id. Update native coverage state, registration, bindings, tests and migration documentation together. Add explicit AcquireClock/ReleaseClock ownership for an entire execution, including pauses between RunUntil calls. Reject competing engines, manual stepping, reset, reinitialization and copying of an owned clock. Keep idle clock initialization and copying available, preserve phase on release, and require registrations to be disarmed before release. Release ownership on engine destruction, detect clocks destroyed first, and reject recursive runs or clearing/releasing a running engine. Guard legacy Python clock wait/run helpers and restore callback state on errors. Consolidate the unpublished branch to native ABI 3 relative to master's ABI 2. Keep coverage descriptor protocol 5 independent of the native ABI. Validation: Release CTest 10/10; 118 xreactor integration tests using ABI 3; verified libxspcomm.so.3 SONAME and Python extension linkage. The runtime implementation also passed ASan/UBSan 10/10, full xreactor 932 tests and 1026 tests in the isolated coverage-v2 frontend before ABI renumbering. --- CMakeLists.txt | 5 +- docs/architecture.md | 38 +++++++-- docs/maintainer-guidance.md | 21 +++-- include/xspcomm/xclock.h | 13 +++ include/xspcomm/xcoverage.h | 10 +-- include/xspcomm/xengine.h | 10 ++- src/core/clock.cpp | 50 ++++++++++- src/coverage/registration.cpp | 5 +- src/coverage/state.cpp | 86 +++++++++---------- src/coverage/state.h | 10 +-- src/runtime/engine.cpp | 65 +++++++++++++- swig/python/xcomm.py | 9 ++ swig/xcomm.i | 2 +- tests/unit/test_coverage_core.cpp | 12 +-- tests/unit/test_xcoverage.cpp | 111 +++++++++++++----------- tests/unit/test_xtrigger.cpp | 136 ++++++++++++++++++++++++++++++ 16 files changed, 450 insertions(+), 133 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 326abc1..5c71339 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,8 +10,9 @@ else() set(XSPCOMM_PACKAGE_VERSION "${PROJECT_VERSION}") endif() -# Increment only when the native C++ ABI becomes incompatible. -set(XSPCOMM_ABI_VERSION 5) +# Increment for incompatibility with master / published native ABIs. +# Unreleased changes on this branch share one ABI transition. +set(XSPCOMM_ABI_VERSION 3) list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) diff --git a/docs/architecture.md b/docs/architecture.md index 65d8840..8f7ac4f 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -101,8 +101,9 @@ comparison enum, now exposed with the same name in language bindings. `xinstance.h` and its `test_xdata()` helper are removed from the production SDK; that test implementation is compiled into `test_xdata` instead. Python-only third-call declarations live beside the Python wrapper. Existing signal -access and clock stepping interfaces are retained. Coverage descriptors and -clock/checker/runtime layouts have changed, so native clients must rebuild against ABI 5; +access and manual clock stepping interfaces remain available outside an owned +execution. Coverage descriptors and clock/checker/runtime layouts have changed, +so native clients must rebuild against ABI 3; source clients must also update the includes above. ## Trigger programs and coverage @@ -112,14 +113,17 @@ expression, sequence or FSM, with event mode and execution capacity. The trigger and coverage paths use the same validator and advancement kernel. Matching state belongs to each execution and is independent of the immutable program description. -`XCoverageItem.kind` selects Value, Pattern or Cross. Value points require an +`XCoveragePoint.kind` selects Value, Pattern or Cross. Value points require an XData source; pattern points have no synthetic source; crosses name preceding value points. `XCoverageBin.kind` selects Normal, Ignore, Illegal or Default, while `XCoverageBin.program` supplies matching and `terminals` selects FSM completion results. Impossible field combinations are rejected at registration. `CoverageVersion() == 5` identifies this descriptor protocol; the native library -ABI is independently versioned as 5. Protocol 4 frontends must migrate flat bin -program fields to `bin.program` and replace `item.pattern` with an explicit kind. +ABI is independently versioned as 3. Protocol 4 frontends must migrate flat bin +program fields to `bin.program` and replace `point.pattern` with an explicit kind. +The public descriptor is `XCoveragePoint`, +with `XCoveragePointKind` / `XCoveragePointVector` in bindings; a bin names its +parent through `point_id`. The unshipped `XCoverageItem` names are removed. Equivalent programs within one point share an execution when their roots, steps/transitions, event mode and overlap capacity agree. Terminal selection, @@ -137,6 +141,30 @@ Crosses containing pattern points are rejected until completion correlation semantics are defined. Program reclamation during a long execution and complete expression width/signed/cast/four-state semantics remain separate work. +## Clock driving ownership + +Constructing `XEngine` leaves its clock available for manual initialization. +Call `AcquireClock()` before registering watchers or running the engine, and +hold it for the whole execution, including pauses between `RunUntil` calls. +Only one engine can acquire a given clock. While owned, manual `Step`, +`StepHalf`, `RunStep`, `Reset`, `ReInit`, changes to the default stopping edge, +and copying or assigning that clock throw before changing its state. +Idle clocks retain their existing copy behavior. + +`ReleaseClock()` requires all registrations to be disarmed, including fired +registrations. It preserves the clock's tick and phase; if released mid-cycle, +finish with `StepHalf()` before calling legacy whole-cycle `Step`. +Destruction also releases the lease, and a destroyed clock is detected before +subsequent engine access. Recursive runs and clearing/releasing a running +engine are rejected. These are single-threaded simulation ownership checks, +not synchronization between host threads. + +The xreactor backend acquires on Execution entry and releases after cleanup, +including exceptional exit. Legacy Python clock wait/run helpers cannot be +mixed with an owned engine; use xreactor triggers within Execution instead. +The phase/matcher loop retains native batch stepping without per-tick Python +ownership callbacks or locks. + ## Monitor callback ownership `XStepCallback.Attach(clock, rising)` registers a managed clock callback; `Detach` diff --git a/docs/maintainer-guidance.md b/docs/maintainer-guidance.md index 2bc7ed7..279a298 100644 --- a/docs/maintainer-guidance.md +++ b/docs/maintainer-guidance.md @@ -48,18 +48,17 @@ not `abi3`. The native C++ ABI number is `XSPCOMM_ABI_VERSION` in `CMakeLists.txt`. It is independent of the Python package version and sets the `libxspcomm.so.N` -SONAME. Increase it only for an incompatible native ABI change. The library +SONAME. Increase it for incompatibility with master or a published native ABI. +Unreleased changes on the same branch share one ABI transition; do not increment +it for every intermediate commit. The library also exports `xspcomm.abi_version()`, so wheel tests can check the loaded ABI. If it changes, update Picker's supported xcomm range and rebuild generated DUT extensions; a Python version specifier alone cannot verify C++ ABI identity. ## Migrating to the native trigger engine -The native trigger engine changes the C++ layouts of `XClock`, `XData`, and -`ExprNode`, which introduced native ABI 2. Complete per-bin trigger programs now -reorganize `XCoverageBin` and checker/runtime layouts (ABI 4). Renaming the -monitor classes changes exported C++ symbols, so the current native ABI is 5 -(`libxspcomm.so.5`). Rebuild +Master uses native ABI 2. This unreleased branch changes clock, expression, +coverage and checker/runtime layouts and renames exported monitor classes. These changes share native ABI 3 (`libxspcomm.so.3`). Rebuild Picker-generated DUT extensions and other native bindings against these headers and library, and update the consumer's ABI requirement together. Keep each generated DUT and its runtime on a matching native ABI. @@ -74,11 +73,11 @@ bundled native runtime. The PR #30 review migration introduces `XEngine` with the existing `XTriggerEngine` entry alias, explicit coverage kinds and `bin.program`. -The next preview is `0.3.0.dev2` for ABI 5 / descriptor protocol 5, with -`xmonitor.h`, `monitor/` and the `XConditionCheck` family of names. The earlier -`0.3.0.dev1` preview identifies ABI 4 / protocol 5, and `0.3.0.dev0` identifies -ABI 3 / protocol 4. Update source names and frontend lowering, then rebuild -native clients together. See [the migration tables](architecture.md#header-migration). +The next preview is `0.3.0.dev2` for ABI 3 / descriptor protocol 5, with +`xmonitor.h`, `monitor/` and the `XConditionCheck` family of names. Unpublished +branch iterations do not establish separate native ABI versions. Update source +names and frontend lowering, then rebuild native clients together. See +[the migration tables](architecture.md#header-migration). The installed SDK consists of `include/xspcomm/` and the generated `xconfig.h`. Keep pure matching, coverage-state and native-memory implementation headers under diff --git a/include/xspcomm/xclock.h b/include/xspcomm/xclock.h index c81aefd..982c56e 100644 --- a/include/xspcomm/xclock.h +++ b/include/xspcomm/xclock.h @@ -18,6 +18,7 @@ uint64_t TEST_get_u64_step_func(); uint64_t TEST_get_u64_ris_fal_cblback_func(); class XClock; +class XEngine; #if ENABLE_XCOROUTINE class XStep : public _XAWait { @@ -58,6 +59,16 @@ enum class FastMode { class XClock { friend class XStepCallback; + friend class XEngine; + // Idle clocks remain copyable; copying an actively controlled clock would + // create a second stepping entry point for the same DUT. + struct DriveControl { + XEngine *owner = nullptr; + bool stepping = false; + DriveControl() = default; + DriveControl(const DriveControl &other); + DriveControl &operator=(const DriveControl &other); + } drive_control; detail::Lifetime callback_lifetime; xfunction step_fc; #if ENABLE_XCOROUTINE @@ -74,6 +85,7 @@ class XClock uint64_t half_tick = 0; XPhase phase = XPhase::RisingStable; + void CheckManualStep() const; virtual void _step(bool d); void _shchedule_await(); bool _step_half_impl(bool honor_disable); @@ -119,6 +131,7 @@ class XClock } void RefreshComb(){this->eval();} void RefreshCombT(){this->eval_t();} + bool IsControlled() const { return drive_control.owner != nullptr; } void Step(int s = 1); bool StepHalf(); uint64_t GetHalfTick() const { return this->half_tick; } diff --git a/include/xspcomm/xcoverage.h b/include/xspcomm/xcoverage.h index 55fc964..268ff24 100644 --- a/include/xspcomm/xcoverage.h +++ b/include/xspcomm/xcoverage.h @@ -10,19 +10,19 @@ namespace xspcomm { class XData; -enum class XCoverageItemKind : uint8_t { Value = 0, Pattern = 1, Cross = 2 }; +enum class XCoveragePointKind : uint8_t { Value = 0, Pattern = 1, Cross = 2 }; enum class XCoverageBinKind : uint8_t { Normal = 0, Ignore = 1, Illegal = 2, Default = 3 }; // Coverage owns classification and statistics; trigger programs own matching. -struct XCoverageItem { - XCoverageItemKind kind = XCoverageItemKind::Value; +struct XCoveragePoint { + XCoveragePointKind kind = XCoveragePointKind::Value; int gate = -1; XData *signal = nullptr; // Value only std::vector dimensions; // Cross only; preceding value point IDs }; struct XCoverageBin { - uint32_t item = 0; + uint32_t point_id = 0; XCoverageBinKind kind = XCoverageBinKind::Normal; XTriggerProgram program; std::vector terminals; // FSM completion selection; empty selects all @@ -33,7 +33,7 @@ struct XCoverageSnapshot { uint32_t generation = 0; uint64_t epoch = 0; uint64_t tick = 0; - // group samples/gated; samples/gated/ignored/unmatched/unknown per item; + // group samples/gated; samples/gated/ignored/unmatched/unknown per point; // then bin counts in declaration order. std::vector counters; std::vector illegal_bins; diff --git a/include/xspcomm/xengine.h b/include/xspcomm/xengine.h index 7c8224a..d0fdc3c 100644 --- a/include/xspcomm/xengine.h +++ b/include/xspcomm/xengine.h @@ -21,6 +21,8 @@ class XEngine { struct Watcher; XClock *clock = nullptr; + std::weak_ptr clock_lifetime; + bool running = false; size_t capacity = 0; uint64_t next_event_id = 1; std::vector watchers; @@ -29,6 +31,8 @@ class XEngine { std::unique_ptr expr_engine; XPhase evaluation_phase = XPhase::RisingStable; + void CheckClockOwnership() const; + void CheckNotRunning() const; XRegistrationHandle Allocate(); void EvaluatePhase(XPhase phase); bool HasArmedPhase(XPhase phase) const; @@ -43,6 +47,10 @@ class XEngine { explicit XEngine(XClock &clock, size_t capacity = 1024); ~XEngine(); + // Hold the clock for the entire execution, including pauses between batches. + void AcquireClock(); + void ReleaseClock(); + XRegistrationHandle ArmEdge(XPhase phase, uint64_t source_id = 0); XRegistrationHandle ArmClockCycles( uint64_t cycles, XPhase phase = XPhase::RisingStable, @@ -85,7 +93,7 @@ class XEngine { XPhase phase = XPhase::RisingStable, uint64_t source_id = 0); static uint32_t CoverageVersion() { return 5; } void AttachCoverage(XRegistrationHandle handle, - const std::vector &items, + const std::vector &points, const std::vector &bins, int gate = -1, int abort = -1, bool raise_illegal = true, size_t diagnostic_capacity = 1024, diff --git a/src/core/clock.cpp b/src/core/clock.cpp index 8365c57..f2213d1 100644 --- a/src/core/clock.cpp +++ b/src/core/clock.cpp @@ -1,6 +1,32 @@ #include "xspcomm/xclock.h" +#include + namespace xspcomm { + +namespace { +class StepScope { + bool &stepping; +public: + explicit StepScope(bool &stepping) : stepping(stepping) { + if (stepping) throw std::logic_error("XClock is already stepping"); + stepping = true; + } + ~StepScope() { stepping = false; } +}; +} // namespace + +XClock::DriveControl::DriveControl(const DriveControl &other) +{ + if (other.owner) throw std::logic_error("cannot copy an XClock controlled by XEngine"); +} + +XClock::DriveControl &XClock::DriveControl::operator=(const DriveControl &other) +{ + if (owner || other.owner) throw std::logic_error("cannot assign an XClock controlled by XEngine"); + return *this; +} + #if ENABLE_XCOROUTINE XStep::XStep(XClock &clk, u_int64_t step, std::map, _XAWait *> *p) : @@ -54,8 +80,13 @@ void XClock::_call_back(std::vector &list) return; } this->in_callback = true; - for (auto &e : list) { - if (e.func) e.func(this->clk, e.args); + try { + for (auto &e : list) { + if (e.func) e.func(this->clk, e.args); + } + } catch (...) { + this->in_callback = false; + throw; } this->in_callback = false; if (callbacks_removed) { @@ -80,6 +111,7 @@ void XClock::_add_cb(std::vector &cblist, void XClock::default_stop_on_rise(bool rise) { + CheckManualStep(); Assert(!this->half_cycle_open, "Cannot change stop_on_rise in the middle of a clock cycle"); this->stop_on_rise = rise; @@ -122,6 +154,7 @@ void XClock::ReInit(xfunction stepfunc, std::initializer_list clock_pins, std::initializer_list ports) { + CheckManualStep(); this->step_fc = stepfunc; for (auto &d : clock_pins) { this->Add(d); } for (auto &d : ports) { this->Add(d); } @@ -174,13 +207,23 @@ XClock& XClock::Add(xspcomm::XPort &d) return *this; } +void XClock::CheckManualStep() const +{ + if (drive_control.owner != nullptr) { + throw std::logic_error( + "XClock is controlled by XEngine; use the owning Execution to advance it"); + } +} + void XClock::Step(int s) { + CheckManualStep(); if (unlikely(this->is_disable)) return; if (unlikely(this->in_callback == true)) { Warn("Cannot call XClock.Step in callbacks, Ignore!"); return; } + StepScope step(drive_control.stepping); for (int i = 0; i < s; i++) { if (unlikely(this->is_disable)) return; Assert(!this->half_cycle_open, @@ -195,10 +238,12 @@ void XClock::Step(int s) bool XClock::StepHalf() { + CheckManualStep(); if (unlikely(this->in_callback == true)) { Warn("Cannot call XClock.StepHalf in callbacks, Ignore!"); return false; } + StepScope step(drive_control.stepping); return this->_step_half_impl(true); } @@ -298,6 +343,7 @@ void XClock::eval_t() void XClock::Reset() { + CheckManualStep(); this->clk = 0; this->half_tick = 0; this->half_cycle_open = false; diff --git a/src/coverage/registration.cpp b/src/coverage/registration.cpp index d3030e7..c9da78e 100644 --- a/src/coverage/registration.cpp +++ b/src/coverage/registration.cpp @@ -10,13 +10,14 @@ namespace xspcomm { void XEngine::CheckCoverageHandle(XRegistrationHandle handle) const { + CheckClockOwnership(); if (!handle.IsValid() || handle.slot >= watchers.size() || !watchers[handle.slot].occupied || watchers[handle.slot].generation != handle.generation || !watchers[handle.slot].coverage) throw std::invalid_argument("stale coverage handle"); } void XEngine::AttachCoverage(XRegistrationHandle handle, - const std::vector &items, const std::vector &bins, + const std::vector &points, const std::vector &bins, int gate, int abort, bool raise_illegal, size_t diagnostic_capacity, bool overlap, size_t max_active, bool diagnostics) { @@ -25,7 +26,7 @@ void XEngine::AttachCoverage(XRegistrationHandle handle, watchers[handle.slot].coverage) throw std::invalid_argument("invalid coverage registration"); auto &watcher = watchers[handle.slot]; watcher.coverage = std::make_shared( - handle.generation, *expr_engine, watcher.CoverageSource(), items, bins, + handle.generation, *expr_engine, watcher.CoverageSource(), points, bins, gate, abort, raise_illegal, diagnostic_capacity, overlap, max_active, diagnostics); } diff --git a/src/coverage/state.cpp b/src/coverage/state.cpp index bbfc8d6..1c34501 100644 --- a/src/coverage/state.cpp +++ b/src/coverage/state.cpp @@ -11,9 +11,9 @@ namespace detail { static bool SameCoverageExecution(const XCoverageBin &a, const XCoverageBin &b) { - if (std::tie(a.item, a.program.kind, a.program.root, a.program.mode, a.program.overlap, a.program.max_active, + if (std::tie(a.point_id, a.program.kind, a.program.root, a.program.mode, a.program.overlap, a.program.max_active, a.program.state_count, a.program.start_state) != - std::tie(b.item, b.program.kind, b.program.root, b.program.mode, b.program.overlap, b.program.max_active, + std::tie(b.point_id, b.program.kind, b.program.root, b.program.mode, b.program.overlap, b.program.max_active, b.program.state_count, b.program.start_state)) return false; if (a.program.steps.size() != b.program.steps.size() || a.program.transitions.size() != b.program.transitions.size()) return false; return std::equal(a.program.steps.begin(), a.program.steps.end(), b.program.steps.begin(), [](const auto &x, const auto &y) { @@ -26,12 +26,12 @@ static bool SameCoverageExecution(const XCoverageBin &a, const XCoverageBin &b) } CoverageState::CoverageState(uint32_t generation, ExprEngine &expr, const PatternView &source, - const std::vector &items, const std::vector &bins, + const std::vector &points, const std::vector &bins, int gate, int abort, bool raise_illegal, size_t diagnostic_capacity, bool overlap, size_t max_active, bool diagnostics) : source_matcher(source) { - if (items.empty() || bins.empty() || diagnostic_capacity == 0) + if (points.empty() || bins.empty() || diagnostic_capacity == 0) throw std::invalid_argument("empty coverage definition or diagnostic capacity"); if (max_active == 0 || (!overlap && max_active != 1)) throw std::invalid_argument("invalid coverage max_active"); @@ -44,32 +44,32 @@ CoverageState::CoverageState(uint32_t generation, ExprEngine &expr, const Patter if (r != -1 && !expr.ValidRoot(r)) throw std::invalid_argument("invalid coverage expression"); }; check(gate); check(abort); - for (size_t i = 0; i < items.size(); ++i) { - check(items[i].gate); - const auto &item = items[i]; - switch (item.kind) { - case XCoverageItemKind::Value: - if (!item.signal || !item.dimensions.empty()) + for (size_t i = 0; i < points.size(); ++i) { + check(points[i].gate); + const auto &point = points[i]; + switch (point.kind) { + case XCoveragePointKind::Value: + if (!point.signal || !point.dimensions.empty()) throw std::invalid_argument("value point requires only a signal"); break; - case XCoverageItemKind::Pattern: - if (item.signal || !item.dimensions.empty()) + case XCoveragePointKind::Pattern: + if (point.signal || !point.dimensions.empty()) throw std::invalid_argument("pattern point cannot have a signal or dimensions"); break; - case XCoverageItemKind::Cross: - if (item.signal || item.dimensions.empty()) + case XCoveragePointKind::Cross: + if (point.signal || point.dimensions.empty()) throw std::invalid_argument("cross requires only dimensions"); break; default: - throw std::invalid_argument("invalid coverage item kind"); + throw std::invalid_argument("invalid coverage point kind"); } - for (auto d : items[i].dimensions) - if (d >= i || items[d].kind != XCoverageItemKind::Value) throw std::invalid_argument("invalid cross point"); + for (auto d : points[i].dimensions) + if (d >= i || points[d].kind != XCoveragePointKind::Value) throw std::invalid_argument("invalid cross point"); } for (const auto &bin : bins) { - if (bin.item >= items.size() || static_cast(bin.kind) > 3) throw std::invalid_argument("invalid coverage bin"); + if (bin.point_id >= points.size() || static_cast(bin.kind) > 3) throw std::invalid_argument("invalid coverage bin"); check(bin.program.root); - const bool pattern = items[bin.item].kind == XCoverageItemKind::Pattern; + const bool pattern = points[bin.point_id].kind == XCoveragePointKind::Pattern; if (!pattern && !bin.program.steps.empty() && bin.program.kind != XTriggerProgramKind::Sequence) throw std::invalid_argument("transition bin requires a sequence program"); if (pattern || bin.program.kind == XTriggerProgramKind::Sequence) { @@ -91,29 +91,29 @@ CoverageState::CoverageState(uint32_t generation, ExprEngine &expr, const Patter if (!pattern) for (const auto &step : bin.program.steps) if (step.kind != XSequenceStepKind::Wait && step.kind != XSequenceStepKind::Next) throw std::invalid_argument("coverage transition requires adjacent steps"); - const auto &dims = items[bin.item].dimensions; + const auto &dims = points[bin.point_id].dimensions; if (bin.dimensions.size() != dims.size()) throw std::invalid_argument("invalid cross tuple"); for (size_t j = 0; j < dims.size(); ++j) { auto d = bin.dimensions[j]; - if (d >= bins.size() || bins[d].kind != XCoverageBinKind::Normal || bins[d].item != dims[j]) + if (d >= bins.size() || bins[d].kind != XCoverageBinKind::Normal || bins[d].point_id != dims[j]) throw std::invalid_argument("invalid cross bin"); } } - this->items = items; this->bins = bins; this->gate = gate; this->abort = abort; + this->points = points; this->bins = bins; this->gate = gate; this->abort = abort; this->raise_illegal = raise_illegal; this->diagnostic_capacity = diagnostic_capacity; this->overlap = overlap; this->max_active = max_active; // Grow only with observed starts, never reserve arbitrary max_active up front. if (diagnostics) snapshot.diagnostics.resize((1 + bins.size()) * CoverageState::DiagnosticCount); snapshot.generation = generation; - snapshot.counters.resize(2 + items.size() * 5 + bins.size()); + snapshot.counters.resize(2 + points.size() * 5 + bins.size()); delta.resize(snapshot.counters.size()); - matched.resize(bins.size()); normal_counts.resize(items.size()); + matched.resize(bins.size()); normal_counts.resize(points.size()); matchers.resize(bins.size()); owners.resize(bins.size()); for (size_t i = 0; i < bins.size(); ++i) { owners[i] = i; if (bins[i].kind == XCoverageBinKind::Illegal) illegal_indices.push_back(i); - if ((items[bins[i].item].kind == XCoverageItemKind::Pattern)) { + if ((points[bins[i].point_id].kind == XCoveragePointKind::Pattern)) { for (size_t previous = 0; previous < i; ++previous) { if (owners[previous] == previous && SameCoverageExecution(bins[i], bins[previous])) { owners[i] = previous; @@ -166,7 +166,7 @@ size_t CoverageState::ExecutionCount() const { size_t count = 0; for (size_t b = 0; b < bins.size(); ++b) - count += (items[bins[b].item].kind == XCoverageItemKind::Pattern) && owners[b] == b; + count += (points[bins[b].point_id].kind == XCoveragePointKind::Pattern) && owners[b] == b; return count; } @@ -219,10 +219,10 @@ inline bool Enabled(ExprEngine &expr, int root) { void CoverageState::SampleCrossPoint(size_t i, size_t offset) { - const auto &item = items[i]; + const auto &point = points[i]; const size_t base = 2 + i * 5; uint64_t combinations = 1, hits = 0; - for (auto d : item.dimensions) { + for (auto d : point.dimensions) { const auto n = normal_counts[d]; if (n && combinations > std::numeric_limits::max() / n) throw std::overflow_error("coverage cross product overflow"); @@ -230,7 +230,7 @@ void CoverageState::SampleCrossPoint(size_t i, size_t offset) } for (size_t b = 0; b < bins.size(); ++b) { const auto &bin = bins[b]; - if (bin.item != i) continue; + if (bin.point_id != i) continue; bool match = true; for (auto d : bin.dimensions) match = match && matched[d]; if (!match) continue; @@ -246,7 +246,7 @@ inline void CoverageState::SamplePatternPoint(ExprEngine &expr, size_t i, size_t bool any = false; for (size_t b = 0; b < bins.size(); ++b) { const auto &bin = bins[b]; - if (bin.item != i) continue; + if (bin.point_id != i) continue; auto &matcher = matchers[owners[b]]; if (owners[b] == b) { if (bin.program.kind == XTriggerProgramKind::Expr) matcher.AdvanceCondition(expr); @@ -266,7 +266,7 @@ void CoverageState::SampleValuePoint(ExprEngine &expr, size_t i, size_t offset) int priority = -1; for (size_t b = 0; b < bins.size(); ++b) { const auto &bin = bins[b]; - if (bin.item != i || bin.kind == XCoverageBinKind::Default) continue; + if (bin.point_id != i || bin.kind == XCoverageBinKind::Default) continue; bool hit = false; if (bin.program.steps.empty()) hit = Enabled(expr, bin.program.root); else { @@ -279,7 +279,7 @@ void CoverageState::SampleValuePoint(ExprEngine &expr, size_t i, size_t offset) bool any = false; for (size_t b = 0; b < bins.size(); ++b) { const auto &bin = bins[b]; - if (bin.item != i) continue; + if (bin.point_id != i) continue; const bool hit = priority < 0 ? bin.kind == XCoverageBinKind::Default : matched[b] && static_cast(bin.kind) == priority; matched[b] = hit && bin.kind == XCoverageBinKind::Normal; if (matched[b]) ++normal_counts[i]; @@ -293,31 +293,31 @@ void CoverageState::Sample(ExprEngine &expr, uint64_t tick) std::fill(delta.begin(), delta.end(), 0); std::fill(matched.begin(), matched.end(), false); std::fill(normal_counts.begin(), normal_counts.end(), 0); - const size_t offset = 2 + items.size() * 5; + const size_t offset = 2 + points.size() * 5; if (!Enabled(expr, gate)) { delta[1] = 1; for (size_t b = 0; b < matchers.size(); ++b) if (owners[b] == b) ClearMatcher(b + 1, matchers[b], Cleared); } else { delta[0] = 1; - for (size_t i = 0; i < items.size(); ++i) { - const auto &item = items[i]; + for (size_t i = 0; i < points.size(); ++i) { + const auto &point = points[i]; const size_t base = 2 + i * 5; - const bool gate = Enabled(expr, item.gate); + const bool gate = Enabled(expr, point.gate); // U refreshes all native words; DataValid checks X/Z across the full width. - const bool known = !item.signal || (item.signal->U(), item.signal->DataValid()); + const bool known = !point.signal || (point.signal->U(), point.signal->DataValid()); if (!gate || !known) { delta[base + 1] = 1; if (gate && !known) delta[base + 4] = 1; for (size_t b = 0; b < bins.size(); ++b) - if (bins[b].item == i && owners[b] == b) ClearMatcher(b + 1, matchers[b], Cleared); + if (bins[b].point_id == i && owners[b] == b) ClearMatcher(b + 1, matchers[b], Cleared); continue; } delta[base] = 1; - switch (item.kind) { - case XCoverageItemKind::Value: SampleValuePoint(expr, i, offset); break; - case XCoverageItemKind::Pattern: SamplePatternPoint(expr, i, offset); break; - case XCoverageItemKind::Cross: SampleCrossPoint(i, offset); break; + switch (point.kind) { + case XCoveragePointKind::Value: SampleValuePoint(expr, i, offset); break; + case XCoveragePointKind::Pattern: SamplePatternPoint(expr, i, offset); break; + case XCoveragePointKind::Cross: SampleCrossPoint(i, offset); break; } } } @@ -333,7 +333,7 @@ void CoverageState::Sample(ExprEngine &expr, uint64_t tick) for (auto b : illegal_indices) if (delta[offset + b]) { snapshot.illegal_bins.push_back(b); snapshot.illegal_ticks.push_back(tick); - auto *signal = items[bins[b].item].signal; + auto *signal = points[bins[b].point_id].signal; snapshot.illegal_values.push_back(signal ? signal->String() : "0"); } if (illegal && raise_illegal) throw std::runtime_error("coverage illegal bin"); diff --git a/src/coverage/state.h b/src/coverage/state.h index 745ba14..835f591 100644 --- a/src/coverage/state.h +++ b/src/coverage/state.h @@ -15,7 +15,7 @@ class CoverageState { public: enum class SampleStatus { Ready, Skip, Aborted }; CoverageState(uint32_t generation, ExprEngine &expr, const PatternView &source, - const std::vector &items, + const std::vector &points, const std::vector &bins, int gate, int abort, bool raise_illegal, size_t diagnostic_capacity, bool overlap, size_t max_active, bool diagnostics); @@ -39,12 +39,12 @@ class CoverageState { private: void RecordDiagnostics(size_t pattern, const MatchUpdate &update); - void SampleValuePoint(ExprEngine &expr, size_t item, size_t offset); - inline void SamplePatternPoint(ExprEngine &expr, size_t item, size_t offset); - void SampleCrossPoint(size_t item, size_t offset); + void SampleValuePoint(ExprEngine &expr, size_t point, size_t offset); + inline void SamplePatternPoint(ExprEngine &expr, size_t point, size_t offset); + void SampleCrossPoint(size_t point, size_t offset); size_t Advance(ExprEngine &expr, PatternMatcher &matcher, size_t pattern, bool overlap, size_t max_active); - std::vector items; + std::vector points; std::vector bins; PatternMatcher source_matcher; std::vector matchers; diff --git a/src/runtime/engine.cpp b/src/runtime/engine.cpp index c4053e7..baf1798 100644 --- a/src/runtime/engine.cpp +++ b/src/runtime/engine.cpp @@ -12,6 +12,15 @@ namespace xspcomm { using WatcherKind = detail::TriggerRegistration::Kind; +namespace { +class RunScope { + bool &running; +public: + explicit RunScope(bool &running) : running(running) { running = true; } + ~RunScope() { running = false; } +}; +} // namespace + XEngine::XEngine(XClock *clock, size_t capacity) : clock(clock), capacity(capacity), expr_engine(std::make_unique()) { @@ -21,6 +30,7 @@ XEngine::XEngine(XClock *clock, size_t capacity) if (capacity == 0) { throw std::invalid_argument("XTriggerEngine capacity must be positive"); } + clock_lifetime = clock->callback_lifetime.Observe(); watchers.reserve(capacity); free_slots.reserve(capacity); hit_buffer.reserve(capacity); @@ -28,10 +38,51 @@ XEngine::XEngine(XClock *clock, size_t capacity) XEngine::XEngine(XClock &clock, size_t capacity) : XEngine(&clock, capacity) {} -XEngine::~XEngine() = default; +XEngine::~XEngine() +{ + if (!clock_lifetime.expired() && clock->drive_control.owner == this) { + clock->drive_control.owner = nullptr; + } +} + +void XEngine::CheckNotRunning() const +{ + if (running) throw std::logic_error("XEngine is already running"); +} + +void XEngine::CheckClockOwnership() const +{ + if (clock_lifetime.expired()) throw std::logic_error("XEngine clock has been destroyed"); + if (clock->drive_control.owner != this) { + throw std::logic_error("XEngine must acquire its clock with AcquireClock first"); + } +} + +void XEngine::AcquireClock() +{ + CheckNotRunning(); + if (clock_lifetime.expired()) throw std::logic_error("XEngine clock has been destroyed"); + if (clock->in_callback || clock->drive_control.stepping) { + throw std::logic_error("cannot acquire XClock during a clock step or callback"); + } + if (clock->drive_control.owner != nullptr && clock->drive_control.owner != this) { + throw std::logic_error("XClock is already controlled by another XEngine"); + } + clock->drive_control.owner = this; +} + +void XEngine::ReleaseClock() +{ + CheckNotRunning(); + if (ActiveCount() != 0) throw std::logic_error("cannot release XClock with active registrations"); + if (!clock_lifetime.expired() && clock->drive_control.owner == this) { + clock->drive_control.owner = nullptr; + } +} XRegistrationHandle XEngine::Allocate() { + CheckClockOwnership(); uint32_t slot; if (!free_slots.empty()) { slot = free_slots.back(); @@ -430,6 +481,10 @@ XRunResult XEngine::RunUntil( uint64_t max_half_ticks, uint64_t max_wall_time_ns, uint32_t budget_check_interval) { + CheckNotRunning(); + CheckClockOwnership(); + if (clock->in_callback) throw std::logic_error("cannot drive XEngine from a clock callback"); + RunScope run(running); XRunResult result; result.stopped_phase = clock->GetPhase(); hit_buffer.clear(); @@ -438,7 +493,7 @@ XRunResult XEngine::RunUntil( if (budget_check_interval == 0) budget_check_interval = 1; for (uint64_t i = 0; i < max_half_ticks; ++i) { - if (!clock->StepHalf()) { + if (!clock->_step_half_impl(true)) { result.stop_reason = XStopReason::BackendStop; result.hits = hit_buffer; return result; @@ -483,6 +538,10 @@ XRunResult XEngine::RunUntil( XRunResult XEngine::SamplePhase(XPhase phase) { + CheckNotRunning(); + CheckClockOwnership(); + if (clock->in_callback) throw std::logic_error("cannot drive XEngine from a clock callback"); + RunScope run(running); if (phase != XPhase::DriveStable || clock->GetPhase() != XPhase::FallingStable) { throw std::logic_error( @@ -513,6 +572,7 @@ size_t XEngine::ActiveCount() const void XEngine::ClearExecutionState() { + CheckNotRunning(); if (ActiveCount() != 0) { throw std::logic_error( "cannot reset trigger runtime with active watchers"); @@ -532,6 +592,7 @@ void XEngine::ClearExecutionState() void XEngine::Clear() { + CheckNotRunning(); watchers.clear(); free_slots.clear(); hit_buffer.clear(); diff --git a/swig/python/xcomm.py b/swig/python/xcomm.py index 5852944..8a0c705 100644 --- a/swig/python/xcomm.py +++ b/swig/python/xcomm.py @@ -294,7 +294,12 @@ def XClock_Check_Exceptions(self): XClock__old_Step = XClock.Step XClock.RawStep = XClock__old_Step +def XClock_RequireManualControl(self): + if self.IsControlled(): + raise RuntimeError("XClock is controlled by XEngine; use xreactor triggers inside Execution") + def XClock_Step(self, cycle: int): + XClock_RequireManualControl(self) for i in range(cycle): XClock__old_Step(self, 1) XClock_Check_Exceptions(self) @@ -302,18 +307,22 @@ def XClock_Step(self, cycle: int): self._step_event.clear() async def XClock_AStep(self, cycle: int): + XClock_RequireManualControl(self) for i in range(cycle): await self._step_event.wait() async def XClock_ACondition(self, condition): + XClock_RequireManualControl(self) assert callable(condition), "condition must be callable" while not condition(): await self._step_event.wait() async def XClock_ANext(self): + XClock_RequireManualControl(self) return await self._step_event.wait() async def XClock_RunStep(self, cycle: int): + XClock_RequireManualControl(self) for i in range(cycle): await tick_clock_ready() XClock_Step(self, 1) diff --git a/swig/xcomm.i b/swig/xcomm.i index 2b86208..0efa7fe 100644 --- a/swig/xcomm.i +++ b/swig/xcomm.i @@ -92,7 +92,7 @@ namespace std { namespace std { %template(XBackendHitVector) vector; - %template(XCoverageItemVector) vector; + %template(XCoveragePointVector) vector; %template(XCoverageBinVector) vector; %template(XUInt32Vector) vector; %template(XUInt64Vector) vector; diff --git a/tests/unit/test_coverage_core.cpp b/tests/unit/test_coverage_core.cpp index 5b564c9..5fdae2b 100644 --- a/tests/unit/test_coverage_core.cpp +++ b/tests/unit/test_coverage_core.cpp @@ -13,11 +13,11 @@ TEST_CASE("Coverage samples independently of the trigger engine", "[coverage]") value = 7; gate = 1; abort = 0; - XCoverageItem item; - item.signal = &value; + XCoveragePoint point; + point.signal = &value; XCoverageBin bin; bin.program.root = expr.NewConst(1); - CoverageState coverage(9, expr, {}, {item}, {bin}, + CoverageState coverage(9, expr, {}, {point}, {bin}, expr.NewSignal(&gate), expr.NewSignal(&abort), true, 32, false, 1, true); REQUIRE(coverage.BeginSample(expr, 1) == CoverageState::SampleStatus::Ready); @@ -52,11 +52,11 @@ TEST_CASE("Coverage abort clears overlapping pattern history", "[coverage][patte {XSequenceStepKind::Wait, expr.NewSignal(&start)}, {XSequenceStepKind::Within, expr.NewSignal(&done), 1, 3}}; const detail::PatternView source{&steps}; - XCoverageItem item; - item.signal = &value; + XCoveragePoint point; + point.signal = &value; XCoverageBin bin; bin.program.root = expr.NewConst(1); - CoverageState coverage(1, expr, source, {item}, {bin}, + CoverageState coverage(1, expr, source, {point}, {bin}, -1, expr.NewSignal(&abort), true, 32, true, 2, true); for (uint64_t tick = 1; tick <= 2; ++tick) { diff --git a/tests/unit/test_xcoverage.cpp b/tests/unit/test_xcoverage.cpp index b30fd97..de3f229 100644 --- a/tests/unit/test_xcoverage.cpp +++ b/tests/unit/test_xcoverage.cpp @@ -9,16 +9,17 @@ using namespace xspcomm; TEST_CASE("Coverage counts persist without returning ordinary hits", "[xtrigger][coverage]") { XClock clock([](bool) { return 0; }); XTriggerEngine engine(clock, 4); + engine.AcquireClock(); XData value(8, XData::InOut); value = 1; const int source = engine.ExprNewSignal(&value); const int one = engine.ExprNewCompare(static_cast(ExprOp::EQ), source, engine.ExprNewConst(1)); - XCoverageItem item; - item.signal = &value; + XCoveragePoint point; + point.signal = &value; XCoverageBin bin; bin.program.root = one; auto handle = engine.ArmEdge(XPhase::RisingStable); - engine.AttachCoverage(handle, {item}, {bin}); + engine.AttachCoverage(handle, {point}, {bin}); auto limit = engine.ArmClockCycles(10); auto run = engine.RunUntil(100); REQUIRE(run.advanced_ticks == 20); @@ -44,17 +45,18 @@ TEST_CASE("Coverage counts persist without returning ordinary hits", "[xtrigger] TEST_CASE("Coverage transitions overlap and have isolated registration history", "[xtrigger][coverage]") { XClock clock([](bool) { return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); XData value(8, XData::InOut); value = 1; const int source = engine.ExprNewSignal(&value); const int one = engine.ExprNewCompare(static_cast(ExprOp::EQ), source, engine.ExprNewConst(1)); - XCoverageItem item; - item.signal = &value; + XCoveragePoint point; + point.signal = &value; XCoverageBin bin; bin.program.kind = XTriggerProgramKind::Sequence; bin.program.steps = {{XSequenceStepKind::Wait, one}, {XSequenceStepKind::Next, one}}; auto h = engine.ArmEdge(XPhase::RisingStable); - engine.AttachCoverage(h, {item}, {bin}); + engine.AttachCoverage(h, {point}, {bin}); REQUIRE(engine.RunUntil(6).hits.empty()); REQUIRE(engine.CoverageSnapshot(h).counters.back() == 2); engine.ResetCoverage(h, false); @@ -62,7 +64,7 @@ TEST_CASE("Coverage transitions overlap and have isolated registration history", REQUIRE(engine.CoverageSnapshot(h).counters.back() == 2); REQUIRE(engine.Disarm(h)); auto next = engine.ArmEdge(XPhase::RisingStable); - engine.AttachCoverage(next, {item}, {bin}); + engine.AttachCoverage(next, {point}, {bin}); engine.RunUntil(2); REQUIRE(engine.CoverageSnapshot(next).counters.back() == 0); REQUIRE(engine.Disarm(next)); @@ -71,13 +73,14 @@ TEST_CASE("Coverage transitions overlap and have isolated registration history", TEST_CASE("Coverage rejects malformed descriptors without changing the watcher", "[xtrigger][coverage]") { XClock clock([](bool) { return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); auto h = engine.ArmEdge(XPhase::RisingStable); - XCoverageItem item; + XCoveragePoint point; XCoverageBin bin; XData value(8, XData::InOut); - item.signal = &value; + point.signal = &value; bin.program.root = 100000; - REQUIRE_THROWS(engine.AttachCoverage(h, {item}, {bin})); + REQUIRE_THROWS(engine.AttachCoverage(h, {point}, {bin})); REQUIRE(engine.RunUntil(2).hits.size() == 1); REQUIRE(engine.Disarm(h)); } @@ -86,16 +89,17 @@ TEST_CASE("Coverage rejects malformed descriptors without changing the watcher", TEST_CASE("Coverage snapshots preserve full-width illegal values", "[xtrigger][coverage]") { XClock clock([](bool) { return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); XData value(128, XData::InOut); value = "0x80000000000000000000000000000007"; auto bytes = value.GetBytes(); - XCoverageItem item; - item.signal = &value; + XCoveragePoint point; + point.signal = &value; XCoverageBin bin; bin.kind = XCoverageBinKind::Illegal; bin.program.root = engine.ExprNewCompareSigConstBytes(static_cast(ExprOp::EQ), &value, bytes); auto handle = engine.ArmEdge(XPhase::RisingStable); - engine.AttachCoverage(handle, {item}, {bin}, -1, -1, false); + engine.AttachCoverage(handle, {point}, {bin}, -1, -1, false); engine.RunUntil(2); value = 0; const auto snapshot = engine.CoverageSnapshot(handle); @@ -114,13 +118,14 @@ TEST_CASE("Coverage snapshots preserve full-width illegal values", "[xtrigger][c TEST_CASE("Coverage overlapping sequences count simultaneous completions", "[xtrigger][coverage]") { XClock clock([](bool) { return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); XData start(1, XData::InOut), done(1, XData::InOut), value(8, XData::InOut); const int start_root = engine.ExprNewSignal(&start), done_root = engine.ExprNewSignal(&done); auto h = engine.ArmSequence({{XSequenceStepKind::Wait, start_root}, {XSequenceStepKind::Within, done_root, 1, 3}}); - XCoverageItem item; item.signal = &value; + XCoveragePoint point; point.signal = &value; XCoverageBin bin; bin.program.root = engine.ExprNewConst(1); - engine.AttachCoverage(h, {item}, {bin}, -1, -1, true, 1024, true, 2, true); + engine.AttachCoverage(h, {point}, {bin}, -1, -1, true, 1024, true, 2, true); start = 1; REQUIRE(engine.RunUntil(4).hits.empty()); auto pending = engine.CoverageSnapshot(h, true); @@ -144,13 +149,14 @@ TEST_CASE("Coverage overlapping sequences count simultaneous completions", "[xtr TEST_CASE("Coverage overlap capacity failure preserves pending state for cleanup", "[xtrigger][coverage]") { XClock clock([](bool) { return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); XData start(1, XData::InOut), done(1, XData::InOut), value(8, XData::InOut); auto h = engine.ArmSequence({{XSequenceStepKind::Wait, engine.ExprNewSignal(&start)}, {XSequenceStepKind::Wait, engine.ExprNewSignal(&done)}}); - XCoverageItem item; item.signal = &value; + XCoveragePoint point; point.signal = &value; XCoverageBin bin; bin.program.root = engine.ExprNewConst(1); - REQUIRE_THROWS(engine.AttachCoverage(h, {item}, {bin}, -1, -1, true, 1024, true, 0, true)); - engine.AttachCoverage(h, {item}, {bin}, -1, -1, true, 1024, true, 1, false); + REQUIRE_THROWS(engine.AttachCoverage(h, {point}, {bin}, -1, -1, true, 1024, true, 0, true)); + engine.AttachCoverage(h, {point}, {bin}, -1, -1, true, 1024, true, 1, false); start = 1; REQUIRE_THROWS(engine.RunUntil(4)); auto snapshot = engine.CoverageSnapshot(h, true); @@ -167,16 +173,17 @@ TEST_CASE("Coverage overlap capacity failure preserves pending state for cleanup TEST_CASE("Coverage bins run full independent temporal programs", "[xtrigger][coverage]") { XClock clock([](bool) { return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); XData start(1, XData::InOut), done(1, XData::InOut), stable(1, XData::InOut); const auto req = engine.ExprNewSignal(&start), ack = engine.ExprNewSignal(&done); const auto ready = engine.ExprNewSignal(&stable); auto handle = engine.ArmSample(XPhase::RisingStable); - XCoverageItem item; item.kind = XCoverageItemKind::Pattern; + XCoveragePoint point; point.kind = XCoveragePointKind::Pattern; XCoverageBin long_bin; long_bin.program.kind = XTriggerProgramKind::Sequence; long_bin.program.overlap = false; long_bin.program.steps = {{XSequenceStepKind::Wait, req}, {XSequenceStepKind::Within, ack, 1, 4}, {XSequenceStepKind::Hold, ready, 0, 0, 2}}; auto short_bin = long_bin; short_bin.program.steps[1].maximum = 1; - engine.AttachCoverage(handle, {item}, {long_bin, short_bin}); + engine.AttachCoverage(handle, {point}, {long_bin, short_bin}); start = 1; REQUIRE(engine.RunUntil(2).hits.empty()); start = 0; REQUIRE(engine.RunUntil(2).hits.empty()); done = 1; REQUIRE(engine.RunUntil(2).hits.empty()); @@ -192,15 +199,16 @@ TEST_CASE("Coverage bins run full independent temporal programs", "[xtrigger][co TEST_CASE("Coverage FSM terminal filtering ends every terminal attempt", "[xtrigger][coverage]") { XClock clock([](bool) { return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); XData start(1, XData::InOut), success(1, XData::InOut), failure(1, XData::InOut); - XCoverageItem item; item.kind = XCoverageItemKind::Pattern; + XCoveragePoint point; point.kind = XCoveragePointKind::Pattern; XCoverageBin bin; bin.program.kind = XTriggerProgramKind::Fsm; bin.program.overlap = false; bin.program.state_count = 2; bin.program.transitions = {{0, engine.ExprNewSignal(&start), 1, 0, false}, {1, engine.ExprNewSignal(&success), 0, 7, true}, {1, engine.ExprNewSignal(&failure), 0, 8, true}}; bin.terminals = {7}; auto h = engine.ArmSample(XPhase::RisingStable); - engine.AttachCoverage(h, {item}, {bin}, -1, -1, true, 1024, false, 1, true); + engine.AttachCoverage(h, {point}, {bin}, -1, -1, true, 1024, false, 1, true); start = 1; engine.RunUntil(2); start = 0; failure = 1; engine.RunUntil(2); auto failed = engine.CoverageSnapshot(h, true); @@ -218,13 +226,14 @@ TEST_CASE("Coverage FSM terminal filtering ends every terminal attempt", "[xtrig TEST_CASE("Coverage pattern overlap preserves simultaneous completion counts", "[xtrigger][coverage]") { XClock clock([](bool) { return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); XData start(1, XData::InOut), done(1, XData::InOut); auto h = engine.ArmSample(XPhase::RisingStable); - XCoverageItem item; item.kind = XCoverageItemKind::Pattern; + XCoveragePoint point; point.kind = XCoveragePointKind::Pattern; XCoverageBin bin; bin.program.kind = XTriggerProgramKind::Sequence; bin.program.overlap = true; bin.program.max_active = 3; bin.program.steps = {{XSequenceStepKind::Wait, engine.ExprNewSignal(&start)}, {XSequenceStepKind::Within, engine.ExprNewSignal(&done), 1, 5}}; - engine.AttachCoverage(h, {item}, {bin}); + engine.AttachCoverage(h, {point}, {bin}); start = 1; REQUIRE(engine.RunUntil(6).hits.empty()); start = 0; done = 1; REQUIRE(engine.RunUntil(2).hits.empty()); REQUIRE(engine.CoverageSnapshot(h).counters.back() == 3); @@ -234,17 +243,18 @@ TEST_CASE("Coverage pattern overlap preserves simultaneous completion counts", " TEST_CASE("Coverage rejects malformed pattern descriptors atomically", "[xtrigger][coverage]") { XClock clock([](bool) { return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); auto h = engine.ArmSample(XPhase::RisingStable); - XCoverageItem item; item.kind = XCoverageItemKind::Pattern; + XCoveragePoint point; point.kind = XCoveragePointKind::Pattern; XCoverageBin bin; bin.program.overlap = false; bin.program.root = engine.ExprNewConst(1); auto invalid = bin; invalid.program.mode = static_cast(99); - REQUIRE_THROWS(engine.AttachCoverage(h, {item}, {invalid})); + REQUIRE_THROWS(engine.AttachCoverage(h, {point}, {invalid})); invalid = bin; invalid.program.max_active = 0; - REQUIRE_THROWS(engine.AttachCoverage(h, {item}, {invalid})); + REQUIRE_THROWS(engine.AttachCoverage(h, {point}, {invalid})); invalid = bin; invalid.program.kind = XTriggerProgramKind::Fsm; invalid.program.state_count = 1; invalid.program.root = -1; invalid.terminals = {99}; - REQUIRE_THROWS(engine.AttachCoverage(h, {item}, {invalid})); - engine.AttachCoverage(h, {item}, {bin}); + REQUIRE_THROWS(engine.AttachCoverage(h, {point}, {invalid})); + engine.AttachCoverage(h, {point}, {bin}); engine.RunUntil(4); REQUIRE(engine.CoverageSnapshot(h).counters.back() == 1); // ENTER, not EACH_SAMPLE. REQUIRE(engine.Disarm(h)); @@ -253,18 +263,19 @@ TEST_CASE("Coverage rejects malformed pattern descriptors atomically", "[xtrigge TEST_CASE("Coverage shares an FSM and routes every terminal to its bins", "[xtrigger][coverage]") { XClock clock([](bool) { return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); XData request(1, XData::InOut), response(1, XData::InOut), correct(1, XData::InOut); const auto req = engine.ExprNewSignal(&request), rsp = engine.ExprNewSignal(&response); const auto good = engine.ExprNewSignal(&correct); const auto ok = engine.ExprNewBinary(static_cast(ExprOp::LAND), rsp, good); - XCoverageItem item; item.kind = XCoverageItemKind::Pattern; + XCoveragePoint point; point.kind = XCoveragePointKind::Pattern; XCoverageBin first; first.program.kind = XTriggerProgramKind::Fsm; first.program.state_count = 2; first.program.overlap = false; first.program.transitions = {{0, req, 1, 0, false}, {1, ok, 0, 7, true}, {1, rsp, 0, 8, true}}; first.terminals = {7}; auto second = first; second.terminals = {8}; second.kind = XCoverageBinKind::Illegal; auto all = first; all.terminals.clear(); const auto handle = engine.ArmSample(XPhase::RisingStable); - engine.AttachCoverage(handle, {item}, {first, second, all}, -1, -1, false, 1024, false, 1, true); + engine.AttachCoverage(handle, {point}, {first, second, all}, -1, -1, false, 1024, false, 1, true); REQUIRE(engine.ActiveCount() == 1); REQUIRE(engine.CoverageExecutionCount(handle) == 1); request = 1; engine.RunUntil(2); @@ -294,9 +305,10 @@ TEST_CASE("Coverage shares an FSM and routes every terminal to its bins", "[xtri TEST_CASE("Shared coverage FSM retains simultaneous counts for different terminals", "[xtrigger][coverage]") { XClock clock([](bool) { return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); XData request(1, XData::InOut), response(1, XData::InOut); const auto req = engine.ExprNewSignal(&request), rsp = engine.ExprNewSignal(&response); - XCoverageItem item; item.kind = XCoverageItemKind::Pattern; + XCoveragePoint point; point.kind = XCoveragePointKind::Pattern; XCoverageBin fast; fast.program.kind = XTriggerProgramKind::Fsm; fast.program.state_count = 3; fast.program.overlap = true; fast.program.max_active = 3; fast.program.transitions = {{0, req, 1, 0, false}, {1, rsp, 0, 7, true}, @@ -305,7 +317,7 @@ TEST_CASE("Shared coverage FSM retains simultaneous counts for different termina auto slow = fast; slow.terminals = {8}; auto all = fast; all.terminals.clear(); const auto handle = engine.ArmSample(XPhase::RisingStable); - engine.AttachCoverage(handle, {item}, {fast, slow, all}, -1, -1, true, 1024, false, 1, true); + engine.AttachCoverage(handle, {point}, {fast, slow, all}, -1, -1, true, 1024, false, 1, true); REQUIRE(engine.CoverageExecutionCount(handle) == 1); request = 1; REQUIRE(engine.RunUntil(6).hits.empty()); request = 0; response = 1; REQUIRE(engine.RunUntil(2).hits.empty()); @@ -324,17 +336,18 @@ TEST_CASE("Shared coverage FSM retains simultaneous counts for different termina TEST_CASE("Coverage shares only identical execution policies in the same point", "[xtrigger][coverage]") { XClock clock([](bool) { return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); XData request(1, XData::InOut), response(1, XData::InOut); const auto req = engine.ExprNewSignal(&request), rsp = engine.ExprNewSignal(&response); - XCoverageItem item; item.kind = XCoverageItemKind::Pattern; + XCoveragePoint point; point.kind = XCoveragePointKind::Pattern; XCoverageBin original; original.program.kind = XTriggerProgramKind::Sequence; original.program.overlap = false; original.program.steps = {{XSequenceStepKind::Wait, req}, {XSequenceStepKind::Within, rsp, 1, 4}}; auto alias = original; - auto other_point = original; other_point.item = 1; + auto other_point = original; other_point.point_id = 1; auto short_window = original; short_window.program.steps[1].maximum = 2; auto overlapping = original; overlapping.program.overlap = true; overlapping.program.max_active = 3; auto h = engine.ArmSample(XPhase::RisingStable); - engine.AttachCoverage(h, {item, item}, {original, alias, other_point, short_window, overlapping}); + engine.AttachCoverage(h, {point, point}, {original, alias, other_point, short_window, overlapping}); REQUIRE(engine.CoverageExecutionCount(h) == 4); request = 1; engine.RunUntil(2); engine.ResetCoverage(h, false); @@ -347,25 +360,26 @@ TEST_CASE("Coverage shares only identical execution policies in the same point", TEST_CASE("Explicit coverage kinds reject mixed definitions", "[coverage][validation]") { XClock clock([](bool) { return 0; }); XEngine engine(clock); + engine.AcquireClock(); XData signal(8, XData::InOut); const auto root = engine.ExprNewConst(1); auto handle = engine.ArmSample(XPhase::RisingStable); - XCoverageItem item; + XCoveragePoint point; XCoverageBin bin; bin.program.root = root; - REQUIRE_THROWS_AS(engine.AttachCoverage(handle, {item}, {bin}), std::invalid_argument); - item.kind = XCoverageItemKind::Pattern; - item.signal = &signal; - REQUIRE_THROWS_AS(engine.AttachCoverage(handle, {item}, {bin}), std::invalid_argument); - item.signal = nullptr; + REQUIRE_THROWS_AS(engine.AttachCoverage(handle, {point}, {bin}), std::invalid_argument); + point.kind = XCoveragePointKind::Pattern; + point.signal = &signal; + REQUIRE_THROWS_AS(engine.AttachCoverage(handle, {point}, {bin}), std::invalid_argument); + point.signal = nullptr; bin.program.overlap = false; bin.program.transitions = {{0, root, 0, 0, true}}; - REQUIRE_THROWS_AS(engine.AttachCoverage(handle, {item}, {bin}), std::invalid_argument); + REQUIRE_THROWS_AS(engine.AttachCoverage(handle, {point}, {bin}), std::invalid_argument); bin.program.transitions.clear(); bin.program.kind = static_cast(99); - REQUIRE_THROWS_AS(engine.AttachCoverage(handle, {item}, {bin}), std::invalid_argument); + REQUIRE_THROWS_AS(engine.AttachCoverage(handle, {point}, {bin}), std::invalid_argument); bin.program.kind = XTriggerProgramKind::Expr; - engine.AttachCoverage(handle, {item}, {bin}); + engine.AttachCoverage(handle, {point}, {bin}); engine.RunUntil(2); REQUIRE(engine.CoverageSnapshot(handle).counters.back() == 1); } @@ -373,15 +387,16 @@ TEST_CASE("Explicit coverage kinds reject mixed definitions", "[coverage][valida TEST_CASE("Trigger and coverage share program validation", "[coverage][trigger][validation]") { XClock clock([](bool) { return 0; }); XEngine engine(clock); + engine.AcquireClock(); const auto root = engine.ExprNewConst(1); const std::vector invalid{{static_cast(99), root}}; REQUIRE_FALSE(engine.ArmSequence(invalid).IsValid()); - XCoverageItem item; - item.kind = XCoverageItemKind::Pattern; + XCoveragePoint point; + point.kind = XCoveragePointKind::Pattern; XCoverageBin bin; bin.program.kind = XTriggerProgramKind::Sequence; bin.program.steps = invalid; bin.program.overlap = false; const auto handle = engine.ArmSample(XPhase::RisingStable); - REQUIRE_THROWS_AS(engine.AttachCoverage(handle, {item}, {bin}), std::invalid_argument); + REQUIRE_THROWS_AS(engine.AttachCoverage(handle, {point}, {bin}), std::invalid_argument); } diff --git a/tests/unit/test_xtrigger.cpp b/tests/unit/test_xtrigger.cpp index 1df08f4..004938a 100644 --- a/tests/unit/test_xtrigger.cpp +++ b/tests/unit/test_xtrigger.cpp @@ -9,6 +9,7 @@ using namespace xspcomm; TEST_CASE("XTriggerEngine broadcasts one edge occurrence", "[xtrigger]") { XClock clock([](bool){ return 0; }); XTriggerEngine engine(clock, 8); + engine.AcquireClock(); const auto first = engine.ArmEdge(XPhase::FallingStable); const auto second = engine.ArmEdge(XPhase::FallingStable); @@ -26,6 +27,7 @@ TEST_CASE("XTriggerEngine broadcasts one edge occurrence", "[xtrigger]") { TEST_CASE("XTriggerEngine counts selected phases", "[xtrigger]") { XClock clock([](bool){ return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); const auto handle = engine.ArmClockCycles(3); const auto result = engine.RunUntil(20); @@ -44,6 +46,7 @@ TEST_CASE("XTriggerEngine evaluates value equality at a stable phase", XData ready(1, XData::InOut); ready = 1; XTriggerEngine engine(clock); + engine.AcquireClock(); const auto handle = engine.ArmValueEq( &ready, 1, XPhase::RisingStable); @@ -60,6 +63,7 @@ TEST_CASE("XTriggerEngine evaluates value equality at a stable phase", TEST_CASE("XTriggerEngine rejects stale generations", "[xtrigger]") { XClock clock([](bool){ return 0; }); XTriggerEngine engine(clock, 1); + engine.AcquireClock(); const auto old_handle = engine.ArmEdge(XPhase::FallingStable); REQUIRE(engine.Disarm(old_handle)); @@ -74,6 +78,7 @@ TEST_CASE("XTriggerEngine runtime reset preserves stale handle safety", "[xtrigger]") { XClock clock([](bool) { return 0; }); XTriggerEngine engine(clock, 1); + engine.AcquireClock(); auto stale = engine.ArmEdge(XPhase::RisingStable); REQUIRE(engine.Disarm(stale)); @@ -95,6 +100,7 @@ TEST_CASE("XTriggerEngine evaluates ExprEngine roots", "[xtrigger]") { flush = 0; XClock clock([](bool){ return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); const int valid_node = engine.ExprNewSignal(&valid); const int ready_node = engine.ExprNewSignal(&ready); @@ -130,6 +136,7 @@ TEST_CASE("XTriggerEngine runs non-overlapping sequence state", "[xtrigger]") { return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); const int req_root = engine.ExprNewSignal(&req); const int ack_root = engine.ExprNewSignal(&ack); const int valid_root = engine.ExprNewSignal(&valid); @@ -153,6 +160,7 @@ TEST_CASE("XTriggerEngine rearms Python sampling without changing handle", "[xtrigger]") { XClock clock([](bool){ return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); const auto handle = engine.ArmSample(XPhase::RisingStable); const auto first = engine.RunUntil(8); @@ -171,6 +179,7 @@ TEST_CASE("XTriggerEngine rearms Python sampling without changing handle", TEST_CASE("XTriggerEngine rearms periodic cycle watchers", "[xtrigger]") { XClock clock([](bool){ return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); const auto handle = engine.ArmClockCycles(2); const auto first = engine.RunUntil(8); @@ -197,6 +206,7 @@ TEST_CASE("XTriggerEngine runs structured branching FSM", "[xtrigger]") { return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); const int req_root = engine.ExprNewSignal(&req); const int ack_root = engine.ExprNewSignal(&ack); const std::vector transitions = { @@ -223,6 +233,7 @@ TEST_CASE("XTriggerEngine samples value changes at selected phase", return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); const auto handle = engine.ArmValueChange( &data, XPhase::RisingStable); @@ -242,6 +253,7 @@ TEST_CASE("XTriggerEngine preserves four-state value changes", "[xtrigger]") { return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); const auto handle = engine.ArmValueChange( &data, XPhase::RisingStable); @@ -260,6 +272,7 @@ TEST_CASE("XTriggerEngine does not match unknown equality", "[xtrigger]") { return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); const auto handle = engine.ArmValueEq( &data, 1, XPhase::RisingStable); @@ -278,6 +291,7 @@ TEST_CASE("XTriggerEngine supports signals wider than uint64", "[xtrigger]") { data.SetVU8(expected); XClock clock([](bool){ return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); const auto value = engine.ArmValueEqBytes( &data, expected, XPhase::RisingStable, XConditionMode::Enter, 0); @@ -325,6 +339,7 @@ TEST_CASE("XTriggerEngine condition enter state survives rearm", "[xtrigger]") { data = 1; XClock clock([](bool){ return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); const auto handle = engine.ArmValueEq( &data, 1, XPhase::RisingStable, XConditionMode::Enter); @@ -342,6 +357,7 @@ TEST_CASE("XTriggerEngine condition change emits both transitions", "[xtrigger]" XData data(1, XData::InOut); XClock clock([](bool){ return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); const auto handle = engine.ArmValueEq( &data, 1, XPhase::RisingStable, XConditionMode::Change); @@ -361,6 +377,7 @@ TEST_CASE("XTriggerEngine condition change emits both transitions", "[xtrigger]" TEST_CASE("XTriggerEngine yields on wall clock quantum", "[xtrigger]") { XClock clock([](bool){ return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); const auto handle = engine.ArmClockCycles(1000); const auto result = engine.RunUntil(1000, 1, 1); @@ -377,6 +394,7 @@ TEST_CASE("XTriggerEngine samples DriveStable without advancing time", XData valid(1, XData::InOut); XClock clock([](bool){ return 0; }); XTriggerEngine engine(clock); + engine.AcquireClock(); const int root = engine.ExprNewSignal(&valid); const auto handle = engine.ArmExpr( root, XPhase::DriveStable, XConditionMode::EachSample); @@ -397,3 +415,121 @@ TEST_CASE("XTriggerEngine samples DriveStable without advancing time", REQUIRE(sampled.hits[0].tick == 1); REQUIRE(engine.Disarm(handle)); } + + +TEST_CASE("XEngine explicitly leases a clock until release", "[xtrigger][ownership]") { + XClock clock([](bool) { return 0; }); + XEngine first(clock), second(clock); + REQUIRE_FALSE(clock.IsControlled()); + clock.Step(); // Construction leaves initialization under manual control. + REQUIRE_THROWS_AS(first.ArmEdge(XPhase::RisingStable), std::logic_error); + REQUIRE_THROWS_AS(first.RunUntil(2), std::logic_error); + + first.AcquireClock(); + first.AcquireClock(); // Idempotent for the same engine. + REQUIRE(clock.IsControlled()); + REQUIRE_THROWS_AS(second.AcquireClock(), std::logic_error); + second.ReleaseClock(); // Never releases another engine's lease. + REQUIRE(clock.IsControlled()); + XClock idle([](bool) { return 0; }); + REQUIRE_THROWS_AS(XClock(clock), std::logic_error); + REQUIRE_THROWS_AS(idle = clock, std::logic_error); + REQUIRE_THROWS_AS(clock = idle, std::logic_error); + const auto tick = clock.GetHalfTick(); + REQUIRE_THROWS_AS(clock.Step(), std::logic_error); + REQUIRE_THROWS_AS(clock.StepHalf(), std::logic_error); + REQUIRE_THROWS_AS(clock.RunStep(), std::logic_error); + REQUIRE_THROWS_AS(clock.Reset(), std::logic_error); + REQUIRE_THROWS_AS(clock.ReInit([](bool) { return 0; }), std::logic_error); + REQUIRE_THROWS_AS(clock.default_stop_on_rise(false), std::logic_error); + REQUIRE(clock.GetHalfTick() == tick); + + const auto handle = first.ArmEdge(XPhase::FallingStable); + REQUIRE(first.RunUntil(8).hits.size() == 1); + REQUIRE_THROWS_AS(first.ReleaseClock(), std::logic_error); // Fired is still registered. + REQUIRE(first.Disarm(handle)); + REQUIRE_THROWS_AS(clock.StepHalf(), std::logic_error); // Still leased between batches. + first.ClearExecutionState(); + first.ReleaseClock(); + REQUIRE_FALSE(clock.IsControlled()); + REQUIRE(clock.GetPhase() == XPhase::FallingStable); // Release never advances time. + REQUIRE_FALSE(clock.IsAtCycleBoundary()); + REQUIRE(clock.StepHalf()); + REQUIRE(clock.IsAtCycleBoundary()); + second.AcquireClock(); + REQUIRE(second.RunUntil(2).advanced_ticks == 2); + second.ReleaseClock(); + clock.Step(); +} + +TEST_CASE("XEngine releases ownership on destruction and checks clock lifetime", + "[xtrigger][ownership]") { + XClock clock([](bool) { return 0; }); + { + XEngine engine(clock); + engine.AcquireClock(); + engine.ArmEdge(XPhase::RisingStable); + } + REQUIRE_FALSE(clock.IsControlled()); + clock.Step(); + + auto temporary = std::make_unique([](bool) { return 0; }); + XEngine engine(*temporary); + engine.AcquireClock(); + temporary.reset(); + REQUIRE_THROWS_AS(engine.RunUntil(2), std::logic_error); + REQUIRE_THROWS_AS(engine.AcquireClock(), std::logic_error); + REQUIRE_THROWS_AS(engine.ArmEdge(XPhase::RisingStable), std::logic_error); + engine.Clear(); + REQUIRE_NOTHROW(engine.ReleaseClock()); +} + +TEST_CASE("XEngine rejects callback reentry and recovers from exceptions", + "[xtrigger][ownership]") { + XClock clock([](bool) { return 0; }); + XEngine engine(clock); + clock.StepFal([&](uint64_t, void *) { + REQUIRE_THROWS_AS(engine.AcquireClock(), std::logic_error); + REQUIRE_THROWS_AS(engine.RunUntil(2), std::logic_error); + }); + clock.Step(); + clock.ClearFalCallBacks(); + engine.AcquireClock(); + unsigned callbacks = 0; + clock.StepFal([&](uint64_t, void *) { + ++callbacks; + REQUIRE_THROWS_AS(clock.Step(), std::logic_error); + REQUIRE_THROWS_AS(engine.RunUntil(2), std::logic_error); + REQUIRE_THROWS_AS(engine.SamplePhase(XPhase::DriveStable), std::logic_error); + REQUIRE_THROWS_AS(engine.Clear(), std::logic_error); + REQUIRE_THROWS_AS(engine.ClearExecutionState(), std::logic_error); + REQUIRE_THROWS_AS(engine.ReleaseClock(), std::logic_error); + }); + REQUIRE(engine.RunUntil(2).advanced_ticks == 2); + REQUIRE(callbacks == 1); + clock.ClearFalCallBacks(); + clock.StepFal([](uint64_t, void *) { throw std::runtime_error("callback failed"); }); + REQUIRE_THROWS_WITH(engine.RunUntil(2), "callback failed"); + clock.ClearFalCallBacks(); + REQUIRE(engine.RunUntil(2).advanced_ticks == 2); + REQUIRE_NOTHROW(engine.ReleaseClock()); +} + + +TEST_CASE("XEngine cannot take over a manual step already in progress", "[xtrigger][ownership]") { + XEngine *engine = nullptr; + XClock clock([&](bool) { + REQUIRE_THROWS_AS(engine->AcquireClock(), std::logic_error); + return 0; + }); + XEngine runtime(clock); + engine = &runtime; + clock.Step(); + REQUIRE_FALSE(clock.IsControlled()); + REQUIRE(clock.GetHalfTick() == 2); + clock.StepHalf(); + REQUIRE_FALSE(clock.IsControlled()); + REQUIRE(clock.GetHalfTick() == 3); + runtime.AcquireClock(); + runtime.ReleaseClock(); +} From c39db6652cd08d46df05d08f0bb5a033b83e4d2f Mon Sep 17 00:00:00 2001 From: Makiras <22570332+Makiras@users.noreply.github.com> Date: Mon, 12 Oct 2026 15:07:09 +0800 Subject: [PATCH 10/10] feat(expr): collect live programs and evaluate typed bit vectors Retain active trigger and coverage roots during expression collection, preserving matcher state and counters. Add fixed-width signed arithmetic and casts to ExprEngine using existing XData and comparison helpers. Reuse typed evaluation buffers and evaluate shared DAG nodes once per call. Validate with 11 native tests, 1104 xreactor tests, a rebuilt Picker Adder DUT, and 5000 warm evaluations with zero heap allocations. --- cmake/Sources.cmake | 1 + docs/architecture.md | 48 ++++- include/xspcomm/xengine.h | 10 + include/xspcomm/xexpr.h | 19 ++ src/coverage/state.cpp | 25 +++ src/coverage/state.h | 2 + src/expr/bits.cpp | 275 +++++++++++++++++++++++++++ src/expr/engine.cpp | 61 ++++++ src/runtime/engine.cpp | 35 ++++ src/trigger/matcher.h | 3 + swig/xcomm.i | 1 + tests/CMakeLists.txt | 2 +- tests/unit/test_expr_allocations.cpp | 53 ++++++ tests/unit/test_xcoverage.cpp | 33 ++++ tests/unit/test_xexpr.cpp | 105 ++++++++++ tests/unit/test_xtrigger.cpp | 32 ++++ 16 files changed, 702 insertions(+), 3 deletions(-) create mode 100644 src/expr/bits.cpp create mode 100644 tests/unit/test_expr_allocations.cpp diff --git a/cmake/Sources.cmake b/cmake/Sources.cmake index ecc6714..5b66a11 100644 --- a/cmake/Sources.cmake +++ b/cmake/Sources.cmake @@ -18,6 +18,7 @@ set(XSPCOMM_SOURCES core/util.cpp coverage/state.cpp expr/engine.cpp + expr/bits.cpp expr/parser.cpp trigger/executor.cpp coverage/registration.cpp diff --git a/docs/architecture.md b/docs/architecture.md index 8f7ac4f..c51a405 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -138,8 +138,52 @@ history; abort clears source and bin history. Reset can preserve counters while clearing execution history. Pattern ignore/illegal bins affect their own completion results; value points retain their classification priority rules. Crosses containing pattern points are rejected until completion correlation -semantics are defined. Program reclamation during a long execution and complete -expression width/signed/cast/four-state semantics remain separate work. +semantics are defined. + +`CollectExpressions(retained_roots)` collects the existing expression DAG at an +explicit safe point outside a running engine. It automatically retains all occupied registrations (also +hit registrations awaiting rearm) and attached coverage conditions, gates, +aborts and programs. It returns an old-to-new root mapping and updates native +watcher/coverage references without changing matcher histories or counters. +Direct C++ callers must pass their own retained roots and replace every stored +root with its mapping; old integer roots are invalid after collection. Unused +wide constants/masks are released; borrowed DUT XData objects are not owned. +Node/constant count queries support lifecycle tests. Coverage descriptors stay +at protocol 5. This unshipped branch continues its ABI 3 transition; rebuild +native clients against these headers because ExprEngine now owns evaluation +scratch storage. + +Typed expression nodes extend the same ExprEngine and reuse XData byte storage. +Wide comparison reuses the common word readers and comparison helpers, +including the existing signed byte comparison for byte-aligned operands. +`src/expr/bits.cpp` implements the typed operations within ExprEngine; it does +not create another trigger, coverage or range execution system. +They carry width and signedness, support casts, fixed-width arithmetic, bitwise +operations, comparisons and saturating shift counts; a signed right shift fills +with the sign bit. Signed division truncates toward zero, remainder follows the +dividend, and division by zero raises. `EvalBits` returns unsigned little-endian +result bits; `Eval` retains its uint64 result contract. A wide Bits predicate is +explicitly converted to a Bool root by the Python compiler. Existing untyped +scalar expression operations retain their old semantics. Four-state expression +changes are deferred. + +Typed evaluation uses an explicit stack and reuses per-node result buffers and +division scratch. Shared DAG nodes are evaluated once per evaluation; results +are never cached across calls, including calls at the same cycle after inputs +change. XData leaves refresh once per visited node and then read the cached +native words. Warm `Eval` calls do not allocate for a fixed, already visited +program. `EvalBits` intentionally returns an owning byte vector and therefore +copies the requested result. New nodes, newly visited branches and collection +can require another workspace allocation. Collection and Clear release this +scratch along with unreachable programs. Predicates preserve logical short +circuiting, and a failed evaluation does not retain results for the next call. + +The existing `XRangeCheck` still handles directional unsigned tolerance checks, +including wide values. Unsigned interval bins continue to compile to existing +full-width comparisons. Typed operations are needed when intermediate results +carry width/sign/cast semantics; range checks alone do not require them. +The string-based `CompileExpr` API retains its existing scalar semantics; typed +programs are constructed with NewBits/ExprNewBits APIs. ## Clock driving ownership diff --git a/include/xspcomm/xengine.h b/include/xspcomm/xengine.h index d0fdc3c..1e27a6e 100644 --- a/include/xspcomm/xengine.h +++ b/include/xspcomm/xengine.h @@ -69,6 +69,16 @@ class XEngine { XRegistrationHandle ArmSample( XPhase phase = XPhase::RisingStable, uint64_t source_id = 0); int ExprNewConst(uint64_t value); + int ExprNewBitsConst(uint32_t width, bool is_signed, std::vector &value); + int ExprNewBool(bool value); + int ExprNewBitsSignal(XData *signal, bool is_signed); + int ExprNewBits(int op, int lhs, int rhs, uint32_t width, bool is_signed); + std::vector ExprEvalBits(int root); + size_t ExpressionNodeCount() const; + size_t ExpressionConstantCount() const; + // Retains occupied registrations and attached coverage automatically. + // Additional client/cache roots are remapped by the returned table. + std::vector CollectExpressions(const std::vector &retained_roots); int ExprNewSignal(XData *signal); int ExprNewUnary(int op, int child); int ExprNewBinary(int op, int lhs, int rhs); diff --git a/include/xspcomm/xexpr.h b/include/xspcomm/xexpr.h index 182277f..ec1f974 100644 --- a/include/xspcomm/xexpr.h +++ b/include/xspcomm/xexpr.h @@ -38,6 +38,8 @@ namespace xspcomm { SELECT = 22, WITHIN = 23, HOLD = 24, + CAST = 25, + TYPED = 26, }; struct ExprNode{ @@ -70,6 +72,11 @@ namespace xspcomm { std::map external_signal_xdata; std::vector eval_stack; std::vector eval_vals; + struct BitsWorkspace; + std::unique_ptr bits_workspace; + const std::vector &EvalBitsValue(int root); + uint64_t EvalBitsScalar(int root); + void ClearBitsWorkspace(); uint64_t current_cycle = 0; uint64_t EvalNode(int id); bool IsKnownNode(int id); @@ -83,6 +90,8 @@ namespace xspcomm { void ReorderShortCircuit(int id, const std::vector &memo, const std::vector &stateful); public: + ExprEngine(); + ~ExprEngine(); bool ValidRoot(int root) const { return root >= 0 && static_cast(root) < nodes.size(); } int NewConst(uint64_t v); int NewSignal(XData* sig); @@ -103,6 +112,11 @@ namespace xspcomm { std::vector &mask); int NewWithin(int child, uint64_t window); int NewHold(int child, uint64_t window); + int NewBitsConst(uint32_t width, bool is_signed, std::vector &value); + int NewBool(bool value); + int NewBitsSignal(XData *signal, bool is_signed); + int NewBits(ExprOp op, int lhs, int rhs, uint32_t width, bool is_signed); + std::vector EvalBits(int root); XData* GetOrCreateSignal(XSignalCFG* cfg, const std::string &name); void RegisterExternalSignal(const std::string &name, XData* sig); void ResetState(); @@ -112,6 +126,11 @@ namespace xspcomm { void OptimizeShortCircuitOrder(int root); void SetCycle(uint64_t cycle){ this->current_cycle = cycle; } void Clear(); + size_t NodeCount() const { return nodes.size(); } + size_t ConstantCount() const { return const_xdata.size(); } + // Explicit safe-point collection. All retained roots must be remapped + // using the returned old-to-new table; -1 denotes an unreachable node. + std::vector Collect(const std::vector &roots); }; diff --git a/src/coverage/state.cpp b/src/coverage/state.cpp index 1c34501..1cf07dd 100644 --- a/src/coverage/state.cpp +++ b/src/coverage/state.cpp @@ -9,6 +9,31 @@ namespace xspcomm { namespace detail { +void CoverageState::ExpressionRoots(std::vector &roots) const +{ + roots.push_back(gate); roots.push_back(abort); + for (const auto &item : points) roots.push_back(item.gate); + for (const auto &bin : bins) { + roots.push_back(bin.program.root); + for (const auto &step : bin.program.steps) roots.push_back(step.root); + for (const auto &transition : bin.program.transitions) roots.push_back(transition.root); + } +} + +void CoverageState::RemapExpressions(const std::vector &mapping) +{ + auto remap = [&](int &root) { if (root >= 0) root = mapping[root]; }; + remap(gate); remap(abort); + for (auto &item : points) remap(item.gate); + for (auto &bin : bins) { + remap(bin.program.root); + for (auto &step : bin.program.steps) remap(step.root); + for (auto &transition : bin.program.transitions) remap(transition.root); + } + source_matcher.RemapExpressions(mapping); + for (auto &matcher : matchers) matcher.RemapExpressions(mapping); +} + static bool SameCoverageExecution(const XCoverageBin &a, const XCoverageBin &b) { if (std::tie(a.point_id, a.program.kind, a.program.root, a.program.mode, a.program.overlap, a.program.max_active, diff --git a/src/coverage/state.h b/src/coverage/state.h index 835f591..bf6f118 100644 --- a/src/coverage/state.h +++ b/src/coverage/state.h @@ -33,6 +33,8 @@ class CoverageState { void Sample(ExprEngine &expr, uint64_t tick); void SamplePattern(ExprEngine &expr, uint64_t tick); size_t ExecutionCount() const; + void ExpressionRoots(std::vector &roots) const; + void RemapExpressions(const std::vector &mapping); void ClearHistory(bool aborted = false); void ResetCounters(); XCoverageSnapshot Snapshot(uint64_t tick, bool progress) const; diff --git a/src/expr/bits.cpp b/src/expr/bits.cpp new file mode 100644 index 0000000..a7dcbdf --- /dev/null +++ b/src/expr/bits.cpp @@ -0,0 +1,275 @@ +#include "xspcomm/xexpr.h" +#include "xspcomm/common/compare.h" + +#include +#include +#include + +namespace xspcomm { +namespace { +using Bytes = std::vector; +constexpr uint64_t SignedFlag = uint64_t{1} << 32; +constexpr uint64_t BoolFlag = uint64_t{1} << 33; +ExprOp BitsOp(const ExprNode &node) { return static_cast(uint32_t(node.imm)); } +bool BitsSigned(const ExprNode &node) { return (node.imm & SignedFlag) != 0; } +bool IsBool(const ExprNode &node) { return (node.imm & BoolFlag) != 0; } + +void Normalize(Bytes &value, uint32_t width) { + value.resize((size_t(width) + 7) / 8, 0); + if (width % 8) value.back() &= (1u << (width % 8)) - 1; +} +bool Negative(const Bytes &value, uint32_t width) { + return (value[(width - 1) / 8] >> ((width - 1) % 8)) & 1; +} +bool Nonzero(const Bytes &value) { + return std::any_of(value.begin(), value.end(), [](auto byte) { return byte != 0; }); +} +int CompareBytes(const Bytes &a, const Bytes &b) { + const memory::ByteWords lhs{a.data(), a.size()}, rhs{b.data(), b.size()}; + return compare::CompareWords(lhs.Count(), lhs, rhs); +} +int CompareBits(const Bytes &a, const Bytes &b, uint32_t width, bool is_signed) { + if (is_signed) { + if (width % 8 == 0) return compare::CompareSignedBytes(a.data(), b.data(), a.size()); + const bool anegative = Negative(a, width), bnegative = Negative(b, width); + if (anegative != bnegative) return anegative ? -1 : 1; + } + return CompareBytes(a, b); +} +void ReadSignal(Bytes &result, XData &signal, uint32_t width) { + // Refresh once, then read the existing XData storage without GetVU8 temporaries. + const auto scalar = signal.U(); + result.resize((size_t(width) + 7) / 8); + if (signal.W() == 0) result[0] = static_cast(scalar); + else for (size_t i = 0; i < result.size(); ++i) + result[i] = static_cast(signal.pVecData[i / 4].aval >> (8 * (i % 4))); +} +// These byte operations permit result to alias either operand. +void AddBytes(Bytes &result, const Bytes &a, const Bytes &b, bool subtract = false) { + result.resize(a.size()); + unsigned carry = subtract; + for (size_t i = 0; i < a.size(); ++i) { + const unsigned sum = unsigned(a[i]) + (subtract ? unsigned(b[i] ^ 255) : b[i]) + carry; + result[i] = sum & 255; + carry = sum >> 8; + } +} +void NegateBytes(Bytes &result, const Bytes &a, uint32_t width) { + result.resize(a.size()); + unsigned carry = 1; + for (size_t i = 0; i < a.size(); ++i) { + const unsigned sum = unsigned(a[i] ^ 255) + carry; + result[i] = sum & 255; + carry = sum >> 8; + } + Normalize(result, width); +} +void ShiftBytes(Bytes &result, const Bytes &a, uint32_t width, uint32_t amount, bool left, bool sign) { + result.assign(a.size(), 0); + for (uint32_t bit = 0; bit < width; ++bit) { + const uint64_t source = left ? uint64_t(bit) - amount : uint64_t(bit) + amount; + const bool value = source < width ? ((a[source / 8] >> (source % 8)) & 1) : (!left && sign); + if (value) result[bit / 8] |= 1u << (bit % 8); + } +} +void DivideBytes(Bytes &result, Bytes &remainder, const Bytes &a, const Bytes &b, uint32_t width, bool modulo) { + if (!Nonzero(b)) throw std::domain_error("typed expression division by zero"); + result.assign(a.size(), 0); + remainder.assign(a.size(), 0); + for (uint32_t bit = width; bit-- > 0;) { + unsigned carry = (a[bit / 8] >> (bit % 8)) & 1; + for (auto &byte : remainder) { + const unsigned shifted = unsigned(byte) * 2 + carry; + byte = shifted & 255; + carry = shifted >> 8; + } + if (CompareBytes(remainder, b) >= 0) { + AddBytes(remainder, remainder, b, true); + result[bit / 8] |= 1u << (bit % 8); + } + } + if (modulo) result.assign(remainder.begin(), remainder.end()); +} +} // namespace + +// Execution scratch belongs to the existing ExprEngine. Values are valid only +// within one evaluation, so signal changes at the same clock tick remain visible. +struct ExprEngine::BitsWorkspace { + std::vector values; + std::vector visits; + std::vector stack; + std::array division; + uint64_t epoch = 0; +}; + +ExprEngine::ExprEngine() = default; +ExprEngine::~ExprEngine() = default; + +void ExprEngine::ClearBitsWorkspace() { bits_workspace.reset(); } + +const std::vector &ExprEngine::EvalBitsValue(int root) { + if (!ValidRoot(root) || nodes[root].op != ExprOp::TYPED) + throw std::invalid_argument("expected typed expression root"); + if (!bits_workspace) bits_workspace = std::make_unique(); + auto &work = *bits_workspace; + work.values.resize(nodes.size()); + work.visits.resize(nodes.size(), 0); + if (++work.epoch == 0) { + std::fill(work.visits.begin(), work.visits.end(), 0); + ++work.epoch; + } + work.stack.clear(); + work.stack.push_back({root, 0, 0}); + while (!work.stack.empty()) { + auto &frame = work.stack.back(); + const int id = frame.id; + if (work.visits[id] == work.epoch) { work.stack.pop_back(); continue; } + const auto &node = nodes[id]; + const auto op = BitsOp(node); + auto &result = work.values[id]; + auto complete = [&] { + Normalize(result, node.width); + work.visits[id] = work.epoch; + work.stack.pop_back(); + }; + if (op == ExprOp::CONST || op == ExprOp::SIGNAL) { + ReadSignal(result, *(op == ExprOp::CONST ? node.lhs_xdata : node.sig), node.width); + complete(); + continue; + } + if (frame.state == 0) { + frame.state = 1; + work.stack.push_back({node.lhs, 0, 0}); + continue; + } + const auto &a = work.values[node.lhs]; + const auto &left = nodes[node.lhs]; + const bool negative_a = BitsSigned(left) && Negative(a, left.width); + if ((op == ExprOp::LAND && !Nonzero(a)) || (op == ExprOp::LOR && Nonzero(a))) { + result.assign(1, op == ExprOp::LOR); + complete(); + continue; + } + if (frame.state == 1 && node.rhs >= 0) { + frame.state = 2; + work.stack.push_back({node.rhs, 0, 0}); + continue; + } + if (op == ExprOp::CAST) { + result.assign(a.begin(), a.end()); + Normalize(result, node.width); + if (negative_a && node.width > left.width) + for (uint32_t bit = left.width; bit < node.width; ++bit) result[bit / 8] |= 1u << (bit % 8); + } else if (op == ExprOp::LNOT) result.assign(1, !Nonzero(a)); + else if (op == ExprOp::BNOT) { + result.resize(a.size()); + for (size_t i = 0; i < a.size(); ++i) result[i] = a[i] ^ 255; + } else if (op == ExprOp::SUB && node.rhs < 0) NegateBytes(result, a, node.width); + else { + const auto &b = work.values[node.rhs]; + const auto &right = nodes[node.rhs]; + const bool negative_b = BitsSigned(right) && Negative(b, right.width); + switch (op) { + case ExprOp::ADD: AddBytes(result, a, b); break; + case ExprOp::SUB: AddBytes(result, a, b, true); break; + case ExprOp::MUL: + result.assign(a.size(), 0); + for (size_t i = 0; i < a.size(); ++i) { + unsigned carry = 0; + for (size_t j = 0; i + j < result.size(); ++j) { + const unsigned sum = unsigned(a[i]) * b[j] + result[i + j] + carry; + result[i + j] = sum & 255; + carry = sum >> 8; + } + } + break; + case ExprOp::DIV: case ExprOp::MOD: { + auto &magnitude_a = work.division[0], &magnitude_b = work.division[1]; + if (negative_a) NegateBytes(magnitude_a, a, left.width); + if (negative_b) NegateBytes(magnitude_b, b, right.width); + DivideBytes(result, work.division[2], negative_a ? magnitude_a : a, + negative_b ? magnitude_b : b, node.width, op == ExprOp::MOD); + if (op == ExprOp::DIV ? negative_a != negative_b : negative_a) + NegateBytes(result, result, node.width); + break; + } + case ExprOp::BAND: case ExprOp::BOR: case ExprOp::BXOR: + result.resize(a.size()); + for (size_t i = 0; i < a.size(); ++i) + result[i] = op == ExprOp::BAND ? a[i] & b[i] : op == ExprOp::BOR ? a[i] | b[i] : a[i] ^ b[i]; + break; + case ExprOp::SHL: case ExprOp::SHR: { + if (negative_b) throw std::domain_error("negative typed shift count"); + uint32_t amount = 0; + for (size_t i = b.size(); i-- > 0;) + amount = static_cast(std::min(left.width, uint64_t(amount) * 256 + b[i])); + ShiftBytes(result, a, left.width, amount, op == ExprOp::SHL, negative_a); + break; + } + case ExprOp::LAND: case ExprOp::LOR: result.assign(1, Nonzero(b)); break; + default: + result.assign(1, compare::Compare(op, CompareBits(a, b, left.width, BitsSigned(left)), 0)); + break; + } + } + complete(); + } + return work.values[root]; +} + +uint64_t ExprEngine::EvalBitsScalar(int root) { + const auto &bytes = EvalBitsValue(root); + uint64_t value = 0; + for (size_t i = 0; i < bytes.size() && i < 8; ++i) value |= uint64_t(bytes[i]) << (8 * i); + return value; +} + +std::vector ExprEngine::EvalBits(int root) { return EvalBitsValue(root); } + +int ExprEngine::NewBitsConst(uint32_t width, bool is_signed, std::vector &value) { + if (!width || value.size() != (size_t(width) + 7) / 8 || + (width % 8 && (value.back() >> (width % 8)))) + throw std::invalid_argument("invalid typed constant width/bytes"); + ExprNode node; node.op = ExprOp::TYPED; node.width = width; + node.imm = uint64_t(ExprOp::CONST) | (is_signed ? SignedFlag : 0); + node.lhs_xdata = MakeConstXDataBytes(width, value); + nodes.push_back(node); return static_cast(nodes.size()) - 1; +} +int ExprEngine::NewBitsSignal(XData *signal, bool is_signed) { + if (!signal) throw std::invalid_argument("null typed signal"); + ExprNode node; node.op = ExprOp::TYPED; node.width = std::max(1u, signal->W()); + node.sig = signal; node.imm = uint64_t(ExprOp::SIGNAL) | (is_signed ? SignedFlag : 0); + nodes.push_back(node); return static_cast(nodes.size()) - 1; +} +int ExprEngine::NewBool(bool value) { + Bytes bytes{static_cast(value)}; + const int root = NewBitsConst(1, false, bytes); + nodes[root].imm |= BoolFlag; + return root; +} +int ExprEngine::NewBits(ExprOp op, int lhs, int rhs, uint32_t width, bool is_signed) { + if (!width || !ValidRoot(lhs) || nodes[lhs].op != ExprOp::TYPED || + (rhs != -1 && (!ValidRoot(rhs) || nodes[rhs].op != ExprOp::TYPED))) + throw std::invalid_argument("invalid typed operands"); + const bool unary = op == ExprOp::CAST || op == ExprOp::BNOT || op == ExprOp::LNOT || (op == ExprOp::SUB && rhs == -1); + const bool logical = op == ExprOp::LAND || op == ExprOp::LOR || op == ExprOp::LNOT; + const bool comparison = op >= ExprOp::EQ && op <= ExprOp::LE; + const bool shift = op == ExprOp::SHL || op == ExprOp::SHR; + if ((!unary && rhs < 0) || (unary && rhs != -1) || + (!unary && !logical && !shift && + (nodes[lhs].width != nodes[rhs].width || BitsSigned(nodes[lhs]) != BitsSigned(nodes[rhs]) || + IsBool(nodes[lhs]) != IsBool(nodes[rhs]))) || + (!logical && op != ExprOp::CAST && + ((IsBool(nodes[lhs]) && (!comparison || (op != ExprOp::EQ && op != ExprOp::NE))) || + (rhs >= 0 && IsBool(nodes[rhs]) && !comparison))) || + ((logical || comparison) && (width != 1 || is_signed)) || + (!logical && !comparison && op != ExprOp::CAST && + (width != nodes[lhs].width || is_signed != BitsSigned(nodes[lhs]))) || + !(op == ExprOp::CAST || op == ExprOp::LNOT || op == ExprOp::BNOT || + (op >= ExprOp::ADD && op <= ExprOp::LE))) + throw std::invalid_argument("inconsistent typed expression types"); + ExprNode node; node.op = ExprOp::TYPED; node.width = width; node.lhs = lhs; node.rhs = rhs; + node.imm = uint64_t(op) | (is_signed ? SignedFlag : 0) | ((logical || comparison) ? BoolFlag : 0); + nodes.push_back(node); return static_cast(nodes.size()) - 1; +} +} // namespace xspcomm diff --git a/src/expr/engine.cpp b/src/expr/engine.cpp index 9562b90..eb5d8d4 100644 --- a/src/expr/engine.cpp +++ b/src/expr/engine.cpp @@ -5,8 +5,56 @@ #include #include #include +#include namespace xspcomm { +std::vector ExprEngine::Collect(const std::vector &roots) +{ + std::vector mapping(nodes.size(), -1), pending; + std::vector live(nodes.size(), false); + for (int root : roots) { + if (root == -1) continue; + if (!ValidRoot(root)) throw std::invalid_argument("invalid retained expression root"); + pending.push_back(root); + } + while (!pending.empty()) { + int id = pending.back(); pending.pop_back(); + if (live[id]) continue; + live[id] = true; + const auto &node = nodes[id]; + auto retain = [&](int child) { + if (child < 0) return; + if (!ValidRoot(child)) throw std::invalid_argument("invalid expression child"); + pending.push_back(child); + }; + retain(node.lhs); retain(node.rhs); + if (node.op == ExprOp::SELECT) retain(static_cast(node.imm)); + } + std::vector compact; + compact.reserve(std::count(live.begin(), live.end(), true)); + std::unordered_set constants; + for (size_t i = 0; i < nodes.size(); ++i) if (live[i]) { + mapping[i] = static_cast(compact.size()); + compact.push_back(nodes[i]); + constants.insert(nodes[i].lhs_xdata); + constants.insert(nodes[i].rhs_xdata); + constants.insert(nodes[i].mask_xdata); + } + for (auto &node : compact) { + if (node.lhs >= 0) node.lhs = mapping[node.lhs]; + if (node.rhs >= 0) node.rhs = mapping[node.rhs]; + if (node.op == ExprOp::SELECT) node.imm = mapping[node.imm]; + } + // All allocations and validation precede mutation. XData objects retained + // by live nodes keep their addresses; borrowed DUT signals are never freed. + nodes.swap(compact); + const_xdata.erase(std::remove_if(const_xdata.begin(), const_xdata.end(), + [&](const auto &data) { return !constants.count(data.get()); }), const_xdata.end()); + eval_stack.clear(); eval_vals.clear(); + ClearBitsWorkspace(); + return mapping; +} + int ExprEngine::NewConst(uint64_t v){ ExprNode n; n.op = ExprOp::CONST; @@ -168,6 +216,12 @@ bool ExprEngine::IsKnown(int root){ bool ExprEngine::IsKnownNode(int id){ if(id < 0 || id >= (int)this->nodes.size()) return false; const ExprNode &n = this->nodes[id]; + if(n.op == ExprOp::TYPED){ + auto op = static_cast(uint32_t(n.imm)); + if(op == ExprOp::CONST) return true; + if(op == ExprOp::SIGNAL){ n.sig->U(); return n.sig->DataValid(); } + return IsKnownNode(n.lhs) && (n.rhs < 0 || IsKnownNode(n.rhs)); + } if(n.op == ExprOp::CONST) return true; if(n.op == ExprOp::SIGNAL){ if(!n.sig) return false; @@ -192,6 +246,7 @@ bool ExprEngine::IsKnownNode(int id){ } void ExprEngine::Clear(){ + ClearBitsWorkspace(); this->nodes.clear(); this->const_xdata.clear(); this->signal_xdata.clear(); @@ -202,6 +257,8 @@ uint64_t ExprEngine::EvalNode(int id){ if(id < 0 || id >= (int)this->nodes.size()) return 0; const ExprNode &n = this->nodes[id]; switch(n.op){ + case ExprOp::TYPED: + return EvalBitsScalar(id); case ExprOp::CONST: return n.imm; case ExprOp::SIGNAL: @@ -323,6 +380,10 @@ uint64_t ExprEngine::EvalIterative(int root){ } ExprNode &n = this->nodes[f.id]; switch(n.op){ + case ExprOp::TYPED: + vals.push_back(EvalBitsScalar(f.id)); + stack.pop_back(); + break; case ExprOp::CONST: vals.push_back(n.imm); stack.pop_back(); diff --git a/src/runtime/engine.cpp b/src/runtime/engine.cpp index baf1798..742fce1 100644 --- a/src/runtime/engine.cpp +++ b/src/runtime/engine.cpp @@ -38,6 +38,30 @@ XEngine::XEngine(XClock *clock, size_t capacity) XEngine::XEngine(XClock &clock, size_t capacity) : XEngine(&clock, capacity) {} +size_t XEngine::ExpressionNodeCount() const { return expr_engine->NodeCount(); } +size_t XEngine::ExpressionConstantCount() const { return expr_engine->ConstantCount(); } + +std::vector XEngine::CollectExpressions(const std::vector &retained_roots) +{ + CheckNotRunning(); + std::vector roots = retained_roots; + for (const auto &watcher : watchers) if (watcher.occupied) { + roots.push_back(watcher.program.root); + for (const auto &step : watcher.program.steps) roots.push_back(step.root); + for (const auto &transition : watcher.program.transitions) roots.push_back(transition.root); + if (watcher.coverage) watcher.coverage->ExpressionRoots(roots); + } + auto mapping = expr_engine->Collect(roots); + auto remap = [&](int &root) { if (root >= 0) root = mapping[root]; }; + for (auto &watcher : watchers) if (watcher.occupied) { + remap(watcher.program.root); + for (auto &step : watcher.program.steps) remap(step.root); + for (auto &transition : watcher.program.transitions) remap(transition.root); + if (watcher.coverage) watcher.coverage->RemapExpressions(mapping); + } + return mapping; +} + XEngine::~XEngine() { if (!clock_lifetime.expired() && clock->drive_control.owner == this) { @@ -226,6 +250,17 @@ int XEngine::ExprNewConst(uint64_t value) return expr_engine->NewConst(value); } +int XEngine::ExprNewBitsConst(uint32_t width, bool is_signed, std::vector &value) +{ return expr_engine->NewBitsConst(width, is_signed, value); } +int XEngine::ExprNewBool(bool value) +{ return expr_engine->NewBool(value); } +int XEngine::ExprNewBitsSignal(XData *signal, bool is_signed) +{ return expr_engine->NewBitsSignal(signal, is_signed); } +int XEngine::ExprNewBits(int op, int lhs, int rhs, uint32_t width, bool is_signed) +{ return expr_engine->NewBits(static_cast(op), lhs, rhs, width, is_signed); } +std::vector XEngine::ExprEvalBits(int root) +{ return expr_engine->EvalBits(root); } + int XEngine::ExprNewSignal(XData *signal) { if (signal == nullptr || signal->W() > 64) return -1; diff --git a/src/trigger/matcher.h b/src/trigger/matcher.h index 9b37499..fd127ee 100644 --- a/src/trigger/matcher.h +++ b/src/trigger/matcher.h @@ -43,6 +43,9 @@ class PatternMatcher { } const std::vector &States() const { return attempts; } void Clear(); + void RemapExpressions(const std::vector &mapping) { + if (program.root >= 0) program.root = mapping[program.root]; + } private: PatternView program; diff --git a/swig/xcomm.i b/swig/xcomm.i index 0efa7fe..297a65c 100644 --- a/swig/xcomm.i +++ b/swig/xcomm.i @@ -53,6 +53,7 @@ %ignore xspcomm::XPort::SelectPins(std::initializer_list); namespace std { + %template(XIntVector) vector; %template(StringVector) vector; %template(XDataVector) vector>; %template(DictStrBool) map; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index aa3a6ed..fea665f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,5 +1,5 @@ if(NOT XSPCOMM_WASM_LIB) - foreach(component IN ITEMS xdata xclock xtrigger xcoverage xmonitor xexpr xvpi xfsm trigger_matcher) + foreach(component IN ITEMS xdata xclock xtrigger xcoverage xmonitor xexpr xvpi xfsm trigger_matcher expr_allocations) add_executable(test_${component} unit/test_${component}.cpp) target_include_directories(test_${component} PRIVATE ${PROJECT_SOURCE_DIR}/third_party/catch2) diff --git a/tests/unit/test_expr_allocations.cpp b/tests/unit/test_expr_allocations.cpp new file mode 100644 index 0000000..cc3a67a --- /dev/null +++ b/tests/unit/test_expr_allocations.cpp @@ -0,0 +1,53 @@ +#include "xspcomm/xexpr.h" + +#include +#include +#include +#include + +namespace { +thread_local bool measuring = false; +thread_local size_t allocations = 0; +} + +void *operator new(std::size_t size) { + if (void *p = std::malloc(size ? size : 1)) { + if (measuring) ++allocations; + return p; + } + throw std::bad_alloc(); +} +void *operator new[](std::size_t size) { return ::operator new(size); } +void operator delete(void *p) noexcept { std::free(p); } +void operator delete[](void *p) noexcept { std::free(p); } +void operator delete(void *p, std::size_t) noexcept { std::free(p); } +void operator delete[](void *p, std::size_t) noexcept { std::free(p); } + +int main() { + using namespace xspcomm; + ExprEngine engine; + XData signal(129, XData::InOut); + std::vector negative(17, 255), divisor(17, 255), amount{3}; + negative[0] = 249; negative.back() = 1; // signed -7 + divisor[0] = 253; divisor.back() = 1; // signed -3 + signal.SetVU8(negative); + const int a = engine.NewBitsSignal(&signal, true); + const int b = engine.NewBitsConst(129, true, divisor); + const int shift = engine.NewBitsConst(8, false, amount); + const std::array roots{ + engine.NewBits(ExprOp::ADD, a, b, 129, true), + engine.NewBits(ExprOp::MUL, a, b, 129, true), + engine.NewBits(ExprOp::DIV, a, b, 129, true), + engine.NewBits(ExprOp::MOD, a, b, 129, true), + engine.NewBits(ExprOp::SHR, a, shift, 129, true), + }; + for (int root : roots) engine.Eval(root); + if (engine.Eval(roots[2]) != 2) return 1; + uint64_t checksum = 0; + measuring = true; + for (int i = 0; i < 1000; ++i) + for (int root : roots) checksum += engine.Eval(root); + measuring = false; + std::cout << "5000 warm typed evaluations: " << allocations << " allocations, checksum=" << checksum << '\n'; + return allocations == 0 ? 0 : 1; +} diff --git a/tests/unit/test_xcoverage.cpp b/tests/unit/test_xcoverage.cpp index de3f229..c5419ff 100644 --- a/tests/unit/test_xcoverage.cpp +++ b/tests/unit/test_xcoverage.cpp @@ -6,6 +6,39 @@ using namespace xspcomm; +TEST_CASE("Collection keeps coverage gates aborts and wide bin constants", "[coverage]") { + XClock clock([](bool) { return 0; }); + XTriggerEngine engine(clock, 4); + engine.AcquireClock(); + for (int i = 0; i < 50; ++i) engine.ExprNewConst(i); + XData signal(128, XData::InOut), gate(1, XData::InOut), abort(1, XData::InOut); + signal = 1; gate = 1; abort = 0; + auto gate_root = engine.ExprNewSignal(&gate), abort_root = engine.ExprNewSignal(&abort); + std::vector one(16); one[0] = 1; + XCoveragePoint item; item.signal = &signal; item.gate = gate_root; + XCoverageBin bin; bin.program.root = engine.ExprNewCompareSigConstBytes(int(ExprOp::EQ), &signal, one); + auto handle = engine.ArmSample(XPhase::RisingStable); + engine.AttachCoverage(handle, {item}, {bin}, gate_root, abort_root); + engine.CollectExpressions({}); + REQUIRE(engine.ExpressionNodeCount() == 3); + REQUIRE(engine.ExpressionConstantCount() == 1); + auto waiter = engine.ArmClockCycles(1); engine.RunUntil(2); engine.Disarm(waiter); + REQUIRE(engine.CoverageSnapshot(handle).counters.back() == 1); + gate = 0; + waiter = engine.ArmClockCycles(1); engine.RunUntil(2); engine.Disarm(waiter); + REQUIRE(engine.CoverageSnapshot(handle).counters.back() == 1); + gate = 1; abort = 1; + engine.CollectExpressions({}); + waiter = engine.ArmClockCycles(1); engine.RunUntil(2); engine.Disarm(waiter); + REQUIRE(engine.CoverageSnapshot(handle).counters.back() == 1); + abort = 0; + waiter = engine.ArmClockCycles(1); engine.RunUntil(2); engine.Disarm(waiter); + REQUIRE(engine.CoverageSnapshot(handle).counters.back() == 2); + engine.Disarm(handle); engine.CollectExpressions({}); + REQUIRE(engine.ExpressionNodeCount() == engine.ExpressionConstantCount()); + REQUIRE(engine.ExpressionNodeCount() == 0); +} + TEST_CASE("Coverage counts persist without returning ordinary hits", "[xtrigger][coverage]") { XClock clock([](bool) { return 0; }); XTriggerEngine engine(clock, 4); diff --git a/tests/unit/test_xexpr.cpp b/tests/unit/test_xexpr.cpp index 646bad7..a1d668a 100644 --- a/tests/unit/test_xexpr.cpp +++ b/tests/unit/test_xexpr.cpp @@ -8,6 +8,52 @@ using namespace xspcomm; +TEST_CASE("Expression collection preserves state and releases unreachable wide constants", "[xexpr]") { + ExprEngine engine; + engine.NewConst(999); // force retained roots to move + XData signal(8, XData::InOut), wide(128, XData::InOut); + signal = 1; wide = 1; + const auto hold = engine.NewWithin(engine.NewSignal(&signal), 3); + std::vector value(16, 0); value[0] = 1; + const auto comparison = engine.NewCompareSigConstBytes(ExprOp::EQ, &wide, value); + engine.NewCompareSigConstBytes(ExprOp::EQ, &wide, value); + engine.SetCycle(1); REQUIRE(engine.Eval(hold) == 1); + signal = 0; engine.SetCycle(2); + REQUIRE(engine.ConstantCount() == 2); + const auto mapping = engine.Collect({hold, comparison}); + REQUIRE(engine.NodeCount() == 3); + REQUIRE(engine.ConstantCount() == 1); + REQUIRE(engine.Eval(mapping[hold]) == 1); + REQUIRE(engine.Eval(mapping[comparison]) == 1); + REQUIRE_THROWS(engine.Collect({999})); + REQUIRE(engine.NodeCount() == 3); + engine.Collect({}); + REQUIRE(engine.NodeCount() == 0); + REQUIRE(engine.ConstantCount() == 0); +} + +TEST_CASE("Typed expressions reject inconsistent native descriptors", "[xexpr]") { + ExprEngine engine; + std::vector ff{255}, one{1}, wide(16, 0); + wide[15] = 128; + const int a = engine.NewBitsConst(8, false, ff); + const int b = engine.NewBitsConst(8, false, one); + const int s = engine.NewBits(ExprOp::CAST, a, -1, 8, true); + const int w = engine.NewBitsConst(128, false, wide); + REQUIRE(engine.EvalBits(engine.NewBits(ExprOp::ADD, a, b, 8, false)) == std::vector{0}); + auto sign_extended = engine.EvalBits(engine.NewBits(ExprOp::CAST, s, -1, 128, true)); + REQUIRE(sign_extended == std::vector(16, 255)); + REQUIRE_THROWS(engine.NewBits(ExprOp::ADD, a, s, 8, false)); + REQUIRE_THROWS(engine.NewBits(ExprOp::ADD, a, w, 8, false)); + REQUIRE_THROWS(engine.NewBits(ExprOp::EQ, a, b, 8, false)); + REQUIRE_THROWS(engine.NewBits(ExprOp::WITHIN, a, -1, 8, false)); + REQUIRE_THROWS(engine.NewBitsConst(7, false, ff)); + const int truth = engine.NewBool(true); + REQUIRE_THROWS(engine.NewBits(ExprOp::ADD, truth, truth, 1, false)); + REQUIRE_THROWS(engine.NewBits(ExprOp::LT, truth, truth, 1, false)); + REQUIRE(engine.EvalBits(engine.NewBits(ExprOp::EQ, truth, truth, 1, false)) == std::vector{1}); +} + TEST_CASE("ExprEngine arithmetic and precedence", "[xexpr]") { XData a(32, XData::InOut); XData b(32, XData::InOut); @@ -178,3 +224,62 @@ TEST_CASE("ExprEngine masked comparison preserves wide bits and unknowns", "[xex engine.Clear(); REQUIRE_FALSE(engine.ValidRoot(root)); } + +TEST_CASE("Typed DAG reads shared signals once and refreshes every evaluation", "[xexpr][typed]") { + XData signal(129, XData::InOut); + unsigned current = 3, reads = 0, writes = 0; + signal.BindDPIRW([&](void *storage) { + ++reads; + auto *words = static_cast(storage); + for (size_t i = 0; i < 5; ++i) words[i] = {i == 0 ? current : 0U, 0}; + }, [&](void *) { ++writes; }); + ExprEngine engine; + engine.NewConst(999); + const int value = engine.NewBitsSignal(&signal, false); + const int doubled = engine.NewBits(ExprOp::ADD, value, value, 129, false); + int root = engine.NewBits(ExprOp::MUL, doubled, doubled, 129, false); + reads = writes = 0; + REQUIRE(engine.Eval(root) == 36); + REQUIRE(reads == 1); + current = 4; + REQUIRE(engine.Eval(root) == 64); // same cycle, different signal value + REQUIRE(reads == 2); + const auto mapping = engine.Collect({root}); + root = mapping[root]; + REQUIRE(engine.NodeCount() == 3); + current = 5; + REQUIRE(engine.Eval(root) == 100); + REQUIRE(reads == 3); + REQUIRE(writes == 0); + engine.Clear(); +} + +TEST_CASE("Typed evaluation short circuits and recovers after arithmetic errors", "[xexpr][typed]") { + ExprEngine engine; + XData numerator(9, XData::InOut), denominator(9, XData::InOut); + numerator = 12; denominator = 0; + const int a = engine.NewBitsSignal(&numerator, true), b = engine.NewBitsSignal(&denominator, true); + const int quotient = engine.NewBits(ExprOp::DIV, a, b, 9, true); + const int zero = engine.NewBool(false), one = engine.NewBool(true); + REQUIRE(engine.Eval(engine.NewBits(ExprOp::LAND, zero, quotient, 1, false)) == 0); + REQUIRE(engine.Eval(engine.NewBits(ExprOp::LOR, one, quotient, 1, false)) == 1); + REQUIRE_THROWS_AS(engine.Eval(quotient), std::domain_error); + denominator = 2; + REQUIRE(engine.Eval(quotient) == 6); + denominator = 511; // -1 in signed 9-bit arithmetic + REQUIRE(engine.Eval(quotient) == 500); // raw bits for -12 + const int shift = engine.NewBits(ExprOp::SHR, a, b, 9, true); + REQUIRE_THROWS_AS(engine.Eval(shift), std::domain_error); + denominator = 9; + REQUIRE(engine.Eval(shift) == 0); +} + +TEST_CASE("Typed evaluation uses an explicit stack for deep shared programs", "[xexpr][typed]") { + ExprEngine engine; + std::vector one{1}; + int root = engine.NewBitsConst(8, false, one); + for (int i = 0; i < 10000; ++i) root = engine.NewBits(ExprOp::ADD, root, root, 8, false); + REQUIRE(engine.Eval(root) == 0); + const auto mapping = engine.Collect({root}); + REQUIRE(engine.Eval(mapping[root]) == 0); +} diff --git a/tests/unit/test_xtrigger.cpp b/tests/unit/test_xtrigger.cpp index 004938a..c1217ae 100644 --- a/tests/unit/test_xtrigger.cpp +++ b/tests/unit/test_xtrigger.cpp @@ -503,6 +503,7 @@ TEST_CASE("XEngine rejects callback reentry and recovers from exceptions", REQUIRE_THROWS_AS(engine.SamplePhase(XPhase::DriveStable), std::logic_error); REQUIRE_THROWS_AS(engine.Clear(), std::logic_error); REQUIRE_THROWS_AS(engine.ClearExecutionState(), std::logic_error); + REQUIRE_THROWS_AS(engine.CollectExpressions({}), std::logic_error); REQUIRE_THROWS_AS(engine.ReleaseClock(), std::logic_error); }); REQUIRE(engine.RunUntil(2).advanced_ticks == 2); @@ -533,3 +534,34 @@ TEST_CASE("XEngine cannot take over a manual step already in progress", "[xtrigg runtime.AcquireClock(); runtime.ReleaseClock(); } + +TEST_CASE("Collection preserves an in-flight FSM and a fired registration awaiting rearm", "[xtrigger][collection]") { + XClock clock([](bool) { return 0; }); + XEngine engine(clock); + engine.AcquireClock(); + XData request(1, XData::InOut), response(1, XData::InOut); + request = 1; response = 0; + for (int i = 0; i < 32; ++i) engine.ExprNewConst(i); + const int req = engine.ExprNewSignal(&request), rsp = engine.ExprNewSignal(&response); + const auto handle = engine.ArmFsm(2, 0, {{0, req, 1, 0, false}, {1, rsp, 0, 7, true}}); + REQUIRE(engine.RunUntil(2).hits.empty()); + engine.CollectExpressions({}); + REQUIRE(engine.ExpressionNodeCount() == 2); + request = 0; response = 1; + const auto first = engine.RunUntil(2); + REQUIRE(first.hits.size() == 1); + REQUIRE(first.hits[0].value == 7); + engine.CollectExpressions({}); // fired, but still owned and rearmable + REQUIRE(engine.ExpressionNodeCount() == 2); + REQUIRE(engine.Rearm(handle)); + request = 1; response = 0; + REQUIRE(engine.RunUntil(2).hits.empty()); + request = 0; response = 1; + const auto second = engine.RunUntil(2); + REQUIRE(second.hits.size() == 1); + REQUIRE(second.hits[0].value == 7); + REQUIRE(engine.Disarm(handle)); + engine.CollectExpressions({}); + REQUIRE(engine.ExpressionNodeCount() == 0); + engine.ReleaseClock(); +}