wavekit 0.1.1__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
wavekit-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,173 @@
1
+ Metadata-Version: 2.4
2
+ Name: wavekit
3
+ Version: 0.2.0
4
+ Summary: a fundamental package for digital circuit waveform analysis
5
+ License-File: LICENSE
6
+ Author: cxzzzz
7
+ Author-email: cxz19961010@outlook.com
8
+ Requires-Python: >=3.9,<4.0
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Requires-Dist: numpy (>=2.0.0,<3.0.0)
17
+ Requires-Dist: pytest (>=8.2.2,<9.0.0)
18
+ Requires-Dist: vcdvcd (>=2.3.5,<3.0.0)
19
+ Project-URL: Repository, https://github.com/cxzzzz/wavekit
20
+ Description-Content-Type: text/markdown
21
+
22
+ # wavekit
23
+
24
+ [![PyPI version](https://img.shields.io/pypi/v/wavekit.svg)](https://pypi.org/project/wavekit/)
25
+ [![Python Versions](https://img.shields.io/pypi/pyversions/wavekit.svg)](https://pypi.org/project/wavekit/)
26
+ [![License](https://img.shields.io/github/license/cxzzzz/wavekit.svg)](LICENSE)
27
+
28
+ **Wavekit** is a fundamental Python library for digital waveform analysis. By seamlessly converting VCD and FSDB data into Numpy arrays, it empowers engineers to perform high-performance signal processing, protocol analysis, and automated verification with ease.
29
+
30
+ ## ✨ Features
31
+
32
+ - **High Performance**: Cython-optimized parsers and Numpy-backed storage deliver exceptional speed and memory efficiency for large waveform files.
33
+ - **Flexible Extraction**: Effortlessly batch-extract signals using glob patterns or regular expressions.
34
+ - **Rich Analysis Tools**: Comprehensive toolkit for bit-precise manipulation, temporal analysis, and signal transformation, streamlining complex analysis tasks with succinct, Numpy-like APIs.
35
+
36
+ ## 📦 Installation
37
+
38
+ ### Using pip (For Users)
39
+
40
+ You can install the library using pip:
41
+
42
+ ```bash
43
+ pip install wavekit
44
+ ```
45
+
46
+ **Note**: To read FSDB files, ensure the `VERDI_HOME` environment variable is set before installation.
47
+
48
+ ### From Source (For Developers)
49
+
50
+ This project uses [Poetry](https://python-poetry.org/) for dependency management.
51
+
52
+ 1. Clone the repository:
53
+
54
+ ```bash
55
+ git clone https://github.com/cxzzzz/wavekit.git
56
+ cd wavekit
57
+ ```
58
+
59
+ 2. Install dependencies:
60
+ ```bash
61
+ poetry install
62
+ ```
63
+
64
+ ## 🚀 Quick Start
65
+
66
+ ### Batch Signal Extraction
67
+
68
+ Wavekit simplifies extracting multiple signals using brace expansion or regular expressions.
69
+
70
+ **1. Using Brace Expansion**
71
+ Use brace patterns (e.g., `{state,next}` or `{0..7}`) to load related signals in one go.
72
+
73
+ ```python
74
+ from wavekit import VcdReader
75
+
76
+ with VcdReader("jtag.vcd") as f:
77
+ # Load both J_state and J_next signals
78
+ # Returns: { ('state',): Waveform(...), ('next',): Waveform(...) }
79
+ waves = f.load_matched_waveforms(
80
+ "tb.u0.J_{state,next}[3:0]",
81
+ clock="tb.tck"
82
+ )
83
+
84
+ for (suffix,), wave in waves.items():
85
+ print(f"Signal J_{suffix}: width={wave.width}")
86
+ ```
87
+
88
+ **2. Using Regular Expressions**
89
+ Prefix the pattern with `@` to enable regex matching. Capture groups `(...)` become the dictionary keys.
90
+
91
+ ```python
92
+ with VcdReader("jtag.vcd") as f:
93
+ # Use '@' prefix for regex mode
94
+ # Match signals like J_state[3:0] and J_next[3:0]
95
+ # Capture the suffix (state/next)
96
+ regex_pattern = r"tb.u0.@J_([a-z]+)"
97
+
98
+ # Returns: { ('state',): Waveform(...), ('next',): Waveform(...) }
99
+ waves = f.load_matched_waveforms(regex_pattern, clock="tb.tck")
100
+
101
+ for (name_part,), wave in waves.items():
102
+ print(f"Matched: {name_part}")
103
+ ```
104
+
105
+ ### Basic: FIFO Occupancy Analysis
106
+
107
+ Here is a simple example demonstrating how to read a VCD file and calculate the average occupancy level of a FIFO:
108
+
109
+ ```python
110
+ import numpy as np
111
+ from wavekit import VcdReader
112
+
113
+ with VcdReader("fifo_tb.vcd") as f:
114
+ clock = "fifo_tb.s_fifo.clk"
115
+ depth = 8
116
+
117
+ # Load signals with clock synchronization
118
+ w_ptr = f.load_waveform("fifo_tb.s_fifo.w_ptr[2:0]", clock=clock)
119
+ r_ptr = f.load_waveform("fifo_tb.s_fifo.r_ptr[2:0]", clock=clock)
120
+
121
+ # Perform vectorized arithmetic operations directly on waveforms
122
+ fifo_water_level = (w_ptr + depth - r_ptr) % depth
123
+
124
+ # Calculate average occupancy using Numpy
125
+ average_level = np.mean(fifo_water_level.value)
126
+ print(f"Average FIFO occupancy: {average_level:.2f}")
127
+ ```
128
+
129
+ ### Advanced: Data Validity Filtering
130
+
131
+ Use the powerful masking capabilities to filter data based on valid signals:
132
+
133
+ ```python
134
+ from wavekit import VcdReader
135
+
136
+ with VcdReader("bus_tb.vcd") as f:
137
+ clock = "top.clk"
138
+
139
+ # Load data and valid signals
140
+ data = f.load_waveform("top.bus_data[31:0]", clock=clock)
141
+ valid = f.load_waveform("top.bus_valid", clock=clock)
142
+
143
+ # Filter data where valid is high (1)
144
+ # The mask() method accepts a boolean waveform or numpy array
145
+ valid_data = data.mask(valid == 1)
146
+
147
+ # Analyze the valid transactions
148
+ print(f"Total valid transactions: {len(valid_data.value)}")
149
+ print(f"First valid data: {hex(valid_data.value[0])}")
150
+ ```
151
+
152
+ ## 🛠️ Development
153
+
154
+ This project adheres to strict code quality standards using modern Python tooling:
155
+
156
+ - **Linting & Formatting**: [Ruff](https://github.com/astral-sh/ruff)
157
+ - **Type Checking**: [Mypy](https://mypy-lang.org/)
158
+
159
+ Ensure all checks pass before submitting a Pull Request:
160
+
161
+ ```bash
162
+ # Run linting and formatting checks
163
+ poetry run ruff check .
164
+ poetry run ruff format --check .
165
+
166
+ # Run type checks
167
+ poetry run mypy .
168
+ ```
169
+
170
+ ## 📄 License
171
+
172
+ This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for details.
173
+
@@ -0,0 +1,151 @@
1
+ # wavekit
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/wavekit.svg)](https://pypi.org/project/wavekit/)
4
+ [![Python Versions](https://img.shields.io/pypi/pyversions/wavekit.svg)](https://pypi.org/project/wavekit/)
5
+ [![License](https://img.shields.io/github/license/cxzzzz/wavekit.svg)](LICENSE)
6
+
7
+ **Wavekit** is a fundamental Python library for digital waveform analysis. By seamlessly converting VCD and FSDB data into Numpy arrays, it empowers engineers to perform high-performance signal processing, protocol analysis, and automated verification with ease.
8
+
9
+ ## ✨ Features
10
+
11
+ - **High Performance**: Cython-optimized parsers and Numpy-backed storage deliver exceptional speed and memory efficiency for large waveform files.
12
+ - **Flexible Extraction**: Effortlessly batch-extract signals using glob patterns or regular expressions.
13
+ - **Rich Analysis Tools**: Comprehensive toolkit for bit-precise manipulation, temporal analysis, and signal transformation, streamlining complex analysis tasks with succinct, Numpy-like APIs.
14
+
15
+ ## 📦 Installation
16
+
17
+ ### Using pip (For Users)
18
+
19
+ You can install the library using pip:
20
+
21
+ ```bash
22
+ pip install wavekit
23
+ ```
24
+
25
+ **Note**: To read FSDB files, ensure the `VERDI_HOME` environment variable is set before installation.
26
+
27
+ ### From Source (For Developers)
28
+
29
+ This project uses [Poetry](https://python-poetry.org/) for dependency management.
30
+
31
+ 1. Clone the repository:
32
+
33
+ ```bash
34
+ git clone https://github.com/cxzzzz/wavekit.git
35
+ cd wavekit
36
+ ```
37
+
38
+ 2. Install dependencies:
39
+ ```bash
40
+ poetry install
41
+ ```
42
+
43
+ ## 🚀 Quick Start
44
+
45
+ ### Batch Signal Extraction
46
+
47
+ Wavekit simplifies extracting multiple signals using brace expansion or regular expressions.
48
+
49
+ **1. Using Brace Expansion**
50
+ Use brace patterns (e.g., `{state,next}` or `{0..7}`) to load related signals in one go.
51
+
52
+ ```python
53
+ from wavekit import VcdReader
54
+
55
+ with VcdReader("jtag.vcd") as f:
56
+ # Load both J_state and J_next signals
57
+ # Returns: { ('state',): Waveform(...), ('next',): Waveform(...) }
58
+ waves = f.load_matched_waveforms(
59
+ "tb.u0.J_{state,next}[3:0]",
60
+ clock="tb.tck"
61
+ )
62
+
63
+ for (suffix,), wave in waves.items():
64
+ print(f"Signal J_{suffix}: width={wave.width}")
65
+ ```
66
+
67
+ **2. Using Regular Expressions**
68
+ Prefix the pattern with `@` to enable regex matching. Capture groups `(...)` become the dictionary keys.
69
+
70
+ ```python
71
+ with VcdReader("jtag.vcd") as f:
72
+ # Use '@' prefix for regex mode
73
+ # Match signals like J_state[3:0] and J_next[3:0]
74
+ # Capture the suffix (state/next)
75
+ regex_pattern = r"tb.u0.@J_([a-z]+)"
76
+
77
+ # Returns: { ('state',): Waveform(...), ('next',): Waveform(...) }
78
+ waves = f.load_matched_waveforms(regex_pattern, clock="tb.tck")
79
+
80
+ for (name_part,), wave in waves.items():
81
+ print(f"Matched: {name_part}")
82
+ ```
83
+
84
+ ### Basic: FIFO Occupancy Analysis
85
+
86
+ Here is a simple example demonstrating how to read a VCD file and calculate the average occupancy level of a FIFO:
87
+
88
+ ```python
89
+ import numpy as np
90
+ from wavekit import VcdReader
91
+
92
+ with VcdReader("fifo_tb.vcd") as f:
93
+ clock = "fifo_tb.s_fifo.clk"
94
+ depth = 8
95
+
96
+ # Load signals with clock synchronization
97
+ w_ptr = f.load_waveform("fifo_tb.s_fifo.w_ptr[2:0]", clock=clock)
98
+ r_ptr = f.load_waveform("fifo_tb.s_fifo.r_ptr[2:0]", clock=clock)
99
+
100
+ # Perform vectorized arithmetic operations directly on waveforms
101
+ fifo_water_level = (w_ptr + depth - r_ptr) % depth
102
+
103
+ # Calculate average occupancy using Numpy
104
+ average_level = np.mean(fifo_water_level.value)
105
+ print(f"Average FIFO occupancy: {average_level:.2f}")
106
+ ```
107
+
108
+ ### Advanced: Data Validity Filtering
109
+
110
+ Use the powerful masking capabilities to filter data based on valid signals:
111
+
112
+ ```python
113
+ from wavekit import VcdReader
114
+
115
+ with VcdReader("bus_tb.vcd") as f:
116
+ clock = "top.clk"
117
+
118
+ # Load data and valid signals
119
+ data = f.load_waveform("top.bus_data[31:0]", clock=clock)
120
+ valid = f.load_waveform("top.bus_valid", clock=clock)
121
+
122
+ # Filter data where valid is high (1)
123
+ # The mask() method accepts a boolean waveform or numpy array
124
+ valid_data = data.mask(valid == 1)
125
+
126
+ # Analyze the valid transactions
127
+ print(f"Total valid transactions: {len(valid_data.value)}")
128
+ print(f"First valid data: {hex(valid_data.value[0])}")
129
+ ```
130
+
131
+ ## 🛠️ Development
132
+
133
+ This project adheres to strict code quality standards using modern Python tooling:
134
+
135
+ - **Linting & Formatting**: [Ruff](https://github.com/astral-sh/ruff)
136
+ - **Type Checking**: [Mypy](https://mypy-lang.org/)
137
+
138
+ Ensure all checks pass before submitting a Pull Request:
139
+
140
+ ```bash
141
+ # Run linting and formatting checks
142
+ poetry run ruff check .
143
+ poetry run ruff format --check .
144
+
145
+ # Run type checks
146
+ poetry run mypy .
147
+ ```
148
+
149
+ ## 📄 License
150
+
151
+ This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for details.
wavekit-0.2.0/build.py ADDED
@@ -0,0 +1,52 @@
1
+ import os
2
+
3
+ import numpy as np
4
+ from Cython.Build import cythonize
5
+ from setuptools import Extension, setup
6
+
7
+ extensions = [
8
+ Extension(
9
+ 'src.wavekit.readers.value_change',
10
+ sources=['src/wavekit/readers/value_change.pyx'],
11
+ include_dirs=[np.get_include()],
12
+ extra_compile_args=['-fpic', '-O3', '-march=native'],
13
+ extra_link_args=['-O3', '-march=native'],
14
+ language='c++',
15
+ )
16
+ ]
17
+
18
+ if VERDI_HOME := os.environ.get('VERDI_HOME'):
19
+ npi_include_dir = os.path.join(VERDI_HOME, 'share/NPI/inc')
20
+ npi_library_dir = os.path.join(VERDI_HOME, 'share/NPI/lib/LINUX64')
21
+
22
+ extensions.append(
23
+ Extension(
24
+ 'src.wavekit.readers.fsdb.npi_fsdb_reader',
25
+ sources=['src/wavekit/readers/fsdb/npi_fsdb_reader.pyx'],
26
+ include_dirs=[np.get_include(), npi_include_dir],
27
+ library_dirs=[npi_library_dir],
28
+ libraries=['NPI', 'rt'],
29
+ extra_compile_args=['-fpic', '-O3', '-march=native'],
30
+ extra_link_args=[
31
+ '-O3',
32
+ '-march=native',
33
+ f'-Wl,-rpath,{npi_library_dir}',
34
+ ],
35
+ language='c++',
36
+ )
37
+ )
38
+
39
+ setup(
40
+ ext_modules=cythonize(
41
+ extensions,
42
+ compiler_directives={
43
+ 'language_level': 3,
44
+ 'embedsignature': True,
45
+ 'boundscheck': False,
46
+ 'wraparound': False,
47
+ 'cdivision': True,
48
+ 'nonecheck': False,
49
+ },
50
+ ),
51
+ script_args=['build_ext', '--inplace'],
52
+ )
@@ -0,0 +1,49 @@
1
+ [tool.poetry]
2
+ name = "wavekit"
3
+ version = "0.2.0"
4
+ description = "a fundamental package for digital circuit waveform analysis"
5
+ authors = ["cxzzzz <cxz19961010@outlook.com>"]
6
+ readme = "README.md"
7
+ repository = "https://github.com/cxzzzz/wavekit"
8
+ build = "build.py"
9
+ include = ["src/wavekit/**/*.so"]
10
+
11
+ [tool.poetry.dependencies]
12
+ python = "^3.9"
13
+ numpy = "^2.0.0"
14
+ vcdvcd = "^2.3.5"
15
+ pytest = "^8.2.2"
16
+
17
+ [tool.poetry.group.dev.dependencies]
18
+ ruff = "^0.6.2"
19
+ mypy = "^1.11.2"
20
+
21
+ [tool.ruff]
22
+ target-version = "py39"
23
+ line-length = 100
24
+ src = ["src", "tests"]
25
+
26
+ [tool.ruff.lint]
27
+ select = ["E", "F", "W", "I", "UP", "B"]
28
+
29
+ [tool.ruff.lint.per-file-ignores]
30
+ "tests/**.py" = ["B017"]
31
+
32
+ [tool.ruff.format]
33
+ quote-style = "single"
34
+ line-ending = "lf"
35
+
36
+ [tool.mypy]
37
+ python_version = "3.9"
38
+ files = ["src", "tests"]
39
+ warn_unused_ignores = true
40
+ warn_redundant_casts = true
41
+ warn_unused_configs = true
42
+ check_untyped_defs = false
43
+ ignore_missing_imports = true
44
+ no_implicit_optional = false
45
+ disable_error_code = ["misc"]
46
+
47
+ [build-system]
48
+ requires = ["poetry-core", "Cython", "setuptools", "wheel", "numpy>=2.0.0"]
49
+ build-backend = "poetry.core.masonry.api"
@@ -0,0 +1,21 @@
1
+ # -------------------------------------------------------------
2
+ # Copyright (c) Microsoft Corporation. All rights reserved.
3
+ # Licensed under the MIT License. See LICENSE in project root for information.
4
+ # -------------------------------------------------------------
5
+ """Python Package Template"""
6
+
7
+ from __future__ import annotations
8
+
9
+ from importlib import metadata
10
+
11
+ try:
12
+ __version__ = metadata.version('wavekit')
13
+ except metadata.PackageNotFoundError:
14
+ __version__ = 'unknown'
15
+
16
+ from .readers.vcd.reader import VcdReader as VcdReader
17
+ from .scope import Scope as Scope
18
+ from .signal import Signal as Signal
19
+ from .waveform import Waveform as Waveform
20
+
21
+ __all__ = ['Waveform', 'VcdReader', 'FsdbReader', 'Scope', 'Signal']
@@ -0,0 +1,142 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import abstractmethod
4
+ from collections.abc import Sequence
5
+ from typing import Any
6
+
7
+ import numpy as np
8
+
9
+ from ..scope import Scope, traverse_scope
10
+ from ..signal import Signal
11
+ from ..waveform import Waveform
12
+ from .pattern_parser import (
13
+ PatternMap,
14
+ expand_brace_pattern,
15
+ split_by_hierarchy,
16
+ )
17
+ from .value_change import value_change_to_value_array
18
+
19
+
20
+ class Reader:
21
+ def __init__(self):
22
+ pass
23
+
24
+ def __enter__(self):
25
+ return self
26
+
27
+ def __exit__(self, exc_type, exc_val, exc_tb):
28
+ self.close()
29
+ # exception wont be suppressed
30
+ return False
31
+
32
+ @abstractmethod
33
+ def load_waveform(
34
+ self,
35
+ signal: str,
36
+ clock: str,
37
+ xz_value: int = 0,
38
+ signed: bool = False,
39
+ sample_on_posedge: bool = False,
40
+ begin_time: int | None = None,
41
+ end_time: int | None = None,
42
+ ) -> Waveform:
43
+ pass
44
+
45
+ @abstractmethod
46
+ def get_signal_width(self, signal: str) -> int:
47
+ pass
48
+
49
+ @staticmethod
50
+ def value_change_to_waveform(
51
+ value_change: np.ndarray,
52
+ clock_changes: np.ndarray,
53
+ width: int | None,
54
+ signed: bool,
55
+ sample_on_posedge: bool = False,
56
+ signal: str = '',
57
+ ) -> Waveform:
58
+ value, clock, time = value_change_to_value_array(
59
+ value_change, clock_changes, sample_on_posedge=sample_on_posedge
60
+ )
61
+
62
+ return Waveform(
63
+ value=value,
64
+ clock=clock,
65
+ time=time,
66
+ signal=Signal(signal, width, signed),
67
+ )
68
+
69
+ @abstractmethod
70
+ def top_scope_list(self) -> Sequence[Scope]:
71
+ pass
72
+
73
+ def get_matched_signals(
74
+ self,
75
+ pattern: str,
76
+ ) -> dict[tuple[Any, ...], str]:
77
+ def combine_dict(
78
+ dict1: dict[tuple[Any, ...], str],
79
+ dict2: dict[tuple[Any, ...], str],
80
+ ) -> dict[tuple[Any, ...], str]:
81
+ common_keys = set(dict1.keys()).intersection(dict2.keys())
82
+
83
+ if common_keys:
84
+ signal1s = [dict1[k] for k in common_keys]
85
+ signal2s = [dict2[k] for k in common_keys]
86
+ raise Exception(
87
+ 'found more than one signal with the same keys: '
88
+ f'keys:{list(common_keys)} , signals:{signal1s + signal2s}'
89
+ )
90
+
91
+ return {**dict1, **dict2}
92
+
93
+ pattern = pattern.strip()
94
+ expanded_pattern_list: list[PatternMap] = [
95
+ expand_brace_pattern(p) for p in split_by_hierarchy(pattern)
96
+ ]
97
+
98
+ matched_signals: dict[tuple[Any, ...], str] = {}
99
+ for scope in self.top_scope_list():
100
+ matched_signals = combine_dict(
101
+ matched_signals,
102
+ traverse_scope(scope, expanded_pattern_list),
103
+ )
104
+ return matched_signals
105
+
106
+ def load_matched_waveforms(
107
+ self,
108
+ pattern: str,
109
+ clock_pattern: str,
110
+ xz_value: int = 0,
111
+ signed: bool = False,
112
+ sample_on_posedge: bool = False,
113
+ begin_time: int | None = None,
114
+ end_time: int | None = None,
115
+ ) -> dict[tuple[Any, ...], Waveform]:
116
+ clock_patterns = self.get_matched_signals(clock_pattern)
117
+ if not clock_patterns:
118
+ raise Exception(f'clock pattern {clock_pattern} can not match any signal')
119
+ if len(clock_patterns) > 1:
120
+ raise Exception(
121
+ f'clock pattern {clock_pattern} match more than one signal: {clock_patterns}'
122
+ )
123
+ clock_full_name = next(iter(clock_patterns.values()))
124
+
125
+ matched_signals = self.get_matched_signals(pattern)
126
+
127
+ return {
128
+ k: self.load_waveform(
129
+ s,
130
+ clock_full_name,
131
+ xz_value=xz_value,
132
+ signed=signed,
133
+ sample_on_posedge=sample_on_posedge,
134
+ begin_time=begin_time,
135
+ end_time=end_time,
136
+ )
137
+ for k, s in matched_signals.items()
138
+ }
139
+
140
+ @abstractmethod
141
+ def close(self):
142
+ pass