diff --git a/CMakeLists.txt b/CMakeLists.txt index a98ab7a..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 2) +# 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) @@ -24,8 +25,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..5b66a11 --- /dev/null +++ b/cmake/Sources.cmake @@ -0,0 +1,28 @@ +set(XSPCOMM_SOURCES + common/lifetime.cpp + common/memory.cpp + monitor/callback.cpp + monitor/condition.cpp + monitor/expr.cpp + monitor/fsm.cpp + monitor/range.cpp + core/clock.cpp + core/coroutine.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/state.cpp + expr/engine.cpp + expr/bits.cpp + expr/parser.cpp + trigger/executor.cpp + coverage/registration.cpp + runtime/engine.cpp + trigger/matcher.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 d40d250..7a72578 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) @@ -72,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 5d6b259..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_base.h`、`xexpr.h`、`xfsm.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,9 +22,11 @@ C++ API 位于 `xspcomm` 命名空间。常用头文件: ```c++ #include "xspcomm/xcomm.h" // 常用核心 API 汇总 -#include "xspcomm/xcomuse.h" // ComUse、Expr、FSM 汇总 +#include "xspcomm/xmonitor.h" // Monitor、Expr、FSM 汇总 ``` +Monitor 组件位于 `xspcomm/monitor/`,可以按需包含 `callback.h`、`condition.h`、`range.h`、`expr.h` 和 `fsm.h`。独立的表达式引擎位于 `xexpr.h`。`common/compare.h` 提供 XData、表达式和检查器共用的比较算法;`common/memory.h` 提供数组、指针及字符串工具。 + 多语言 SWIG 包通常复用 C++ 名称,部分语言会增加更符合本语言习惯的包装方法,见“多语言差异”。 ### IOType @@ -167,7 +169,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。 | @@ -195,6 +197,11 @@ 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`;读写支持未对齐的原生地址。 + ### 连接、比较、回调 | API | 说明 | @@ -411,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"); @@ -487,11 +494,11 @@ if (eng.Eval(root)) { } ``` -## ComUse 系列 +## Monitor 系列 -### ComUseStepCb +### XStepCallback -`ComUseStepCb` 是可挂到 `XClock::StepRis/StepFal` 的基类。派生类覆写 `Call()` 实现逻辑。 +`XStepCallback` 是可挂到 `XClock::StepRis/StepFal` 的基类。派生类覆写 `Call()` 实现逻辑。 | API | 说明 | | --- | --- | @@ -505,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` 取值: @@ -524,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。 | @@ -558,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。 | @@ -586,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); @@ -597,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*`。 | @@ -651,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()` | 清零。 | @@ -669,17 +676,21 @@ state S2: | `int FromBytes(std::vector &input)` | 从 bytes 写入,返回实际写入长度。 | | `operator==` | 比较 size 和内容。 | -### ComUseRangeCheck +### XRangeCheck -`ComUseRangeCheck` 生成可传给 `ComUseCondCheck` 的范围比较函数。 +`XRangeCheck` 生成可传给 `XConditionCheck` 的范围比较函数。 | API | 说明 | | --- | --- | -| `ComUseRangeCheck(int range, int bytes)` | `bytes <= 8`。 | -| `static bool cmp(uint64_t t, uint64_t c, int r)` | 当 `r >= 0` 时检查 `c-r <= t <= c`;当 `r < 0` 时检查 `c <= t <= c-r`。 | +| `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。 | +| `GetXDataCmp()` | 返回 XData 比较 callback,使用信号的完整位宽;不同位宽按无符号零扩展,含 X/Z 时返回 false。`bytes` 不截断 XData。 | + +`range` 是有符号 `int` 容差,比较值的位宽不受其限制。例如 `XRangeCheck(1, 16)` +可以比较两个 128-bit buffer;`0xffffffffffffffff` 位于 `0x10000000000000000` 下方 1 的范围内。 +宽比较直接逐 32-bit word 计算差值,不分配临时大整数;每个 XData 操作数只刷新一次,不写回。 ### CString @@ -1010,7 +1021,7 @@ JS wrapper 增加: ```c++ #include "xspcomm/xcomm.h" -#include "xspcomm/xcomuse.h" +#include "xspcomm/xmonitor.h" using namespace xspcomm; @@ -1030,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); @@ -1045,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); @@ -1058,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 new file mode 100644 index 0000000..c51a405 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,235 @@ +# 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 `xmonitor.h` are the aggregate entry points. + +- `common/`: comparison operations and algorithms, plus memory/string utilities. + `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. `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`: 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 + 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. +- `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/`: 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`. +- `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. + +`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/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 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 + +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. + +`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 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, +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. + +`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 + +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` +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. + +`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. + +## 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/maintainer-guidance.md b/docs/maintainer-guidance.md index 30e9ecc..279a298 100644 --- a/docs/maintainer-guidance.md +++ b/docs/maintainer-guidance.md @@ -48,23 +48,41 @@ 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`, 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. +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. -`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() == 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`. +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 +`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 231b7b0..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/xdata.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/xdata.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. @@ -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/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/xdata.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,10 +103,33 @@ Behavior impact: --- ## Correctness Fix: validate checks all bval words -Location: src/xdata.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: - 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/data/backends.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/_not_export.h b/include/xspcomm/common/callback_storage.h similarity index 65% rename from include/xspcomm/_not_export.h rename to include/xspcomm/common/callback_storage.h index ddbf841..331e3c5 100644 --- a/include/xspcomm/_not_export.h +++ b/include/xspcomm/common/callback_storage.h @@ -1,5 +1,5 @@ -#ifndef __xspcomm_no_export__ -#define __xspcomm_no_export__ +#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 new file mode 100644 index 0000000..634b64f --- /dev/null +++ b/include/xspcomm/common/compare.h @@ -0,0 +1,100 @@ +#ifndef XSPCOMM_COMMON_COMPARE_H +#define XSPCOMM_COMMON_COMPARE_H + +#include +#include +#include +#include "xspcomm/common/memory_access.h" +#include "xspcomm/common/compare_op.h" + +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 +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 memory::ByteWords lhs{a, bytes}; + const memory::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/common/compare_op.h b/include/xspcomm/common/compare_op.h new file mode 100644 index 0000000..5642e4f --- /dev/null +++ b/include/xspcomm/common/compare_op.h @@ -0,0 +1,10 @@ +#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 }; + +} // namespace xspcomm + +#endif diff --git a/include/xspcomm/common/lifetime.h b/include/xspcomm/common/lifetime.h new file mode 100644 index 0000000..774270c --- /dev/null +++ b/include/xspcomm/common/lifetime.h @@ -0,0 +1,20 @@ +#ifndef XSPCOMM_COMMON_LIFETIME_H +#define XSPCOMM_COMMON_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/common/memory.h b/include/xspcomm/common/memory.h new file mode 100644 index 0000000..dd8fe09 --- /dev/null +++ b/include/xspcomm/common/memory.h @@ -0,0 +1,117 @@ +#ifndef XSPCOMM_COMMON_MEMORY_H +#define XSPCOMM_COMMON_MEMORY_H + +#include "xspcomm/xutil.h" +#include "xspcomm/common/memory_access.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 XByteBuffer{ + bool is_ref = false; + int byte_size; + int *buffer = nullptr; + public: + 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); + } + XByteBuffer(uint64_t base, int byte_size):byte_size(byte_size){ + this->buffer = (int*)base; + this->is_ref = true; + } + ~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; + } + XByteBuffer * Copy(){ + auto ret = new XByteBuffer(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/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/monitor/callback.h b/include/xspcomm/monitor/callback.h new file mode 100644 index 0000000..6e6dbc9 --- /dev/null +++ b/include/xspcomm/monitor/callback.h @@ -0,0 +1,70 @@ +#ifndef XSPCOMM_MONITOR_CALLBACK_H +#define XSPCOMM_MONITOR_CALLBACK_H + +#include "xspcomm/xutil.h" +#include "xspcomm/common/lifetime.h" +#include +#include +#include + +namespace xspcomm { + + class XData; + class XClock; + + class XStepCallback{ + 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 = 0; + XStepCallback() = default; + XStepCallback(const XStepCallback &) = delete; + XStepCallback &operator=(const XStepCallback &) = delete; + virtual ~XStepCallback(); + void Attach(XClock *clock, bool rising = true); + void Detach(); + 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 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) + XEcho(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/monitor/condition.h b/include/xspcomm/monitor/condition.h new file mode 100644 index 0000000..ac963e7 --- /dev/null +++ b/include/xspcomm/monitor/condition.h @@ -0,0 +1,109 @@ +#ifndef XSPCOMM_MONITOR_CONDITION_H +#define XSPCOMM_MONITOR_CONDITION_H + +#include "xspcomm/monitor/callback.h" +#include "xspcomm/common/compare.h" +#include "xspcomm/xdata.h" +#include +#include +#include +#include + +namespace xspcomm { + + class XClock; + class ExprEngine; + + // Condition Checker + class XConditionCheck: public XStepCallback{ + using XDataCmpFn = bool (*)(XData*, XData*); + 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(XConditionCheck*, 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(CompareOp cmp); + static PtrCmpFn SelectPtrCmpFn(CompareOp cmp); + std::vector clk_list; + struct CondXDataEntry { + std::string name; + XData* pin = nullptr; + XData* val = nullptr; + CompareOp cmp = CompareOp::EQ; + XData* valid = nullptr; + XData* valid_value = nullptr; + xfunction func = nullptr; + int triggered = 0; + uint64_t arg = 0; + CompareOp valid_cmp = CompareOp::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; + CompareOp cmp = CompareOp::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; + 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; + 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); + protected: + void SetExpression(std::string name, ExprEngine &engine, int root); + public: + 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); + 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(); + 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/monitor/expr.h b/include/xspcomm/monitor/expr.h new file mode 100644 index 0000000..668819b --- /dev/null +++ b/include/xspcomm/monitor/expr.h @@ -0,0 +1,40 @@ +#ifndef XSPCOMM_MONITOR_EXPR_H +#define XSPCOMM_MONITOR_EXPR_H + +#include "xspcomm/monitor/condition.h" +#include "xspcomm/xexpr.h" + + +namespace xspcomm { + +class XClock; + + class XExprCheck: public XConditionCheck{ + ExprEngine engine; + public: + XExprCheck(XClock* clk=nullptr) : XConditionCheck(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/monitor/fsm.h similarity index 75% rename from include/xspcomm/xfsm.h rename to include/xspcomm/monitor/fsm.h index 022effd..ac58f24 100644 --- a/include/xspcomm/xfsm.h +++ b/include/xspcomm/monitor/fsm.h @@ -1,11 +1,14 @@ -#ifndef __xspcomm_xfsm_h__ -#define __xspcomm_xfsm_h__ +#ifndef XSPCOMM_MONITOR_FSM_H +#define XSPCOMM_MONITOR_FSM_H +#include "xspcomm/monitor/callback.h" #include "xspcomm/xexpr.h" namespace xspcomm { - class ComUseFsmTrigger: public ComUseStepCb{ + class XClock; + + class XFsmMonitor: public XStepCallback{ struct FsmTransition{ int cond_root = -1; std::string next_name; @@ -22,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; @@ -46,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/monitor/range.h b/include/xspcomm/monitor/range.h new file mode 100644 index 0000000..13f79fd --- /dev/null +++ b/include/xspcomm/monitor/range.h @@ -0,0 +1,61 @@ +#ifndef XSPCOMM_MONITOR_RANGE_H +#define XSPCOMM_MONITOR_RANGE_H + +#include "xspcomm/xcallback.h" +#include "xspcomm/common/compare.h" +#include "xspcomm/xutil.h" +#include + +namespace xspcomm { + + class XData; + + class XRangeCheck { + 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: + XRangeCheck(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) { + return compare::WithinRange(target, center, 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/runtime/types.h b/include/xspcomm/runtime/types.h new file mode 100644 index 0000000..aef3ddb --- /dev/null +++ b/include/xspcomm/runtime/types.h @@ -0,0 +1,74 @@ +#ifndef XSPCOMM_RUNTIME_TYPES_H +#define XSPCOMM_RUNTIME_TYPES_H + +#include +#include +#include + +namespace xspcomm { + +enum class XPhase { + FallingStable = 0, + RisingStable = 1, + DriveStable = 2, +}; + +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/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..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/_not_export.h" +#include "xspcomm/common/callback_storage.h" #include #include #include diff --git a/include/xspcomm/xclock.h b/include/xspcomm/xclock.h index de71950..982c56e 100644 --- a/include/xspcomm/xclock.h +++ b/include/xspcomm/xclock.h @@ -4,6 +4,8 @@ #include "xspcomm/xdata.h" #include "xspcomm/xport.h" #include "xspcomm/xutil.h" +#include "xspcomm/common/lifetime.h" +#include "xspcomm/runtime/types.h" #if ENABLE_XCOROUTINE #include "xspcomm/xcoroutine.h" @@ -16,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 { @@ -53,14 +56,20 @@ 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 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 std::map, _XAWait *> cor_handler; @@ -68,6 +77,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; @@ -75,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); @@ -120,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/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 2574748..0000000 --- a/include/xspcomm/xcomuse.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef __xspcomm_xcomuse_h__ -#define __xspcomm_xcomuse_h__ - -#include "xspcomm/xcomuse_base.h" -#include "xspcomm/xexpr.h" -#include "xspcomm/xfsm.h" - -#endif diff --git a/include/xspcomm/xcomuse_base.h b/include/xspcomm/xcomuse_base.h deleted file mode 100644 index 66e898c..0000000 --- a/include/xspcomm/xcomuse_base.h +++ /dev/null @@ -1,277 +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; - public: - ComUseRangeCheck(int range, int bytes):bytes(bytes), range(range){ - Assert(bytes <= 8, "FIXME: bytes more than 8 is not supported!"); - } - static bool cmp(uint64_t t, uint64_t c, int r){ - if(r >= 0){ - return (c - r <= t) && (c >= t); - } - return (c - r >= t) && (c <= t); - } - 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); - } - 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; - } - 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/xcoverage.h b/include/xspcomm/xcoverage.h new file mode 100644 index 0000000..268ff24 --- /dev/null +++ b/include/xspcomm/xcoverage.h @@ -0,0 +1,52 @@ +#ifndef XSPCOMM_XCOVERAGE_H +#define XSPCOMM_XCOVERAGE_H + +#include "xspcomm/xpattern.h" +#include +#include +#include + +namespace xspcomm { + +class XData; + +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 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 point_id = 0; + 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 { + uint32_t generation = 0; + uint64_t epoch = 0; + uint64_t tick = 0; + // group samples/gated; samples/gated/ignored/unmatched/unknown per point; + // 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/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/xengine.h b/include/xspcomm/xengine.h new file mode 100644 index 0000000..1e27a6e --- /dev/null +++ b/include/xspcomm/xengine.h @@ -0,0 +1,130 @@ +#ifndef XSPCOMM_XENGINE_H +#define XSPCOMM_XENGINE_H + +#include "xspcomm/runtime/types.h" +#include "xspcomm/xcoverage.h" + +#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 { + 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; + std::vector free_slots; + std::vector hit_buffer; + 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; + 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); + void CheckCoverageHandle(XRegistrationHandle handle) const; + +public: + explicit XEngine(XClock *clock, size_t capacity = 1024); + 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, + 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 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); + 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 &points, + 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/xexpr.h b/include/xspcomm/xexpr.h index 85057de..ec1f974 100644 --- a/include/xspcomm/xexpr.h +++ b/include/xspcomm/xexpr.h @@ -1,15 +1,16 @@ #ifndef __xspcomm_xexpr_h__ #define __xspcomm_xexpr_h__ -#include "xspcomm/xcomuse_base.h" +#include "xspcomm/xdata.h" #include #include -#include #include #include namespace xspcomm { + class XSignalCFG; + // Expression Engine enum class ExprOp{ CONST = 0, @@ -37,6 +38,8 @@ namespace xspcomm { SELECT = 22, WITHIN = 23, HOLD = 24, + CAST = 25, + TYPED = 26, }; struct ExprNode{ @@ -69,11 +72,15 @@ 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); 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); @@ -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,44 +126,14 @@ 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); }; - 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/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/xpattern.h b/include/xspcomm/xpattern.h new file mode 100644 index 0000000..0514d1f --- /dev/null +++ b/include/xspcomm/xpattern.h @@ -0,0 +1,55 @@ +#ifndef XSPCOMM_XPATTERN_H +#define XSPCOMM_XPATTERN_H + +#include +#include + +namespace xspcomm { + +enum class XConditionMode : uint8_t { + Enter = 0, + EachSample = 1, + Change = 2, +}; + +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; +}; + +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/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..d1b3eb4 100644 --- a/include/xspcomm/xtrigger.h +++ b/include/xspcomm/xtrigger.h @@ -1,274 +1,12 @@ -#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 -#include -#include -#include +#include "xspcomm/xexpr.h" +#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, -}; - -enum class XConditionMode : uint8_t { - Enter = 0, - EachSample = 1, - Change = 2, -}; - -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; - } -}; - -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, - ClockCycles, - ValueEq, - ValueChange, - Sample, - Expr, - Sequence, - 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 { - 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; - }; - - 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 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); - -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 3; } - 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); - 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 0510148..a10dadc 100644 --- a/include/xspcomm/xutil.h +++ b/include/xspcomm/xutil.h @@ -264,10 +264,15 @@ 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); - int numInts = absShift / intBits; - int bitShift = 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 = distance % intBits; if (shift < 0) { if (numInts > 0) { std::memmove(a + numInts, a, (size - numInts) * sizeof(int)); @@ -277,7 +282,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/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 800cfd6..1af6912 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,66 +1,34 @@ -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 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}/src + ${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/lifetime.cpp b/src/common/lifetime.cpp new file mode 100644 index 0000000..91bf757 --- /dev/null +++ b/src/common/lifetime.cpp @@ -0,0 +1,8 @@ +#include "xspcomm/common/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 new file mode 100644 index 0000000..a180d2c --- /dev/null +++ b/src/common/memory.cpp @@ -0,0 +1,49 @@ +#include "xspcomm/common/memory.h" + +namespace xspcomm { +namespace { + +template +unsigned char *ArrayElement(uint64_t address, int index) { + auto *base = reinterpret_cast(address); + return base + ptrdiff_t(index) * ptrdiff_t(sizeof(T)); +} + +} // namespace + +uint64_t GetFromU64Array(uint64_t address, int index) { + return memory::Load(ArrayElement(address, index)); +} +uint32_t GetFromU32Array(uint64_t address, int index) { + return memory::Load(ArrayElement(address, index)); +} +uint8_t GetFromU8Array(uint64_t address, int index) { + return memory::Load(ArrayElement(address, index)); +} + +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(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); +} + +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); } + +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/xclock.cpp b/src/core/clock.cpp similarity index 86% rename from src/xclock.cpp rename to src/core/clock.cpp index 301a8c1..f2213d1 100644 --- a/src/xclock.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,10 +80,21 @@ void XClock::_call_back(std::vector &list) return; } this->in_callback = true; - for (auto &e : list) { - 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) { + 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, @@ -74,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; @@ -116,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); } @@ -168,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, @@ -189,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); } @@ -292,6 +343,7 @@ void XClock::eval_t() void XClock::Reset() { + CheckManualStep(); this->clk = 0; this->half_tick = 0; this->half_cycle_open = false; @@ -349,29 +401,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/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/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/registration.cpp b/src/coverage/registration.cpp new file mode 100644 index 0000000..c9da78e --- /dev/null +++ b/src/coverage/registration.cpp @@ -0,0 +1,56 @@ +#include "runtime/registration.h" +#include "xspcomm/xclock.h" +#include "xspcomm/xexpr.h" +#include "coverage/state.h" + +#include +#include + +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 &points, 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, watcher.CoverageSource(), points, bins, + gate, abort, raise_illegal, diagnostic_capacity, overlap, max_active, diagnostics); +} + +void XEngine::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 XEngine::CoverageSnapshot(XRegistrationHandle handle, bool progress) const +{ + CheckCoverageHandle(handle); + const auto &watcher = watchers[handle.slot]; + return watcher.coverage->Snapshot(clock->GetHalfTick(), progress); +} + +size_t XEngine::CoverageExecutionCount(XRegistrationHandle handle) const +{ + CheckCoverageHandle(handle); + return watchers[handle.slot].coverage->ExecutionCount(); +} + +} // namespace xspcomm diff --git a/src/coverage/state.cpp b/src/coverage/state.cpp new file mode 100644 index 0000000..1cf07dd --- /dev/null +++ b/src/coverage/state.cpp @@ -0,0 +1,370 @@ +#include "coverage/state.h" +#include "xspcomm/xexpr.h" + +#include +#include +#include + +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, + a.program.state_count, a.program.start_state) != + 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) { + 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.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); + }); +} + +CoverageState::CoverageState(uint32_t generation, ExprEngine &expr, const PatternView &source, + 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 (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"); + 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 < 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 XCoveragePointKind::Pattern: + if (point.signal || !point.dimensions.empty()) + throw std::invalid_argument("pattern point cannot have a signal or dimensions"); + break; + 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 point kind"); + } + 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.point_id >= points.size() || static_cast(bin.kind) > 3) throw std::invalid_argument("invalid coverage bin"); + check(bin.program.root); + 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) { + 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"); + } + 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"); + 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].point_id != dims[j]) + throw std::invalid_argument("invalid cross bin"); + } + } + 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 + points.size() * 5 + bins.size()); + delta.resize(snapshot.counters.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 ((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; + break; + } + } + } + if (owners[i] != i) { + this->bins[i].program.steps.clear(); + this->bins[i].program.transitions.clear(); + continue; + } + const auto &bin = this->bins[i]; + const auto program = PatternView::From(bin.program); + 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, uint64_t tick) +{ + const auto completed = Advance(expr, source_matcher, 0, overlap, max_active); + for (size_t i = 0; i < completed; ++i) Sample(expr, tick); +} + +void CoverageState::RecordDiagnostics(size_t pattern, const MatchUpdate &update) +{ + 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"); + } + return update.completed; +} + +size_t CoverageState::ExecutionCount() const +{ + size_t count = 0; + for (size_t b = 0; b < bins.size(); ++b) + count += (points[bins[b].point_id].kind == XCoveragePointKind::Pattern) && owners[b] == b; + return count; +} + +void CoverageState::ClearHistory(bool aborted) +{ + const auto reason = aborted ? CoverageState::Aborted : CoverageState::Cleared; + 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() +{ + 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 (!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_matcher.States()); + for (size_t b = 0; b < matchers.size(); ++b) append(b + 1, matchers[owners[b]].States()); + } + 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 &point = points[i]; + const size_t base = 2 + i * 5; + uint64_t combinations = 1, hits = 0; + 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"); + combinations *= n; + } + for (size_t b = 0; b < bins.size(); ++b) { + const auto &bin = bins[b]; + if (bin.point_id != 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.point_id != 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.point_id != 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.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]; + 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 + 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 < points.size(); ++i) { + const auto &point = points[i]; + const size_t base = 2 + i * 5; + const bool gate = Enabled(expr, point.gate); + // U refreshes all native words; DataValid checks X/Z across the full width. + 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].point_id == i && owners[b] == b) ClearMatcher(b + 1, matchers[b], Cleared); + continue; + } + delta[base] = 1; + 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; + } + } + } + size_t illegal = 0; + 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) + 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 (auto b : illegal_indices) if (delta[offset + b]) { + snapshot.illegal_bins.push_back(b); + snapshot.illegal_ticks.push_back(tick); + 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"); +} + + +} // namespace detail + +} // namespace xspcomm diff --git a/src/coverage/state.h b/src/coverage/state.h new file mode 100644 index 0000000..bf6f118 --- /dev/null +++ b/src/coverage/state.h @@ -0,0 +1,84 @@ +#ifndef XSPCOMM_COVERAGE_STATE_H +#define XSPCOMM_COVERAGE_STATE_H + +#include "xspcomm/xcoverage.h" +#include "trigger/matcher.h" +#include "xspcomm/xexpr.h" +#include +#include +#include + +namespace xspcomm::detail { + +// Coverage applies statistical policies to executions provided by trigger matchers. +class CoverageState { +public: + enum class SampleStatus { Ready, Skip, Aborted }; + CoverageState(uint32_t generation, ExprEngine &expr, const PatternView &source, + 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); + 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, 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; + +private: + void RecordDiagnostics(size_t pattern, const MatchUpdate &update); + 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 points; + std::vector bins; + 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 }; + 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 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; + 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/data/backends.cpp b/src/data/backends.cpp new file mode 100644 index 0000000..b818863 --- /dev/null +++ b/src/data/backends.cpp @@ -0,0 +1,208 @@ +#include "xspcomm/xdata.h" +#include "xspcomm/xexpr.h" + +#include "data/native_memory.h" + +namespace xspcomm { + +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) { + detail::VecMemoryIO::Bind(pdata, this->vecSize, this->vecRead, this->vecWrite); + } else if (this->mWidth <= 16) { + 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: \ + detail::VecMemoryIO::Bind(pdata, this->vecSize, this->vecRead, this->vecWrite); \ + break; + + switch (this->vecSize) { + XCOMM_NATIVE_WORD_COUNTS(XCOMM_BIND_NATIVE_CASE) + default: + detail::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/data/data.cpp b/src/data/data.cpp new file mode 100644 index 0000000..114c812 --- /dev/null +++ b/src/data/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/data/native_memory.h b/src/data/native_memory.h new file mode 100644 index 0000000..a1bf837 --- /dev/null +++ b/src/data/native_memory.h @@ -0,0 +1,70 @@ +#ifndef XSPCOMM_DATA_NATIVE_MEMORY_H +#define XSPCOMM_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/src/data/pin.cpp b/src/data/pin.cpp new file mode 100644 index 0000000..7227763 --- /dev/null +++ b/src/data/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/data/value.cpp b/src/data/value.cpp new file mode 100644 index 0000000..ddab569 --- /dev/null +++ b/src/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/data/vpi.cpp b/src/data/vpi.cpp new file mode 100644 index 0000000..813c34b --- /dev/null +++ b/src/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/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/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..eb5d8d4 100644 --- a/src/xexpr_engine.cpp +++ b/src/expr/engine.cpp @@ -1,10 +1,60 @@ #include "xspcomm/xexpr.h" +#include "xspcomm/xsignal_cfg.h" +#include "xspcomm/common/compare.h" #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; @@ -166,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; @@ -190,6 +246,7 @@ bool ExprEngine::IsKnownNode(int id){ } void ExprEngine::Clear(){ + ClearBitsWorkspace(); this->nodes.clear(); this->const_xdata.clear(); this->signal_xdata.clear(); @@ -200,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: @@ -321,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(); @@ -363,17 +426,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 +582,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 +745,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/monitor/callback.cpp b/src/monitor/callback.cpp new file mode 100644 index 0000000..50e2c76 --- /dev/null +++ b/src/monitor/callback.cpp @@ -0,0 +1,103 @@ +#include "xspcomm/monitor/callback.h" +#include "xspcomm/xclock.h" +#include + +namespace xspcomm { + +XStepCallback::~XStepCallback() { Detach(); } + +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 = "XStepCallback:" + 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 XStepCallback::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 XStepCallback::GetCb(){ + return (u_int64_t)XStepCallback::Cb; +} + +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 XStepCallback::Disable(){ + this->cb_enable = false; +} +void XStepCallback::Enable(){ + this->cb_enable = true; +} +bool XStepCallback::IsDisable(){ + return !this->cb_enable; +} +int XStepCallback::GetCbCount(){ + return this->cb_counts; +} +int XStepCallback::IncCbCount(){ + this->cb_counts += 1; + return this->cb_counts; +} +int XStepCallback::DecCbCount(){ + this->cb_counts -= 1; + return this->cb_counts; +} +void XStepCallback::SetMaxCbs(int c){ + this->cb_maxcts = c; +} +void XStepCallback::Reset(){ + this->cb_enable = true; + this->cb_counts = 0; +} + +void XStepCallback::Call(){ + fprintf(stderr, "Error, This is a virtual Call!\n"); +} + +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"); + 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/monitor/condition.cpp b/src/monitor/condition.cpp new file mode 100644 index 0000000..9a08367 --- /dev/null +++ b/src/monitor/condition.cpp @@ -0,0 +1,266 @@ +#include "xspcomm/monitor/condition.h" +#include "xspcomm/xclock.h" +#include "xspcomm/xexpr.h" + +namespace xspcomm { + +void XConditionCheck::BindXClock(XClock *clk){ + this->clk_list.push_back(clk); +} +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)) + 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()){ + CondXDataEntry entry; + entry.name = unique_name; + entry.pin = pin; + entry.val = val; + entry.cmp = cmp; + entry.valid = valid; + entry.valid_value = valid_value; + entry.func = func; + entry.arg = arg; + 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)); + this->cond_idx_xdata[unique_name] = this->cond_vec_xdata.size() - 1; + }else{ + auto &entry = this->cond_vec_xdata[it->second]; + entry.pin = pin; + entry.val = val; + entry.cmp = cmp; + entry.valid = valid; + entry.valid_value = valid_value; + entry.func = func; + entry.arg = arg; + entry.cmp_fn = SelectXDataCmpFn(cmp); + entry.valid_cmp_fn = SelectXDataCmpFn(entry.valid_cmp); + } +}; +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))) + 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()){ + CondUint64Entry entry; + entry.name = unique_name; + entry.pin_ptr = pin_ptr; + entry.val_ptr = val_ptr; + entry.cmp = cmp; + entry.bytes = bytes; + entry.valid_ptr = valid_ptr; + entry.valid_value_ptr = valid_value_ptr; + entry.valid_bytes = valid_bytes; + entry.func = func; + entry.arg = arg; + 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)); + this->cond_idx_uint64[unique_name] = this->cond_vec_uint64.size() - 1; + }else{ + auto &entry = this->cond_vec_uint64[it->second]; + entry.pin_ptr = pin_ptr; + entry.val_ptr = val_ptr; + entry.cmp = cmp; + entry.bytes = bytes; + entry.valid_ptr = valid_ptr; + entry.valid_value_ptr = valid_value_ptr; + entry.valid_bytes = valid_bytes; + entry.func = func; + entry.arg = arg; + entry.cmp_fn = SelectPtrCmpFn(cmp); + entry.valid_cmp_fn = SelectPtrCmpFn(entry.valid_cmp); + } +}; +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 XConditionCheck::GetTriggeredConditionKeys(){ + std::vector ret; + for(auto &e : this->cond_vec_xdata){ + if(e.triggered)ret.push_back(e.name); + } + 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 XConditionCheck::ListCondition(){ + std::map ret; + for(auto &e : this->cond_vec_xdata){ + ret[e.name] = e.triggered ? true : false; + } + 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; +} +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; + } + auto it2 = this->cond_idx_uint64.find(unique_name); + if(it2 != this->cond_idx_uint64.end()){ + return this->cond_vec_uint64[it2->second].valid_cmp; + } + Error("Condition not found: %s", unique_name.c_str()); + return CompareOp::EQ; // default +} +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()){ + auto &entry = this->cond_vec_xdata[it->second]; + entry.valid_cmp = cmp; + entry.valid_cmp_fn = SelectXDataCmpFn(cmp); + return; + } + auto it2 = this->cond_idx_uint64.find(unique_name); + if(it2 != this->cond_idx_uint64.end()){ + auto &entry = this->cond_vec_uint64[it2->second]; + entry.valid_cmp = cmp; + entry.valid_cmp_fn = SelectPtrCmpFn(cmp); + return; + } + Error("Condition not found: %s", unique_name.c_str()); +} +void XConditionCheck::ClearClock(){this->clk_list.clear();} +void XConditionCheck::ClearCondition(){ + expressions.clear(); + expression_index.clear(); + this->cond_idx_xdata.clear(); + this->cond_idx_uint64.clear(); + this->cond_vec_xdata.clear(); + this->cond_vec_uint64.clear(); +}; +xfunction XConditionCheck::AsXDataXFunc(uint64_t func){ + xfunction ret = (bool (*)(XData*, XData*, uint64_t))func; + return ret; +} +xfunction XConditionCheck::AsPtrXFunc(uint64_t func){ + xfunction ret = (bool (*)(uint64_t, uint64_t, uint64_t))func; + return ret; +} +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; + } + 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(); + } +} +} + +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 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 XConditionCheck::Evaluate(ExprEntry &entry) { + return entry.root >= 0 && entry.engine->Eval(entry.root) != 0; +} + +void XConditionCheck::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 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); + 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 XConditionCheck::RemoveExpression(const std::string &name) { + RemoveEntry(expression_index, expressions, name); +} + +XConditionCheck::XDataCmpFn XConditionCheck::SelectXDataCmpFn(CompareOp cmp){ + switch (cmp) { + 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; + } +} + +XConditionCheck::PtrCmpFn XConditionCheck::SelectPtrCmpFn(CompareOp cmp){ + switch (cmp) { + 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 XConditionCheck::RemoveXDataCond(std::unordered_map &index, + std::vector &entries, const std::string &name) { + return RemoveEntry(index, entries, name); +} + +bool XConditionCheck::RemoveUint64Cond(std::unordered_map &index, + std::vector &entries, const std::string &name) { + return RemoveEntry(index, entries, name); +} + +} // namespace xspcomm diff --git a/src/monitor/expr.cpp b/src/monitor/expr.cpp new file mode 100644 index 0000000..a3a07f7 --- /dev/null +++ b/src/monitor/expr.cpp @@ -0,0 +1,75 @@ +#include "xspcomm/monitor/expr.h" +#include "xspcomm/xclock.h" + +namespace xspcomm { + +int XExprCheck::ExprNewConst(uint64_t v){ + return this->engine.NewConst(v); +} + +int XExprCheck::ExprNewSignal(XData* sig){ + return this->engine.NewSignal(sig); +} + +int XExprCheck::ExprNewUnary(int op, int child){ + return this->engine.NewUnary(static_cast(op), child); +} + +int XExprCheck::ExprNewBinary(int op, int lhs, int rhs){ + return this->engine.NewBinary(static_cast(op), lhs, rhs); +} + +int XExprCheck::ExprNewCompare(int op, int lhs, int rhs){ + return this->engine.NewCompare(static_cast(op), lhs, rhs); +} + +int XExprCheck::ExprNewCompareSigSig(int op, XData* lhs, XData* rhs){ + return this->engine.NewCompareSigSig(static_cast(op), lhs, rhs); +} + +int XExprCheck::ExprNewCompareSigConst(int op, XData* lhs, uint64_t rhs){ + return this->engine.NewCompareSigConst(static_cast(op), lhs, rhs); +} + +int XExprCheck::ExprNewCompareConstSig(int op, uint64_t lhs, XData* rhs){ + return this->engine.NewCompareConstSig(static_cast(op), lhs, rhs); +} + +int XExprCheck::ExprNewCompareSigConstBytes( + int op, XData* lhs, std::vector &rhs){ + return this->engine.NewCompareSigConstBytes(static_cast(op), lhs, rhs); +} + +int XExprCheck::ExprNewCompareConstBytesSig( + int op, std::vector &lhs, XData* rhs){ + return this->engine.NewCompareConstBytesSig(static_cast(op), lhs, rhs); +} + +int XExprCheck::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 XExprCheck::SetExpr(std::string name, int root) { + SetExpression(std::move(name), engine, root); +} + +void XExprCheck::RemoveExpr(std::string name) { RemoveCondition(std::move(name)); } +std::map XExprCheck::ListExpr() { return ListCondition(); } +std::vector XExprCheck::GetTriggeredExprKeys() { return GetTriggeredConditionKeys(); } + +void XExprCheck::ClearExpr() { + ClearCondition(); + engine.Clear(); +} + +void XExprCheck::Call() { + engine.SetCycle(cycle); + XConditionCheck::Call(); +} + +} // namespace xspcomm diff --git a/src/xfsm_trigger.cpp b/src/monitor/fsm.cpp similarity index 95% rename from src/xfsm_trigger.cpp rename to src/monitor/fsm.cpp index d608cce..1d822d5 100644 --- a/src/xfsm_trigger.cpp +++ b/src/monitor/fsm.cpp @@ -1,4 +1,6 @@ -#include "xspcomm/xfsm.h" +#include "xspcomm/monitor/fsm.h" +#include "xspcomm/xclock.h" +#include "xspcomm/xsignal_cfg.h" #include #include @@ -110,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()){ @@ -218,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; } @@ -243,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; } @@ -268,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; } @@ -293,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; } @@ -437,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; @@ -456,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(); @@ -470,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); @@ -485,7 +487,7 @@ std::vector ComUseFsmTrigger::ListStates(){ return ret; } -void ComUseFsmTrigger::Call(){ +void XFsmMonitor::Call(){ if(this->triggered){ return; } diff --git a/src/monitor/range.cpp b/src/monitor/range.cpp new file mode 100644 index 0000000..4368655 --- /dev/null +++ b/src/monitor/range.cpp @@ -0,0 +1,49 @@ +#include "xspcomm/monitor/range.h" +#include "xspcomm/xdata.h" + +#include + +namespace xspcomm { + +template +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 XRangeCheck::ArrayCompare XRangeCheck::comparisons[] = { + Compare<1>, Compare<2>, Compare<3>, Compare<4>, + Compare<5>, Compare<6>, Compare<7>, Compare<8>, +}; + +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 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; + 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/runtime/engine.cpp b/src/runtime/engine.cpp new file mode 100644 index 0000000..742fce1 --- /dev/null +++ b/src/runtime/engine.cpp @@ -0,0 +1,638 @@ +#include "runtime/registration.h" +#include "xspcomm/xclock.h" +#include "xspcomm/xexpr.h" +#include "coverage/state.h" +#include "trigger/matcher.h" + +#include +#include +#include + +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()) +{ + if (clock == nullptr) { + throw std::invalid_argument("XTriggerEngine clock must not be null"); + } + 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); +} + +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) { + 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(); + free_slots.pop_back(); + watchers[slot].generation += 1; + } else { + if (watchers.size() >= capacity) { + return {}; + } + slot = static_cast(watchers.size()); + watchers.emplace_back(); + watchers[slot].generation = 1; + } + Watcher &watcher = watchers[slot]; + watcher.coverage.reset(); + watcher.sequence_failed = watcher.sequence_expired = false; + watcher.occupied = true; + watcher.armed = true; + watcher.signal = nullptr; + watcher.remaining = 0; + watcher.initial_count = 0; + watcher.expected = 0; + watcher.expected_x_mask = 0; + watcher.expected_wide.reset(); + watcher.expected_bytes.clear(); + watcher.expected_x_bytes.clear(); + watcher.program.mode = XConditionMode::Enter; + watcher.last_condition = false; + 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.program.transitions.clear(); + watcher.program.state_count = 0; + watcher.program.start_state = 0; + watcher.fsm_current_state = 0; + return {slot, watcher.generation}; +} + +XRegistrationHandle XEngine::ArmEdge( + XPhase phase, uint64_t source_id) +{ + auto handle = Allocate(); + if (!handle.IsValid()) return handle; + auto &watcher = watchers[handle.slot]; + watcher.kind = WatcherKind::Edge; + watcher.phase = phase; + watcher.source_id = source_id ? source_id : clock->CSelf(); + return handle; +} + +XRegistrationHandle XEngine::ArmClockCycles( + uint64_t cycles, XPhase phase, uint64_t source_id) +{ + if (cycles == 0) return {}; + auto handle = Allocate(); + if (!handle.IsValid()) return handle; + auto &watcher = watchers[handle.slot]; + watcher.kind = WatcherKind::ClockCycles; + watcher.phase = phase; + watcher.source_id = source_id ? source_id : clock->CSelf(); + watcher.remaining = cycles; + watcher.initial_count = cycles; + return handle; +} + +XRegistrationHandle XEngine::ArmValueEq( + XData *signal, uint64_t expected, XPhase phase, XConditionMode mode, + uint64_t source_id) +{ + if (signal == nullptr || signal->W() > 64) return {}; + auto handle = Allocate(); + if (!handle.IsValid()) return handle; + auto &watcher = watchers[handle.slot]; + watcher.kind = WatcherKind::ValueEq; + watcher.phase = phase; + watcher.source_id = source_id ? source_id : signal->CSelf(); + watcher.signal = signal; + watcher.expected = expected; + watcher.program.mode = mode; + return handle; +} + +XRegistrationHandle XEngine::ArmValueEqBytes( + XData *signal, std::vector &expected, XPhase phase, + XConditionMode mode, uint64_t source_id) +{ + if (signal == nullptr || signal->W() <= 64) return {}; + auto handle = Allocate(); + if (!handle.IsValid()) return handle; + auto &watcher = watchers[handle.slot]; + watcher.kind = WatcherKind::ValueEq; + watcher.phase = phase; + watcher.source_id = source_id ? source_id : signal->CSelf(); + watcher.signal = signal; + watcher.expected_wide = std::make_shared(signal->W(), XData::InOut); + watcher.expected_wide->SetVU8(expected); + watcher.program.mode = mode; + return handle; +} + +XRegistrationHandle XEngine::ArmValueChange( + XData *signal, XPhase phase, uint64_t source_id) +{ + if (signal == nullptr) return {}; + auto handle = Allocate(); + if (!handle.IsValid()) return handle; + auto &watcher = watchers[handle.slot]; + watcher.kind = WatcherKind::ValueChange; + watcher.phase = phase; + watcher.source_id = source_id ? source_id : signal->CSelf(); + watcher.signal = signal; + if (signal->W() > 64) { + watcher.expected_bytes = signal->GetVU8(); + watcher.expected_x_bytes = signal->GetBvalBytes(); + } else { + watcher.expected = signal->U(); + watcher.expected_x_mask = signal->XMask(); + } + return handle; +} + +XRegistrationHandle XEngine::ArmSample( + XPhase phase, uint64_t source_id) +{ + auto handle = Allocate(); + if (!handle.IsValid()) return handle; + auto &watcher = watchers[handle.slot]; + watcher.kind = WatcherKind::Sample; + watcher.phase = phase; + watcher.source_id = source_id; + return handle; +} + +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; + return expr_engine->NewSignal(signal); +} + +int XEngine::ExprNewUnary(int op, int child) +{ + return expr_engine->NewUnary(static_cast(op), child); +} + +int XEngine::ExprNewBinary(int op, int lhs, int rhs) +{ + return expr_engine->NewBinary(static_cast(op), lhs, rhs); +} + +int XEngine::ExprNewCompare(int op, int lhs, int rhs) +{ + return expr_engine->NewCompare(static_cast(op), lhs, rhs); +} + +int XEngine::ExprNewCompareSigSig(int op, XData *lhs, XData *rhs) +{ + return expr_engine->NewCompareSigSig(static_cast(op), lhs, rhs); +} + +int XEngine::ExprNewCompareSigConstBytes( + int op, XData *lhs, std::vector &rhs) +{ + return expr_engine->NewCompareSigConstBytes( + static_cast(op), lhs, rhs); +} + +int XEngine::ExprNewCompareConstBytesSig( + int op, std::vector &lhs, XData *rhs) +{ + return expr_engine->NewCompareConstBytesSig( + static_cast(op), lhs, rhs); +} + +int XEngine::ExprNewMaskedCompareSigConstBytes( + XData *lhs, std::vector &value, std::vector &mask) +{ + const int root = expr_engine->NewMaskedCompareSigConstBytes(lhs, value, mask); + if (root < 0) throw std::invalid_argument("invalid masked signal comparison"); + return root; +} + +XRegistrationHandle XEngine::ArmExpr( + int root, XPhase phase, XConditionMode mode, uint64_t source_id) +{ + 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.program.root = root; + watcher.program.mode = mode; + expr_engine->OptimizeShortCircuitOrder(root); + return handle; +} + +XRegistrationHandle XEngine::ArmSequence( + const std::vector &steps, XPhase phase, + uint64_t source_id) +{ + 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.program.kind = XTriggerProgramKind::Sequence; + watcher.program.steps = steps; + for (const auto &step : steps) { + expr_engine->OptimizeShortCircuitOrder(step.root); + } + return handle; +} + +XRegistrationHandle XEngine::ArmFsm( + uint32_t state_count, uint32_t start_state, + const std::vector &transitions, XPhase phase, + uint64_t source_id) +{ + 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.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) { + expr_engine->OptimizeShortCircuitOrder(transition.root); + } + } + return handle; +} + +bool XEngine::Disarm(XRegistrationHandle handle) +{ + if (!handle.IsValid() || handle.slot >= watchers.size()) return false; + auto &watcher = watchers[handle.slot]; + if (!watcher.occupied || watcher.generation != handle.generation) { + return false; + } + watcher.coverage.reset(); + watcher.occupied = false; + watcher.armed = false; + watcher.signal = nullptr; + watcher.expected_wide.reset(); + watcher.expected_bytes.clear(); + watcher.expected_x_bytes.clear(); + free_slots.push_back(handle.slot); + return true; +} + +bool XEngine::RearmSample(XRegistrationHandle handle) +{ + if (!handle.IsValid() || handle.slot >= watchers.size()) return false; + auto &watcher = watchers[handle.slot]; + if (!watcher.occupied || watcher.generation != handle.generation || + watcher.kind != WatcherKind::Sample || watcher.armed) { + return false; + } + watcher.armed = true; + return true; +} + +bool XEngine::Rearm(XRegistrationHandle handle) +{ + if (!handle.IsValid() || handle.slot >= watchers.size()) return false; + auto &watcher = watchers[handle.slot]; + if (!watcher.occupied || watcher.generation != handle.generation || + watcher.armed) { + return false; + } + watcher.armed = true; + if (watcher.kind == WatcherKind::ClockCycles) { + watcher.remaining = watcher.initial_count; + } else if (watcher.kind == WatcherKind::Sequence) { + watcher.sequence_index = 0; + watcher.sequence_age = 0; + watcher.sequence_held = 0; + } else if (watcher.kind == WatcherKind::Fsm) { + 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(); + watcher.expected_x_bytes = watcher.signal->GetBvalBytes(); + } else { + watcher.expected = watcher.signal->U(); + watcher.expected_x_mask = watcher.signal->XMask(); + } + } + return true; +} + +void XEngine::AppendHit( + uint32_t slot, Watcher &watcher, XHitKind kind, uint64_t value, + uint64_t event_id, uint64_t x_mask) +{ + if (watcher.coverage) { + watcher.coverage->Sample(*expr_engine, clock->GetHalfTick()); + watcher.ResetPattern(); + return; + } + if (event_id == 0) event_id = next_event_id++; + hit_buffer.push_back({ + event_id, + clock->GetHalfTick(), + watcher.source_id, + value, + x_mask, + slot, + watcher.generation, + kind, + evaluation_phase, + 0, + }); + watcher.armed = false; +} + +bool XEngine::HasArmedPhase(XPhase phase) const +{ + for (const auto &watcher : watchers) { + if (watcher.occupied && watcher.armed && watcher.phase == phase) { + return true; + } + } + return false; +} + +// Keep the sampling adapter visible to the phase loop so it can inline. +inline bool XEngine::EvaluateCoverage(Watcher &watcher) +{ + 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; + } + const auto source = watcher.CoverageSource(); + if (source.sequence || source.fsm) { + coverage.SamplePattern(*expr_engine, clock->GetHalfTick()); + return true; + } + return false; +} + +void XEngine::EvaluatePhase(XPhase phase) +{ + evaluation_phase = phase; + uint64_t edge_event_id = 0; + expr_engine->SetCycle(clock->GetHalfTick()); + for (uint32_t slot = 0; slot < watchers.size(); ++slot) { + auto &watcher = watchers[slot]; + if (!watcher.occupied || !watcher.armed || + watcher.phase != phase) { + continue; + } + if (watcher.coverage && EvaluateCoverage(watcher)) continue; + 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++; + event_id = edge_event_id; + } + AppendHit(slot, watcher, match.kind, match.value, event_id, match.x_mask); + } +} + +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(); + 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) { + if (!clock->_step_half_impl(true)) { + result.stop_reason = XStopReason::BackendStop; + result.hits = hit_buffer; + return result; + } + result.advanced_ticks += 1; + result.stopped_phase = clock->GetPhase(); + EvaluatePhase(clock->GetPhase()); + if (!hit_buffer.empty()) { + result.stop_reason = XStopReason::TriggerHit; + for (const auto &hit : hit_buffer) { + if (hit.kind == XHitKind::ClockFall || + hit.kind == XHitKind::ClockRise) { + result.stop_reason = XStopReason::EdgeBarrier; + break; + } + } + result.hits = hit_buffer; + return result; + } + if (clock->GetPhase() == XPhase::FallingStable && + HasArmedPhase(XPhase::DriveStable)) { + result.stop_reason = XStopReason::EdgeBarrier; + result.hits = hit_buffer; + return result; + } + if (max_wall_time_ns != 0 && + (i + 1) % budget_check_interval == 0) { + const auto elapsed = std::chrono::duration_cast< + std::chrono::nanoseconds>( + std::chrono::steady_clock::now() - started); + if (static_cast(elapsed.count()) >= max_wall_time_ns) { + result.stop_reason = XStopReason::QuantumExpired; + result.hits = hit_buffer; + return result; + } + } + } + result.stop_reason = XStopReason::RunLimit; + result.hits = hit_buffer; + return result; +} + +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( + "DriveStable may only be sampled after FallingStable"); + } + XRunResult result; + result.stopped_phase = phase; + result.stop_reason = XStopReason::EdgeBarrier; + hit_buffer.clear(); + clock->RefreshComb(); + EvaluatePhase(phase); + if (!hit_buffer.empty()) { + result.stop_reason = XStopReason::TriggerHit; + } + result.hits = hit_buffer; + return result; +} + + +size_t XEngine::ActiveCount() const +{ + size_t count = 0; + for (const auto &watcher : watchers) { + if (watcher.occupied) count += 1; + } + return count; +} + +void XEngine::ClearExecutionState() +{ + CheckNotRunning(); + if (ActiveCount() != 0) { + throw std::logic_error( + "cannot reset trigger runtime with active watchers"); + } + for (auto &watcher : watchers) { + watcher.signal = nullptr; + watcher.expected_wide.reset(); + watcher.expected_bytes.clear(); + watcher.expected_x_bytes.clear(); + watcher.program.steps.clear(); + watcher.program.transitions.clear(); + watcher.program.root = -1; + } + hit_buffer.clear(); + expr_engine->Clear(); +} + +void XEngine::Clear() +{ + CheckNotRunning(); + watchers.clear(); + free_slots.clear(); + hit_buffer.clear(); + next_event_id = 1; + expr_engine->Clear(); +} + +} // namespace xspcomm 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/executor.cpp b/src/trigger/executor.cpp new file mode 100644 index 0000000..f8ca20c --- /dev/null +++ b/src/trigger/executor.cpp @@ -0,0 +1,118 @@ +#include "trigger/executor.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/src/trigger/executor.h b/src/trigger/executor.h new file mode 100644 index 0000000..4255066 --- /dev/null +++ b/src/trigger/executor.h @@ -0,0 +1,77 @@ +#ifndef XSPCOMM_TRIGGER_EXECUTOR_H +#define XSPCOMM_TRIGGER_EXECUTOR_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/src/trigger/matcher.cpp b/src/trigger/matcher.cpp new file mode 100644 index 0000000..2be5c4e --- /dev/null +++ b/src/trigger/matcher.cpp @@ -0,0 +1,92 @@ +#include "trigger/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()); + } +} + +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) { + update.completed = AdvanceCondition(expr); + 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 = PatternExecutor::Advance(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 = 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) { + 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..fd127ee --- /dev/null +++ b/src/trigger/matcher.h @@ -0,0 +1,61 @@ +#ifndef XSPCOMM_TRIGGER_MATCHER_H +#define XSPCOMM_TRIGGER_MATCHER_H + +#include "trigger/executor.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 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); + // 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 + { + 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(); + void RemapExpressions(const std::vector &mapping) { + if (program.root >= 0) program.root = mapping[program.root]; + } + +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/src/trigger/registration.h b/src/trigger/registration.h new file mode 100644 index 0000000..51e23fe --- /dev/null +++ b/src/trigger/registration.h @@ -0,0 +1,104 @@ +#ifndef XSPCOMM_TRIGGER_REGISTRATION_H +#define XSPCOMM_TRIGGER_REGISTRATION_H + +#include "xspcomm/runtime/types.h" +#include "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/src/xcomuse_base.cpp b/src/xcomuse_base.cpp deleted file mode 100644 index a577ba8..0000000 --- a/src/xcomuse_base.cpp +++ /dev/null @@ -1,402 +0,0 @@ - -#include "xspcomm/xcomuse_base.h" - -#include -#include -#include - - -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); -} -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); - 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()){ - CondXDataEntry entry; - entry.name = unique_name; - entry.pin = pin; - entry.val = val; - entry.cmp = cmp; - entry.valid = valid; - entry.valid_value = valid_value; - entry.func = func; - entry.arg = arg; - entry.valid_cmp = ComUseCondCmp::EQ; - entry.cmp_fn = SelectXDataCmpFn(cmp); - entry.valid_cmp_fn = SelectXDataCmpFn(entry.valid_cmp); - this->cond_vec_xdata.push_back(std::move(entry)); - this->cond_idx_xdata[unique_name] = this->cond_vec_xdata.size() - 1; - }else{ - auto &entry = this->cond_vec_xdata[it->second]; - entry.pin = pin; - entry.val = val; - entry.cmp = cmp; - entry.valid = valid; - entry.valid_value = valid_value; - entry.func = func; - entry.arg = arg; - entry.cmp_fn = SelectXDataCmpFn(cmp); - 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){ - 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()){ - CondUint64Entry entry; - entry.name = unique_name; - entry.pin_ptr = pin_ptr; - entry.val_ptr = val_ptr; - entry.cmp = cmp; - entry.bytes = bytes; - entry.valid_ptr = valid_ptr; - entry.valid_value_ptr = valid_value_ptr; - entry.valid_bytes = valid_bytes; - entry.func = func; - entry.arg = arg; - entry.valid_cmp = ComUseCondCmp::EQ; - entry.cmp_fn = SelectPtrCmpFn(cmp); - entry.valid_cmp_fn = SelectPtrCmpFn(entry.valid_cmp); - this->cond_vec_uint64.push_back(std::move(entry)); - this->cond_idx_uint64[unique_name] = this->cond_vec_uint64.size() - 1; - }else{ - auto &entry = this->cond_vec_uint64[it->second]; - entry.pin_ptr = pin_ptr; - entry.val_ptr = val_ptr; - entry.cmp = cmp; - entry.bytes = bytes; - entry.valid_ptr = valid_ptr; - entry.valid_value_ptr = valid_value_ptr; - entry.valid_bytes = valid_bytes; - entry.func = func; - entry.arg = arg; - entry.cmp_fn = SelectPtrCmpFn(cmp); - entry.valid_cmp_fn = SelectPtrCmpFn(entry.valid_cmp); - } -}; -void ComUseCondCheck::RemoveCondition(std::string 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 ret; - for(auto &e : this->cond_vec_xdata){ - if(e.triggered)ret.push_back(e.name); - } - for(auto &e : this->cond_vec_uint64){ - if(e.triggered)ret.push_back(e.name); - } - return ret; -} -std::map ComUseCondCheck::ListCondition(){ - std::map ret; - for(auto &e : this->cond_vec_xdata){ - ret[e.name] = e.triggered ? true : false; - } - for(auto &e : this->cond_vec_uint64){ - ret[e.name] = e.triggered ? true : false; - } - return ret; -} -ComUseCondCmp ComUseCondCheck::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; - } - auto it2 = this->cond_idx_uint64.find(unique_name); - if(it2 != this->cond_idx_uint64.end()){ - return this->cond_vec_uint64[it2->second].valid_cmp; - } - Error("Condition not found: %s", unique_name.c_str()); - return ComUseCondCmp::EQ; // default -} -void ComUseCondCheck::SetValidCmpMode(std::string unique_name, ComUseCondCmp cmp){ - auto it = this->cond_idx_xdata.find(unique_name); - if(it != this->cond_idx_xdata.end()){ - auto &entry = this->cond_vec_xdata[it->second]; - entry.valid_cmp = cmp; - entry.valid_cmp_fn = SelectXDataCmpFn(cmp); - return; - } - auto it2 = this->cond_idx_uint64.find(unique_name); - if(it2 != this->cond_idx_uint64.end()){ - auto &entry = this->cond_vec_uint64[it2->second]; - entry.valid_cmp = cmp; - entry.valid_cmp_fn = SelectPtrCmpFn(cmp); - return; - } - Error("Condition not found: %s", unique_name.c_str()); -} -void ComUseCondCheck::ClearClock(){this->clk_list.clear();} -void ComUseCondCheck::ClearCondition(){ - this->cond_idx_xdata.clear(); - this->cond_idx_uint64.clear(); - 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; -} -xfunction ComUseCondCheck::AsPtrXFunc(uint64_t func){ - 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; - } - // 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(); - } - } - // 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(); - } - } -}; -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; - 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; - default: return nullptr; - } -} - -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::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; -} - -} // namespace xspcomm diff --git a/src/xdata.cpp b/src/xdata.cpp deleted file mode 100644 index aacdfec..0000000 --- a/src/xdata.cpp +++ /dev/null @@ -1,1668 +0,0 @@ - -#include "xspcomm/xdata.h" -#include "xspcomm/xexpr.h" -#include "xspcomm/xutil.h" - -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 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] = (1 << last_bits) - 1; - } - } - // 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(); -} -void XData::BindNativeData(uint64_t pdata){ - if (this->mWidth == 0){ - this->bitRead = [pdata](void *d){ - *(unsigned char *)d = *(unsigned char *)pdata; - }; - this->bitWrite = [pdata](const unsigned char d){ - *(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)); - }; - // 9 - 16 - }else if (this->mWidth <= 16){ - this->vecWrite = [this, pdata](void *d){ - *(unsigned short *)pdata = *(unsigned short *)(&(((xsvLogicVecVal *)d)[0].aval)); - }; - // 17 + - }else{ - this->vecWrite = [this, pdata](const void *d){ - for(int i = 0; i < this->vecSize; i++){ - ((uint32_t *)pdata)[i] = ((xsvLogicVecVal *)d)[i].aval; - } - }; - } - } - 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 = std::min(count, this->vecSize - start); - 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(); - // less 2 bit or more than 64 bit, ignore cast - 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/src/xtrigger.cpp b/src/xtrigger.cpp deleted file mode 100644 index a68b21f..0000000 --- a/src/xtrigger.cpp +++ /dev/null @@ -1,968 +0,0 @@ -#include "xspcomm/xtrigger.h" -#include "xspcomm/xexpr.h" - -#include -#include -#include - -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()) -{ - if (clock == nullptr) { - throw std::invalid_argument("XTriggerEngine clock must not be null"); - } - if (capacity == 0) { - throw std::invalid_argument("XTriggerEngine capacity must be positive"); - } - watchers.reserve(capacity); - free_slots.reserve(capacity); - hit_buffer.reserve(capacity); -} - -XTriggerEngine::~XTriggerEngine() = default; - -XRegistrationHandle XTriggerEngine::Allocate() -{ - uint32_t slot; - if (!free_slots.empty()) { - slot = free_slots.back(); - free_slots.pop_back(); - watchers[slot].generation += 1; - } else { - if (watchers.size() >= capacity) { - return {}; - } - slot = static_cast(watchers.size()); - watchers.emplace_back(); - watchers[slot].generation = 1; - } - Watcher &watcher = watchers[slot]; - watcher.coverage.reset(); - watcher.sequence_failed = watcher.sequence_expired = false; - watcher.occupied = true; - watcher.armed = true; - watcher.signal = nullptr; - watcher.remaining = 0; - watcher.initial_count = 0; - watcher.expected = 0; - watcher.expected_x_mask = 0; - watcher.expected_wide.reset(); - watcher.expected_bytes.clear(); - watcher.expected_x_bytes.clear(); - watcher.condition_mode = XConditionMode::Enter; - watcher.last_condition = false; - watcher.expr_root = -1; - watcher.sequence_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.fsm_current_state = 0; - return {slot, watcher.generation}; -} - -XRegistrationHandle XTriggerEngine::ArmEdge( - XPhase phase, uint64_t source_id) -{ - auto handle = Allocate(); - if (!handle.IsValid()) return handle; - auto &watcher = watchers[handle.slot]; - watcher.kind = WatcherKind::Edge; - watcher.phase = phase; - watcher.source_id = source_id ? source_id : clock->CSelf(); - return handle; -} - -XRegistrationHandle XTriggerEngine::ArmClockCycles( - uint64_t cycles, XPhase phase, uint64_t source_id) -{ - if (cycles == 0) return {}; - auto handle = Allocate(); - if (!handle.IsValid()) return handle; - auto &watcher = watchers[handle.slot]; - watcher.kind = WatcherKind::ClockCycles; - watcher.phase = phase; - watcher.source_id = source_id ? source_id : clock->CSelf(); - watcher.remaining = cycles; - watcher.initial_count = cycles; - return handle; -} - -XRegistrationHandle XTriggerEngine::ArmValueEq( - XData *signal, uint64_t expected, XPhase phase, XConditionMode mode, - uint64_t source_id) -{ - if (signal == nullptr || signal->W() > 64) return {}; - auto handle = Allocate(); - if (!handle.IsValid()) return handle; - auto &watcher = watchers[handle.slot]; - watcher.kind = WatcherKind::ValueEq; - watcher.phase = phase; - watcher.source_id = source_id ? source_id : signal->CSelf(); - watcher.signal = signal; - watcher.expected = expected; - watcher.condition_mode = mode; - return handle; -} - -XRegistrationHandle XTriggerEngine::ArmValueEqBytes( - XData *signal, std::vector &expected, XPhase phase, - XConditionMode mode, uint64_t source_id) -{ - if (signal == nullptr || signal->W() <= 64) return {}; - auto handle = Allocate(); - if (!handle.IsValid()) return handle; - auto &watcher = watchers[handle.slot]; - watcher.kind = WatcherKind::ValueEq; - watcher.phase = phase; - watcher.source_id = source_id ? source_id : signal->CSelf(); - watcher.signal = signal; - watcher.expected_wide = std::make_shared(signal->W(), XData::InOut); - watcher.expected_wide->SetVU8(expected); - watcher.condition_mode = mode; - return handle; -} - -XRegistrationHandle XTriggerEngine::ArmValueChange( - XData *signal, XPhase phase, uint64_t source_id) -{ - if (signal == nullptr) return {}; - auto handle = Allocate(); - if (!handle.IsValid()) return handle; - auto &watcher = watchers[handle.slot]; - watcher.kind = WatcherKind::ValueChange; - watcher.phase = phase; - watcher.source_id = source_id ? source_id : signal->CSelf(); - watcher.signal = signal; - if (signal->W() > 64) { - watcher.expected_bytes = signal->GetVU8(); - watcher.expected_x_bytes = signal->GetBvalBytes(); - } else { - watcher.expected = signal->U(); - watcher.expected_x_mask = signal->XMask(); - } - return handle; -} - -XRegistrationHandle XTriggerEngine::ArmSample( - XPhase phase, uint64_t source_id) -{ - auto handle = Allocate(); - if (!handle.IsValid()) return handle; - auto &watcher = watchers[handle.slot]; - watcher.kind = WatcherKind::Sample; - watcher.phase = phase; - watcher.source_id = source_id; - return handle; -} - -int XTriggerEngine::ExprNewConst(uint64_t value) -{ - return expr_engine->NewConst(value); -} - -int XTriggerEngine::ExprNewSignal(XData *signal) -{ - if (signal == nullptr || signal->W() > 64) return -1; - return expr_engine->NewSignal(signal); -} - -int XTriggerEngine::ExprNewUnary(int op, int child) -{ - return expr_engine->NewUnary(static_cast(op), child); -} - -int XTriggerEngine::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) -{ - return expr_engine->NewCompare(static_cast(op), lhs, rhs); -} - -int XTriggerEngine::ExprNewCompareSigSig(int op, XData *lhs, XData *rhs) -{ - return expr_engine->NewCompareSigSig(static_cast(op), lhs, rhs); -} - -int XTriggerEngine::ExprNewCompareSigConstBytes( - int op, XData *lhs, std::vector &rhs) -{ - return expr_engine->NewCompareSigConstBytes( - static_cast(op), lhs, rhs); -} - -int XTriggerEngine::ExprNewCompareConstBytesSig( - int op, std::vector &lhs, XData *rhs) -{ - return expr_engine->NewCompareConstBytesSig( - static_cast(op), lhs, rhs); -} - -int XTriggerEngine::ExprNewMaskedCompareSigConstBytes( - XData *lhs, std::vector &value, std::vector &mask) -{ - const int root = expr_engine->NewMaskedCompareSigConstBytes(lhs, value, mask); - if (root < 0) throw std::invalid_argument("invalid masked signal comparison"); - return root; -} - -XRegistrationHandle XTriggerEngine::ArmExpr( - int root, XPhase phase, XConditionMode mode, uint64_t source_id) -{ - if (root < 0) 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; - expr_engine->OptimizeShortCircuitOrder(root); - return handle; -} - -XRegistrationHandle XTriggerEngine::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 {}; - } - } - 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; - for (const auto &step : steps) { - expr_engine->OptimizeShortCircuitOrder(step.root); - } - return handle; -} - -XRegistrationHandle XTriggerEngine::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 {}; - } - } - 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.fsm_current_state = start_state; - for (const auto &transition : transitions) { - if (transition.root >= 0) { - expr_engine->OptimizeShortCircuitOrder(transition.root); - } - } - return handle; -} - -bool XTriggerEngine::Disarm(XRegistrationHandle handle) -{ - if (!handle.IsValid() || handle.slot >= watchers.size()) return false; - auto &watcher = watchers[handle.slot]; - if (!watcher.occupied || watcher.generation != handle.generation) { - return false; - } - watcher.coverage.reset(); - watcher.occupied = false; - watcher.armed = false; - watcher.signal = nullptr; - watcher.expected_wide.reset(); - watcher.expected_bytes.clear(); - watcher.expected_x_bytes.clear(); - free_slots.push_back(handle.slot); - return true; -} - -bool XTriggerEngine::RearmSample(XRegistrationHandle handle) -{ - if (!handle.IsValid() || handle.slot >= watchers.size()) return false; - auto &watcher = watchers[handle.slot]; - if (!watcher.occupied || watcher.generation != handle.generation || - watcher.kind != WatcherKind::Sample || watcher.armed) { - return false; - } - watcher.armed = true; - return true; -} - -bool XTriggerEngine::Rearm(XRegistrationHandle handle) -{ - if (!handle.IsValid() || handle.slot >= watchers.size()) return false; - auto &watcher = watchers[handle.slot]; - if (!watcher.occupied || watcher.generation != handle.generation || - watcher.armed) { - return false; - } - watcher.armed = true; - if (watcher.kind == WatcherKind::ClockCycles) { - watcher.remaining = watcher.initial_count; - } else if (watcher.kind == WatcherKind::Sequence) { - watcher.sequence_index = 0; - watcher.sequence_age = 0; - watcher.sequence_held = 0; - } else if (watcher.kind == WatcherKind::Fsm) { - watcher.fsm_current_state = watcher.fsm_start_state; - } else if (watcher.kind == WatcherKind::ValueChange) { - if (watcher.signal->W() > 64) { - watcher.expected_bytes = watcher.signal->GetVU8(); - watcher.expected_x_bytes = watcher.signal->GetBvalBytes(); - } else { - watcher.expected = watcher.signal->U(); - watcher.expected_x_mask = watcher.signal->XMask(); - } - } - return true; -} - -void XTriggerEngine::AppendHit( - uint32_t slot, Watcher &watcher, XHitKind kind, uint64_t value, - 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; - return; - } - if (event_id == 0) event_id = next_event_id++; - hit_buffer.push_back({ - event_id, - clock->GetHalfTick(), - watcher.source_id, - value, - x_mask, - slot, - watcher.generation, - kind, - evaluation_phase, - 0, - }); - watcher.armed = false; -} - -bool XTriggerEngine::AdvanceSequence(const std::vector &steps, MatchState &watcher) -{ - 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; - } - 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) -{ - 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; - 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; - } - } - return false; -} - -void XTriggerEngine::EvaluatePhase(XPhase phase) -{ - evaluation_phase = phase; - uint64_t edge_event_id = 0; - expr_engine->SetCycle(clock->GetHalfTick()); - for (uint32_t slot = 0; slot < watchers.size(); ++slot) { - auto &watcher = watchers[slot]; - if (!watcher.occupied || !watcher.armed || - 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; - } - } - switch (watcher.kind) { - case 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 = 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; - 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 = 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; - if (emit) { - AppendHit(slot, watcher, XHitKind::Condition, current ? 1 : 0); - } - break; - } - case WatcherKind::Sequence: - if (AdvanceSequence(watcher.sequence_steps, watcher)) { - 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); - break; - } - } - } -} - -XRunResult XTriggerEngine::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(); - if (budget_check_interval == 0) budget_check_interval = 1; - - for (uint64_t i = 0; i < max_half_ticks; ++i) { - if (!clock->StepHalf()) { - result.stop_reason = XStopReason::BackendStop; - result.hits = hit_buffer; - return result; - } - result.advanced_ticks += 1; - result.stopped_phase = clock->GetPhase(); - EvaluatePhase(clock->GetPhase()); - if (!hit_buffer.empty()) { - result.stop_reason = XStopReason::TriggerHit; - for (const auto &hit : hit_buffer) { - if (hit.kind == XHitKind::ClockFall || - hit.kind == XHitKind::ClockRise) { - result.stop_reason = XStopReason::EdgeBarrier; - break; - } - } - result.hits = hit_buffer; - return result; - } - if (clock->GetPhase() == XPhase::FallingStable && - HasArmedPhase(XPhase::DriveStable)) { - result.stop_reason = XStopReason::EdgeBarrier; - result.hits = hit_buffer; - return result; - } - if (max_wall_time_ns != 0 && - (i + 1) % budget_check_interval == 0) { - const auto elapsed = std::chrono::duration_cast< - std::chrono::nanoseconds>( - std::chrono::steady_clock::now() - started); - if (static_cast(elapsed.count()) >= max_wall_time_ns) { - result.stop_reason = XStopReason::QuantumExpired; - result.hits = hit_buffer; - return result; - } - } - } - result.stop_reason = XStopReason::RunLimit; - result.hits = hit_buffer; - return result; -} - -XRunResult XTriggerEngine::SamplePhase(XPhase phase) -{ - if (phase != XPhase::DriveStable || - clock->GetPhase() != XPhase::FallingStable) { - throw std::logic_error( - "DriveStable may only be sampled after FallingStable"); - } - XRunResult result; - result.stopped_phase = phase; - result.stop_reason = XStopReason::EdgeBarrier; - hit_buffer.clear(); - clock->RefreshComb(); - EvaluatePhase(phase); - if (!hit_buffer.empty()) { - result.stop_reason = XStopReason::TriggerHit; - } - result.hits = hit_buffer; - return result; -} - - -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; - for (const auto &watcher : watchers) { - if (watcher.occupied) count += 1; - } - return count; -} - -void XTriggerEngine::ClearExecutionState() -{ - if (ActiveCount() != 0) { - throw std::logic_error( - "cannot reset trigger runtime with active watchers"); - } - for (auto &watcher : watchers) { - watcher.signal = nullptr; - 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; - } - hit_buffer.clear(); - expr_engine->Clear(); -} - -void XTriggerEngine::Clear() -{ - watchers.clear(); - free_slots.clear(); - hit_buffer.clear(); - next_event_id = 1; - expr_engine->Clear(); -} - -} // 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..c355bce 100644 --- a/swig/javascript/CMakeLists.txt +++ b/swig/javascript/CMakeLists.txt @@ -4,13 +4,12 @@ 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}/src + ${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 bb4fb52..c277aa2 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}") @@ -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/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 cfe5c67..297a65c 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 %} @@ -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 @@ -53,20 +53,12 @@ %ignore xspcomm::XPort::SelectPins(std::initializer_list); namespace std { + %template(XIntVector) vector; %template(StringVector) vector; %template(XDataVector) vector>; %template(DictStrBool) map; } -%include "xspcomm/xclock.h" -%include "xspcomm/xdata.h" -%include "xspcomm/xport.h" -%include "xspcomm/xutil.h" -%include "xspcomm/xsignal_cfg.h" -%include "xspcomm/xcomuse_base.h" -%include "xspcomm/xexpr.h" -%include "xspcomm/xfsm.h" -%include "xspcomm/xcomuse.h" %include exception.i %exception { try { $action } @@ -77,12 +69,31 @@ 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/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/monitor/expr.h" +%include "xspcomm/monitor/fsm.h" +%include "xspcomm/xmonitor.h" +%include "xspcomm/xpattern.h" +%include "xspcomm/xcoverage.h" +%rename(XTriggerEngine) xspcomm::XEngine; +%include "xspcomm/xengine.h" %include "xspcomm/xtrigger.h" %exception; namespace std { %template(XBackendHitVector) vector; - %template(XCoverageItemVector) vector; + %template(XCoveragePointVector) vector; %template(XCoverageBinVector) vector; %template(XUInt32Vector) vector; %template(XUInt64Vector) vector; @@ -130,7 +141,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); @@ -148,6 +159,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 56ee974..fea665f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,31 +1,18 @@ -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 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) + 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) + 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/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 95% rename from tests/test_python.py rename to tests/bindings/python/test_python.py index 596c3f3..aad9d79 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 * @@ -114,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' @@ -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/test_xcomuse_base.cpp b/tests/test_xcomuse_base.cpp deleted file mode 100644 index 04d77a6..0000000 --- a/tests/test_xcomuse_base.cpp +++ /dev/null @@ -1,161 +0,0 @@ -#define CATCH_CONFIG_MAIN -#include "catch.hpp" - -#include "xspcomm/xcomuse_base.h" -#include "xspcomm/xclock.h" -#include "xspcomm/xdata.h" - -using namespace xspcomm; - -namespace { -struct StepCbProbe : public ComUseStepCb { - int calls = 0; - void Call() override { - calls++; - IncCbCount(); - } -}; -} - -TEST_CASE("ComUseStepCb basic behavior", "[xcomuse_base]") { - StepCbProbe cb; - REQUIRE(cb.IsDisable() == false); - REQUIRE(cb.GetCbCount() == 0); - - cb.Disable(); - REQUIRE(cb.IsDisable() == true); - cb.Enable(); - REQUIRE(cb.IsDisable() == false); - - cb.SetMaxCbs(2); - auto fn = (void (*)(uint64_t, void*))ComUseStepCb::GetCb(); - fn(1, &cb); - REQUIRE(cb.calls == 1); - REQUIRE(cb.GetCbCount() == 1); - REQUIRE(cb.IsDisable() == false); - - fn(2, &cb); - REQUIRE(cb.calls == 2); - REQUIRE(cb.GetCbCount() == 2); - REQUIRE(cb.IsDisable() == true); - - fn(3, &cb); - REQUIRE(cb.calls == 2); - REQUIRE(cb.GetCbCount() == 2); -} - -TEST_CASE("ComUseCondCheck xdata conditions", "[xcomuse_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(); - fn(1, &checker); - - auto keys = checker.GetTriggeredConditionKeys(); - REQUIRE(keys.size() == 1); - REQUIRE(keys[0] == "eq"); - REQUIRE(checker.ListCondition()["eq"] == true); - REQUIRE(clk.IsDisable() == true); -} - -TEST_CASE("ComUseCondCheck valid gating", "[xcomuse_base]") { - XData a(8, XData::InOut); - XData b(8, XData::InOut); - XData valid(1, XData::InOut); - XData valid_val(1, XData::InOut); - - a = 1; - b = 1; - 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(); - - fn(1, &checker); - REQUIRE(checker.GetTriggeredConditionKeys().empty()); - - valid = 1; - fn(2, &checker); - REQUIRE(checker.GetTriggeredConditionKeys().size() == 1); -} - -TEST_CASE("ComUseCondCheck pointer conditions", "[xcomuse_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)); - - auto fn = (void (*)(uint64_t, void*))ComUseStepCb::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)); - fn(2, &checker2); - REQUIRE(checker2.GetTriggeredConditionKeys().size() == 1); -} - -TEST_CASE("ComUseDataArray and helpers", "[xcomuse_base]") { - ComUseDataArray arr(10); - REQUIRE(arr.Size() == 10); - arr.SetZero(); - auto bytes = arr.AsBytes(); - REQUIRE(bytes.size() == 10); - - std::vector input = {1, 2, 3, 4, 5}; - arr.FromBytes(input); - auto out = arr.AsBytes(); - REQUIRE(out[0] == 1); - REQUIRE(out[4] == 5); - - auto copy = std::unique_ptr(arr.Copy()); - REQUIRE((*copy) == arr); - - unsigned char buf[4] = {9, 8, 7, 6}; - arr.SyncFrom((uint64_t)buf, 4); - auto out2 = arr.AsBytes(); - REQUIRE(out2[0] == 9); - - uint32_t u32s[2] = {0, 0}; - SetU32Array((uint64_t)u32s, 1, 0x1234); - 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); - - uint64_t a = 10; - uint64_t b = 12; - ComUseRangeCheck rc(2, 8); - auto fn = rc.GetArrayCmp(); - REQUIRE(fn((uint64_t)&a, (uint64_t)&b, rc.CSelf()) == true); -} - -TEST_CASE("CString basic", "[xcomuse_base]") { - CString s("abc"); - REQUIRE(s.Get() == "abc"); - s.Set("def"); - REQUIRE(s.Get() == "def"); - s.AssignFrom("xyz"); - REQUIRE(s.Get() == "xyz"); - REQUIRE(s.CharAddress() != 0); -} - -TEST_CASE("ComUseEcho smoke", "[xcomuse_base]") { - XData valid(1, XData::InOut); - XData data(8, XData::InOut); - valid = 1; - data = 'A'; - ComUseEcho echo(valid.CSelf(), data.CSelf(), false, "%c", 0); - echo.Call(); -} diff --git a/tests/test_xdata.cpp b/tests/test_xdata.cpp deleted file mode 100644 index a9e22da..0000000 --- a/tests/test_xdata.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "xspcomm/xcomm.h" -#include "xspcomm/xinstance.h" - -using namespace xspcomm; - -int main(int argsc, const char **argsv) -{ - Debug("version: %s", version().c_str()); - checkVersion(); - test_xdata(); - return 0; -} diff --git a/tests/test_xexpr.cpp b/tests/test_xexpr.cpp deleted file mode 100644 index cca7de6..0000000 --- a/tests/test_xexpr.cpp +++ /dev/null @@ -1,179 +0,0 @@ -#define CATCH_CONFIG_MAIN -#include "catch.hpp" - -#include "xspcomm/xexpr.h" -#include "xspcomm/xdata.h" -#include "xspcomm/xsignal_cfg.h" - -using namespace xspcomm; - -TEST_CASE("ExprEngine arithmetic and precedence", "[xexpr]") { - XData a(32, XData::InOut); - XData b(32, XData::InOut); - a = 3; - b = 4; - - ExprEngine eng; - eng.RegisterExternalSignal("a", &a); - eng.RegisterExternalSignal("b", &b); - - int root = eng.CompileExpr("a + 2 * b", nullptr); - REQUIRE(eng.Eval(root) == 11); - - int root2 = eng.CompileExpr("(a + 2) * b", nullptr); - REQUIRE(eng.Eval(root2) == 20); -} - -TEST_CASE("ExprEngine parses hexadecimal integer literals", "[xexpr]") { - ExprEngine eng; - - REQUIRE(eng.Eval(eng.CompileExpr("0xff", nullptr)) == 0xff); - REQUIRE(eng.Eval(eng.CompileExpr("0XDEAD_BEEFULL", nullptr)) == 0xdeadbeefULL); - REQUIRE(eng.Eval(eng.CompileExpr("0x10U + 1", nullptr)) == 0x11); - REQUIRE_THROWS(eng.CompileExpr("0x1G", nullptr)); - REQUIRE_THROWS(eng.CompileExpr("0x1UUU", nullptr)); - REQUIRE_THROWS(eng.CompileExpr("0b", nullptr)); -} - -TEST_CASE("XSignalCFG parses hexadecimal const signals", "[xexpr][xsignal_cfg]") { - const std::string cfg_text = R"yaml( -variables: [] -signals: - - name: hex_const - kind: const - type: IData - rtl_width: 32 - value: "0xDEAD_BEEFU" - - name: shifted_const - kind: const - type: IData - rtl_width: 32 - value: "1U << 4U" - - name: verilator_macro_const - kind: const - type: QData - rtl_width: 64 - value: "VL_ULL(0x1234_ABCDULL)" -)yaml"; - - XSignalCFG cfg(cfg_text); - std::unique_ptr hex_signal(cfg.NewXData("hex_const")); - std::unique_ptr shifted_signal(cfg.NewXData("shifted_const")); - std::unique_ptr macro_signal(cfg.NewXData("verilator_macro_const")); - - REQUIRE(hex_signal != nullptr); - REQUIRE(hex_signal->U() == 0xdeadbeefULL); - REQUIRE(shifted_signal != nullptr); - REQUIRE(shifted_signal->U() == 0x10); - REQUIRE(macro_signal != nullptr); - REQUIRE(macro_signal->U() == 0x1234abcdULL); -} - -TEST_CASE("ExprEngine logical ops and keywords", "[xexpr]") { - XData a(1, XData::InOut); - XData b(1, XData::InOut); - a = 0; - b = 1; - - ExprEngine eng; - eng.RegisterExternalSignal("a", &a); - eng.RegisterExternalSignal("b", &b); - - int root = eng.CompileExpr("a || b", nullptr); - REQUIRE(eng.Eval(root) == 1); - - int root2 = eng.CompileExpr("not a && b", nullptr); - REQUIRE(eng.Eval(root2) == 1); -} - -TEST_CASE("ExprEngine within/hold", "[xexpr]") { - XData a(1, XData::InOut); - ExprEngine eng; - eng.RegisterExternalSignal("a", &a); - - int within_root = eng.CompileExpr("within(2, a)", nullptr); - int hold_root = eng.CompileExpr("hold(2, a)", nullptr); - - a = 1; - eng.SetCycle(1); - REQUIRE(eng.Eval(within_root) == 1); - REQUIRE(eng.Eval(hold_root) == 0); - - a = 0; - eng.SetCycle(2); - REQUIRE(eng.Eval(within_root) == 1); - REQUIRE(eng.Eval(hold_root) == 0); - - a = 1; - eng.SetCycle(3); - REQUIRE(eng.Eval(within_root) == 1); - REQUIRE(eng.Eval(hold_root) == 0); - - a = 1; - eng.SetCycle(4); - REQUIRE(eng.Eval(hold_root) == 1); - - a = 0; - eng.SetCycle(5); - REQUIRE(eng.Eval(hold_root) == 0); -} - -TEST_CASE("ExprEngine wide signal compare", "[xexpr]") { - XData w(128, XData::InOut); - ExprEngine eng; - eng.RegisterExternalSignal("w", &w); - - int root = eng.CompileExpr("w == 1", nullptr); - w = "0x1"; - REQUIRE(eng.Eval(root) == 1); - - REQUIRE_THROWS(eng.CompileExpr("w + 1", nullptr)); -} - -TEST_CASE("ExprEngine parser errors", "[xexpr]") { - ExprEngine eng; - REQUIRE_THROWS(eng.CompileExpr("", nullptr)); - REQUIRE_THROWS(eng.CompileExpr("a @ b", nullptr)); -} - -TEST_CASE("ComUseExprCheck reports every expression hit in one call", - "[xexpr][trigger]") { - ComUseExprCheck checker; - checker.SetExpr("first", checker.ExprNewConst(1)); - checker.SetExpr("second", checker.ExprNewConst(1)); - - checker.Call(); - const auto keys = checker.GetTriggeredExprKeys(); - - REQUIRE(keys.size() == 2); - REQUIRE(keys[0] == "first"); - REQUIRE(keys[1] == "second"); - REQUIRE(checker.GetCbCount() == 1); -} - - -TEST_CASE("ExprEngine masked comparison preserves wide bits and unknowns", "[xexpr]") { - ExprEngine engine; - XData signal(65, XData::InOut); - std::vector value(9, 0), mask(9, 0); - value[0] = 1; value[8] = 1; - mask[0] = 3; mask[8] = 1; - const int root = engine.NewMaskedCompareSigConstBytes(&signal, value, mask); - REQUIRE(root >= 0); - signal = "0x10000000000000005"; - REQUIRE(engine.IsKnown(root)); - REQUIRE(engine.Eval(root) == 1); - signal = "0x5"; - REQUIRE(engine.Eval(root) == 0); - signal = "0x10000000000000006"; - REQUIRE(engine.Eval(root) == 0); - signal = "0bx0000000000000000000000000000000000000000000000000000000000000001"; - REQUIRE_FALSE(engine.IsKnown(root)); - REQUIRE(engine.Eval(root) == 0); - mask[8] = 2; - REQUIRE(engine.NewMaskedCompareSigConstBytes(&signal, value, mask) == -1); - mask.pop_back(); - REQUIRE(engine.NewMaskedCompareSigConstBytes(&signal, value, mask) == -1); - engine.Clear(); - REQUIRE_FALSE(engine.ValidRoot(root)); -} 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..5fdae2b --- /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; + XCoveragePoint point; + point.signal = &value; + XCoverageBin bin; + bin.program.root = expr.NewConst(1); + 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); + 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}; + XCoveragePoint point; + point.signal = &value; + XCoverageBin bin; + bin.program.root = expr.NewConst(1); + CoverageState coverage(1, expr, source, {point}, {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, 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, 4); + REQUIRE(coverage.Snapshot(4, true).counters[0] == 0); +} 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_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/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/unit/test_xcoverage.cpp b/tests/unit/test_xcoverage.cpp new file mode 100644 index 0000000..c5419ff --- /dev/null +++ b/tests/unit/test_xcoverage.cpp @@ -0,0 +1,435 @@ +#define CATCH_CONFIG_MAIN +#include "catch.hpp" + +#include "xspcomm/xtrigger.h" +#include "xspcomm/xexpr.h" + +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); + 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)); + XCoveragePoint point; + point.signal = &value; + XCoverageBin bin; + bin.program.root = one; + auto handle = engine.ArmEdge(XPhase::RisingStable); + engine.AttachCoverage(handle, {point}, {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); + 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)); + 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, {point}, {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, {point}, {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); + engine.AcquireClock(); + auto h = engine.ArmEdge(XPhase::RisingStable); + XCoveragePoint point; + XCoverageBin bin; + XData value(8, XData::InOut); + point.signal = &value; + bin.program.root = 100000; + REQUIRE_THROWS(engine.AttachCoverage(h, {point}, {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); + engine.AcquireClock(); + XData value(128, XData::InOut); + value = "0x80000000000000000000000000000007"; + auto bytes = value.GetBytes(); + 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, {point}, {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); + 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}}); + XCoveragePoint point; point.signal = &value; + XCoverageBin bin; bin.program.root = engine.ExprNewConst(1); + 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); + 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); + 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)}}); + XCoveragePoint point; point.signal = &value; + XCoverageBin bin; bin.program.root = engine.ExprNewConst(1); + 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); + 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); +} + +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); + 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, {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()); + 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); + engine.AcquireClock(); + XData start(1, XData::InOut), success(1, XData::InOut), failure(1, XData::InOut); + 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, {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); + 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); + engine.AcquireClock(); + XData start(1, XData::InOut), done(1, XData::InOut); + auto h = engine.ArmSample(XPhase::RisingStable); + 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, {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); + REQUIRE(engine.Disarm(h)); +} + +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); + 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, {point}, {invalid})); + invalid = bin; invalid.program.max_active = 0; + 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, {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)); +} + +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); + 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, {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); + 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); + engine.AcquireClock(); + XData request(1, XData::InOut), response(1, XData::InOut); + const auto req = engine.ExprNewSignal(&request), rsp = engine.ExprNewSignal(&response); + 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}, + {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, {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()); + 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); + engine.AcquireClock(); + XData request(1, XData::InOut), response(1, XData::InOut); + const auto req = engine.ExprNewSignal(&request), rsp = engine.ExprNewSignal(&response); + 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.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, {point, point}, {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)); +} + +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); + XCoveragePoint point; + XCoverageBin bin; + bin.program.root = root; + 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, {point}, {bin}), std::invalid_argument); + bin.program.transitions.clear(); + bin.program.kind = static_cast(99); + REQUIRE_THROWS_AS(engine.AttachCoverage(handle, {point}, {bin}), std::invalid_argument); + bin.program.kind = XTriggerProgramKind::Expr; + engine.AttachCoverage(handle, {point}, {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); + engine.AcquireClock(); + const auto root = engine.ExprNewConst(1); + const std::vector invalid{{static_cast(99), root}}; + REQUIRE_FALSE(engine.ArmSequence(invalid).IsValid()); + 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, {point}, {bin}), std::invalid_argument); +} diff --git a/tests/unit/test_xdata.cpp b/tests/unit/test_xdata.cpp new file mode 100644 index 0000000..2e7cb56 --- /dev/null +++ b/tests/unit/test_xdata.cpp @@ -0,0 +1,203 @@ +#include "xspcomm/xcomm.h" +namespace xspcomm { int test_xdata(); } +#include +#include +#include + +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); + } +} + +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_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); + 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_native_vector_boundaries(); + test_native_reinit(); + test_signed_values_and_slices(); + test_bit_buffer_boundaries(); + Assert(test_xdata() == 0, "XData legacy cases failed"); + return 0; +} diff --git a/tests/unit/test_xexpr.cpp b/tests/unit/test_xexpr.cpp new file mode 100644 index 0000000..a1d668a --- /dev/null +++ b/tests/unit/test_xexpr.cpp @@ -0,0 +1,285 @@ +#define CATCH_CONFIG_MAIN +#include "catch.hpp" + +#include "xspcomm/xexpr.h" +#include "xspcomm/monitor/expr.h" +#include "xspcomm/xdata.h" +#include "xspcomm/xsignal_cfg.h" + +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); + a = 3; + b = 4; + + ExprEngine eng; + eng.RegisterExternalSignal("a", &a); + eng.RegisterExternalSignal("b", &b); + + int root = eng.CompileExpr("a + 2 * b", nullptr); + REQUIRE(eng.Eval(root) == 11); + + int root2 = eng.CompileExpr("(a + 2) * b", nullptr); + REQUIRE(eng.Eval(root2) == 20); +} + +TEST_CASE("ExprEngine parses hexadecimal integer literals", "[xexpr]") { + ExprEngine eng; + + REQUIRE(eng.Eval(eng.CompileExpr("0xff", nullptr)) == 0xff); + REQUIRE(eng.Eval(eng.CompileExpr("0XDEAD_BEEFULL", nullptr)) == 0xdeadbeefULL); + REQUIRE(eng.Eval(eng.CompileExpr("0x10U + 1", nullptr)) == 0x11); + REQUIRE_THROWS(eng.CompileExpr("0x1G", nullptr)); + REQUIRE_THROWS(eng.CompileExpr("0x1UUU", nullptr)); + REQUIRE_THROWS(eng.CompileExpr("0b", nullptr)); +} + +TEST_CASE("XSignalCFG parses hexadecimal const signals", "[xexpr][xsignal_cfg]") { + const std::string cfg_text = R"yaml( +variables: [] +signals: + - name: hex_const + kind: const + type: IData + rtl_width: 32 + value: "0xDEAD_BEEFU" + - name: shifted_const + kind: const + type: IData + rtl_width: 32 + value: "1U << 4U" + - name: verilator_macro_const + kind: const + type: QData + rtl_width: 64 + value: "VL_ULL(0x1234_ABCDULL)" +)yaml"; + + XSignalCFG cfg(cfg_text); + std::unique_ptr hex_signal(cfg.NewXData("hex_const")); + std::unique_ptr shifted_signal(cfg.NewXData("shifted_const")); + std::unique_ptr macro_signal(cfg.NewXData("verilator_macro_const")); + + REQUIRE(hex_signal != nullptr); + REQUIRE(hex_signal->U() == 0xdeadbeefULL); + REQUIRE(shifted_signal != nullptr); + REQUIRE(shifted_signal->U() == 0x10); + REQUIRE(macro_signal != nullptr); + REQUIRE(macro_signal->U() == 0x1234abcdULL); +} + +TEST_CASE("ExprEngine logical ops and keywords", "[xexpr]") { + XData a(1, XData::InOut); + XData b(1, XData::InOut); + a = 0; + b = 1; + + ExprEngine eng; + eng.RegisterExternalSignal("a", &a); + eng.RegisterExternalSignal("b", &b); + + int root = eng.CompileExpr("a || b", nullptr); + REQUIRE(eng.Eval(root) == 1); + + int root2 = eng.CompileExpr("not a && b", nullptr); + REQUIRE(eng.Eval(root2) == 1); +} + +TEST_CASE("ExprEngine within/hold", "[xexpr]") { + XData a(1, XData::InOut); + ExprEngine eng; + eng.RegisterExternalSignal("a", &a); + + int within_root = eng.CompileExpr("within(2, a)", nullptr); + int hold_root = eng.CompileExpr("hold(2, a)", nullptr); + + a = 1; + eng.SetCycle(1); + REQUIRE(eng.Eval(within_root) == 1); + REQUIRE(eng.Eval(hold_root) == 0); + + a = 0; + eng.SetCycle(2); + REQUIRE(eng.Eval(within_root) == 1); + REQUIRE(eng.Eval(hold_root) == 0); + + a = 1; + eng.SetCycle(3); + REQUIRE(eng.Eval(within_root) == 1); + REQUIRE(eng.Eval(hold_root) == 0); + + a = 1; + eng.SetCycle(4); + REQUIRE(eng.Eval(hold_root) == 1); + + a = 0; + eng.SetCycle(5); + REQUIRE(eng.Eval(hold_root) == 0); +} + +TEST_CASE("ExprEngine wide signal compare", "[xexpr]") { + XData w(128, XData::InOut); + ExprEngine eng; + eng.RegisterExternalSignal("w", &w); + + int root = eng.CompileExpr("w == 1", nullptr); + w = "0x1"; + REQUIRE(eng.Eval(root) == 1); + + REQUIRE_THROWS(eng.CompileExpr("w + 1", nullptr)); +} + +TEST_CASE("ExprEngine parser errors", "[xexpr]") { + ExprEngine eng; + REQUIRE_THROWS(eng.CompileExpr("", nullptr)); + REQUIRE_THROWS(eng.CompileExpr("a @ b", nullptr)); +} + +TEST_CASE("XExprCheck reports every expression hit in one call", + "[xexpr][trigger]") { + XExprCheck checker; + checker.SetExpr("first", checker.ExprNewConst(1)); + checker.SetExpr("second", checker.ExprNewConst(1)); + + checker.Call(); + const auto keys = checker.GetTriggeredExprKeys(); + + REQUIRE(keys.size() == 2); + REQUIRE(keys[0] == "first"); + REQUIRE(keys[1] == "second"); + REQUIRE(checker.GetCbCount() == 1); +} + + +TEST_CASE("ExprEngine masked comparison preserves wide bits and unknowns", "[xexpr]") { + ExprEngine engine; + XData signal(65, XData::InOut); + std::vector value(9, 0), mask(9, 0); + value[0] = 1; value[8] = 1; + mask[0] = 3; mask[8] = 1; + const int root = engine.NewMaskedCompareSigConstBytes(&signal, value, mask); + REQUIRE(root >= 0); + signal = "0x10000000000000005"; + REQUIRE(engine.IsKnown(root)); + REQUIRE(engine.Eval(root) == 1); + signal = "0x5"; + REQUIRE(engine.Eval(root) == 0); + signal = "0x10000000000000006"; + REQUIRE(engine.Eval(root) == 0); + signal = "0bx0000000000000000000000000000000000000000000000000000000000000001"; + REQUIRE_FALSE(engine.IsKnown(root)); + REQUIRE(engine.Eval(root) == 0); + mask[8] = 2; + REQUIRE(engine.NewMaskedCompareSigConstBytes(&signal, value, mask) == -1); + mask.pop_back(); + REQUIRE(engine.NewMaskedCompareSigConstBytes(&signal, value, mask) == -1); + 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/test_xfsm.cpp b/tests/unit/test_xfsm.cpp similarity index 90% rename from tests/test_xfsm.cpp rename to tests/unit/test_xfsm.cpp index b47336f..19686d3 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/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_xmonitor.cpp b/tests/unit/test_xmonitor.cpp new file mode 100644 index 0000000..5f63d8d --- /dev/null +++ b/tests/unit/test_xmonitor.cpp @@ -0,0 +1,496 @@ +#define CATCH_CONFIG_MAIN +#include "catch.hpp" + +#include "xspcomm/xmonitor.h" +#include "xspcomm/xclock.h" +#include "xspcomm/xdata.h" +#include +#include +#include +#include +#include + +using namespace xspcomm; + +namespace { +struct StepCbProbe : public XStepCallback { + int calls = 0; + void Call() override { + calls++; + IncCbCount(); + } +}; +} + +TEST_CASE("XStepCallback basic behavior", "[xmonitor_base]") { + StepCbProbe cb; + REQUIRE(cb.IsDisable() == false); + REQUIRE(cb.GetCbCount() == 0); + + cb.Disable(); + REQUIRE(cb.IsDisable() == true); + cb.Enable(); + REQUIRE(cb.IsDisable() == false); + + cb.SetMaxCbs(2); + auto fn = (void (*)(uint64_t, void*))XStepCallback::GetCb(); + fn(1, &cb); + REQUIRE(cb.calls == 1); + REQUIRE(cb.GetCbCount() == 1); + REQUIRE(cb.IsDisable() == false); + + fn(2, &cb); + REQUIRE(cb.calls == 2); + REQUIRE(cb.GetCbCount() == 2); + REQUIRE(cb.IsDisable() == true); + + fn(3, &cb); + REQUIRE(cb.calls == 2); + REQUIRE(cb.GetCbCount() == 2); +} + +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; + 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(); + REQUIRE(keys.size() == 1); + REQUIRE(keys[0] == "eq"); + REQUIRE(checker.ListCondition()["eq"] == true); + REQUIRE(clk.IsDisable() == true); +} + +TEST_CASE("XConditionCheck valid gating", "[xmonitor_base]") { + XData a(8, XData::InOut); + XData b(8, XData::InOut); + XData valid(1, XData::InOut); + XData valid_val(1, XData::InOut); + + a = 1; + b = 1; + valid = 0; + valid_val = 1; + + 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()); + + valid = 1; + fn(2, &checker); + REQUIRE(checker.GetTriggeredConditionKeys().size() == 1); +} + +TEST_CASE("XConditionCheck pointer conditions", "[xmonitor_base]") { + uint32_t lhs = 5; + uint32_t rhs = 7; + XConditionCheck checker; + checker.SetCondition("gt", (uint64_t)&rhs, (uint64_t)&lhs, CompareOp::GT, sizeof(uint32_t)); + + auto fn = (void (*)(uint64_t, void*))XStepCallback::GetCb(); + fn(1, &checker); + REQUIRE(checker.GetTriggeredConditionKeys().size() == 1); + + int8_t neg = -1; + int8_t pos = 1; + 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("XByteBuffer and helpers", "[xmonitor_base]") { + XByteBuffer arr(10); + REQUIRE(arr.Size() == 10); + arr.SetZero(); + auto bytes = arr.AsBytes(); + REQUIRE(bytes.size() == 10); + + std::vector input = {1, 2, 3, 4, 5}; + arr.FromBytes(input); + auto out = arr.AsBytes(); + REQUIRE(out[0] == 1); + REQUIRE(out[4] == 5); + + auto copy = std::unique_ptr(arr.Copy()); + REQUIRE((*copy) == arr); + + unsigned char buf[4] = {9, 8, 7, 6}; + arr.SyncFrom((uint64_t)buf, 4); + auto out2 = arr.AsBytes(); + REQUIRE(out2[0] == 9); + + uint32_t u32s[2] = {0, 0}; + SetU32Array((uint64_t)u32s, 1, 0x1234); + REQUIRE(GetFromU32Array((uint64_t)u32s, 1) == 0x1234); +} + +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; + XRangeCheck rc(2, 8); + auto fn = rc.GetArrayCmp(); + REQUIRE(fn((uint64_t)&a, (uint64_t)&b, rc.CSelf()) == true); +} + +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); + 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); + XRangeCheck rc(2, bytes); + auto compare = rc.GetArrayCmp(); + REQUIRE(compare(lhs, rhs, rc.CSelf())); + REQUIRE(XRangeCheck::ArrayCmp(lhs, rhs, rc.CSelf())); + b[1] = 7; + REQUIRE_FALSE(compare(lhs, rhs, rc.CSelf())); + REQUIRE_FALSE(XRangeCheck::ArrayCmp(lhs, rhs, rc.CSelf())); + b[1] = 4; + REQUIRE(compare(lhs, rhs, rc.CSelf())); + } +} + +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("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}, + {"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); + 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(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. + std::memset(lhs.get() + 1, 0xFF, bytes); + std::memset(rhs.get() + 1, 0, bytes); + lhs[bytes] = 0; + rhs[bytes] = 1; + XRangeCheck 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())); + XRangeCheck above(-1, bytes); + REQUIRE_FALSE(XRangeCheck::ArrayCmp(y, x, above.CSelf())); + } +} + +TEST_CASE("XRangeCheck uses signal width and rejects unknowns", "[xmonitor_base]") { + // The byte count configures pointer comparisons; XData uses its actual width. + XRangeCheck 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("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}; + 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; + XRangeCheck 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", "[xmonitor_base]") { + CString s("abc"); + REQUIRE(s.Get() == "abc"); + s.Set("def"); + REQUIRE(s.Get() == "def"); + s.AssignFrom("xyz"); + REQUIRE(s.Get() == "xyz"); + REQUIRE(s.CharAddress() != 0); +} + +TEST_CASE("XEcho smoke", "[xmonitor_base]") { + XData valid(1, XData::InOut); + XData data(8, XData::InOut); + valid = 1; + data = 'A'; + XEcho echo(valid.CSelf(), data.CSelf(), false, "%c", 0); + echo.Call(); +} + + +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); + 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))); + }; + 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[] = { + {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)); + 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", "[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); + 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))); + 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")); + peer[width - 1] = "z"; + checker.Call(); + REQUIRE_FALSE(checker.ListCondition().at("eq")); + REQUIRE(checker.ListCondition().at("ne")); + } +} + +TEST_CASE("Managed Monitor callbacks detach with either lifetime order", "[xmonitor_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", "[xmonitor_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", "[xmonitor_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", "[xmonitor_base][xexpr]") { + XClock clock([](bool) { return 0; }); + XData left(129, XData::InOut), right(129, XData::InOut); + left = right = "0x100000000000000000000000000000001"; + 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); + 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), CompareOp::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", "[xmonitor_base][xexpr]") { + XExprCheck 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", "[xmonitor_base][xexpr]") { + XClock clock([](bool) { return 0; }); + XData left(129, XData::InOut), right(129, XData::InOut); + left = right = "x"; + 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), 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); + REQUIRE(observed_disabled); + REQUIRE(checker.GetCbCount() == 1); +} + +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), 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); +} + +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); + 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/test_xtrigger.cpp b/tests/unit/test_xtrigger.cpp similarity index 67% rename from tests/test_xtrigger.cpp rename to tests/unit/test_xtrigger.cpp index 6f0c332..c1217ae 100644 --- a/tests/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); @@ -398,159 +416,152 @@ TEST_CASE("XTriggerEngine samples DriveStable without advancing time", 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]") { +TEST_CASE("XEngine explicitly leases a clock until release", "[xtrigger][ownership]") { 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)); + 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("Coverage rejects malformed descriptors without changing the watcher", "[xtrigger][coverage]") { +TEST_CASE("XEngine releases ownership on destruction and checks clock lifetime", + "[xtrigger][ownership]") { 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)); + { + 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("Coverage snapshots preserve full-width illegal values", "[xtrigger][coverage]") { +TEST_CASE("XEngine rejects callback reentry and recovers from exceptions", + "[xtrigger][ownership]") { 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); + 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.CollectExpressions({}), 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("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("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(); } -TEST_CASE("Coverage overlap capacity failure preserves pending state for cleanup", "[xtrigger][coverage]") { +TEST_CASE("Collection preserves an in-flight FSM and a fired registration awaiting rearm", "[xtrigger][collection]") { 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); + 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(); } 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 96% rename from src/xinstance.cpp rename to tests/unit/xdata_cases.cpp index 5b4afb2..4fa5ada 100644 --- a/src/xinstance.cpp +++ b/tests/unit/xdata_cases.cpp @@ -1,6 +1,5 @@ #include "xspcomm/xcomm.h" -#include "xspcomm/xinstance.h" -#include "xspcomm/xcomuse.h" +#include "xspcomm/xmonitor.h" namespace xspcomm { @@ -350,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); @@ -369,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; } 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