wavekit 0.7.2__tar.gz → 0.8.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.
Files changed (37) hide show
  1. wavekit-0.8.0/PKG-INFO +125 -0
  2. wavekit-0.8.0/README.md +102 -0
  3. wavekit-0.7.2/build.py → wavekit-0.8.0/build_exts.py +22 -5
  4. {wavekit-0.7.2 → wavekit-0.8.0}/pyproject.toml +11 -6
  5. {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/__init__.py +2 -2
  6. {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/expression.py +1 -1
  7. {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/pattern/compiler.py +1 -1
  8. {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/pattern/dsl.py +17 -9
  9. {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/pattern/result.py +10 -6
  10. {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/pattern/runtime.py +16 -16
  11. wavekit-0.8.0/src/wavekit/readers/__init__.py +1 -0
  12. {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/readers/base.py +362 -173
  13. wavekit-0.8.0/src/wavekit/readers/clock_domain.py +57 -0
  14. wavekit-0.8.0/src/wavekit/readers/fsdb/__init__.py +1 -0
  15. {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/readers/fsdb/reader.py +13 -9
  16. wavekit-0.8.0/src/wavekit/readers/fst/__init__.py +1 -0
  17. wavekit-0.8.0/src/wavekit/readers/fst/reader.py +11 -0
  18. {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/readers/hierarchy.py +93 -12
  19. {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/readers/matcher.py +2 -5
  20. wavekit-0.8.0/src/wavekit/readers/vcd/__init__.py +1 -0
  21. wavekit-0.8.0/src/wavekit/readers/vcd/reader.py +11 -0
  22. wavekit-0.8.0/src/wavekit/readers/wellen/__init__.py +1 -0
  23. wavekit-0.8.0/src/wavekit/readers/wellen/reader.py +252 -0
  24. {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/waveform.py +274 -172
  25. wavekit-0.7.2/PKG-INFO +0 -556
  26. wavekit-0.7.2/README.md +0 -532
  27. wavekit-0.7.2/src/wavekit/readers/fst/reader.py +0 -206
  28. wavekit-0.7.2/src/wavekit/readers/vcd/reader.py +0 -178
  29. {wavekit-0.7.2 → wavekit-0.8.0}/LICENSE +0 -0
  30. {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/pattern/__init__.py +0 -0
  31. {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/pattern/errors.py +0 -0
  32. {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/pattern/steps.py +0 -0
  33. {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/readers/expr_parser.py +0 -0
  34. {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/readers/fsdb/npi_fsdb.pxd +0 -0
  35. {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/readers/fsdb/npi_fsdb_reader.pyx +0 -0
  36. {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/readers/range.py +0 -0
  37. {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/readers/value_change.pyx +0 -0
wavekit-0.8.0/PKG-INFO ADDED
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.4
2
+ Name: wavekit
3
+ Version: 0.8.0
4
+ Summary: High-level digital waveform analysis in Python
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: lark (>=1.2,<2)
17
+ Requires-Dist: numpy (>=2.0.0,<3.0.0)
18
+ Requires-Dist: wavekit-pywellen (>=0.25.6)
19
+ Project-URL: Documentation, https://cxzzzz.github.io/wavekit
20
+ Project-URL: Repository, https://github.com/cxzzzz/wavekit
21
+ Description-Content-Type: text/markdown
22
+
23
+ # <img src="docs/en/images/logo.svg" alt="" width="30" height="30" valign="middle"> wavekit
24
+
25
+ [![CI](https://github.com/cxzzzz/wavekit/actions/workflows/python-package.yml/badge.svg)](https://github.com/cxzzzz/wavekit/actions/workflows/python-package.yml)
26
+ [![PyPI version](https://img.shields.io/pypi/v/wavekit.svg)](https://pypi.org/project/wavekit/)
27
+ [![Python Versions](https://img.shields.io/pypi/pyversions/wavekit.svg)](https://pypi.org/project/wavekit/)
28
+ [![Downloads](https://pepy.tech/badge/wavekit)](https://pepy.tech/project/wavekit)
29
+ [![License](https://img.shields.io/github/license/cxzzzz/wavekit.svg)](LICENSE)
30
+
31
+ English | [中文](README_ZH.md)
32
+
33
+ **Wavekit is a Python library for high-level digital waveform analysis.**
34
+ It loads signals from waveform files as clock-aligned `Waveform` objects,
35
+ allowing users to query and operate on them at the cycle and transaction levels,
36
+ making complex hardware behavior easier to analyze.
37
+
38
+ > **AI integration:** [wavekit-mcp](https://github.com/cxzzzz/wavekit-mcp) exposes wavekit analysis through
39
+ > MCP tools for AI-assisted workflows.
40
+
41
+ ## Features
42
+
43
+ - **Flexible signal queries:** find and batch-load related signals from
44
+ hierarchical waveform data using multiple path-matching options.
45
+ - **Cycle-level analysis:** use a range of waveform operations on clock-sampled
46
+ data to analyze cycle-based behavior such as interface backpressure and FIFO
47
+ occupancy.
48
+ - **Transaction-level analysis:** use temporal pattern matching to describe
49
+ signal relationships across multiple clock cycles for protocol analysis,
50
+ transaction extraction, and latency measurement.
51
+ - **Multi-format waveform support:** load and analyze VCD, FST, and FSDB files
52
+ using the same API.
53
+
54
+ ## Installation
55
+
56
+ ```console
57
+ python -m pip install wavekit
58
+ ```
59
+
60
+ wavekit supports Python 3.9 and newer. FSDB support requires the Verdi NPI
61
+ runtime (`libNPI.so`); see the [installation guide](https://cxzzzz.github.io/wavekit/getting-started/installation/) for
62
+ setup details.
63
+
64
+ ## Quick start
65
+
66
+ Calculate the occupancy of a FIFO from a VCD waveform:
67
+
68
+ ```python
69
+ import numpy as np
70
+
71
+ from wavekit import VcdReader
72
+
73
+ with VcdReader('simulation.vcd') as r:
74
+ fifo = r['tb.u_fifo']
75
+ with r.clock_domain(clock='tb.clk'):
76
+ w_ptr = fifo['w_ptr'].w
77
+ r_ptr = fifo['r_ptr'].w
78
+
79
+ depth = 16
80
+ occupancy = (w_ptr + depth - r_ptr) % depth
81
+
82
+ print('Average occupancy:', np.mean(occupancy.value))
83
+ ```
84
+
85
+ See the [first waveform tutorial](https://cxzzzz.github.io/wavekit/getting-started/first-waveform/) for a
86
+ complete runnable FIFO example.
87
+
88
+ ## Documentation
89
+
90
+ See the [documentation](https://cxzzzz.github.io/wavekit/) for the full reference and guides:
91
+
92
+ - [Installation](https://cxzzzz.github.io/wavekit/getting-started/installation/)
93
+ - [First waveform](https://cxzzzz.github.io/wavekit/getting-started/first-waveform/)
94
+ - [Reader guide](https://cxzzzz.github.io/wavekit/guides/reader/)
95
+ - [Signal queries](https://cxzzzz.github.io/wavekit/guides/signal-query/)
96
+ - [Waveform analysis](https://cxzzzz.github.io/wavekit/guides/waveform-analysis/)
97
+ - [Pattern matching](https://cxzzzz.github.io/wavekit/guides/pattern-matching/)
98
+ - [API reference](https://cxzzzz.github.io/wavekit/reference/api/)
99
+ - [Complete examples](https://cxzzzz.github.io/wavekit/examples/)
100
+
101
+ ## Development
102
+
103
+ Install the development dependencies and run the checks with Poetry:
104
+
105
+ ```console
106
+ poetry install
107
+ poetry run pytest
108
+ poetry run ruff check .
109
+ poetry run ruff format --check .
110
+ poetry run mypy
111
+ ```
112
+
113
+ Build the documentation locally with Python 3.10 or newer:
114
+
115
+ ```console
116
+ poetry install --with docs
117
+ poetry run zensical build --clean --strict
118
+ ```
119
+
120
+ See [Contributing](https://cxzzzz.github.io/wavekit/contributing/) for contribution guidelines.
121
+
122
+ ## License
123
+
124
+ This project is licensed under the MIT License. See [LICENSE](LICENSE).
125
+
@@ -0,0 +1,102 @@
1
+ # <img src="docs/en/images/logo.svg" alt="" width="30" height="30" valign="middle"> wavekit
2
+
3
+ [![CI](https://github.com/cxzzzz/wavekit/actions/workflows/python-package.yml/badge.svg)](https://github.com/cxzzzz/wavekit/actions/workflows/python-package.yml)
4
+ [![PyPI version](https://img.shields.io/pypi/v/wavekit.svg)](https://pypi.org/project/wavekit/)
5
+ [![Python Versions](https://img.shields.io/pypi/pyversions/wavekit.svg)](https://pypi.org/project/wavekit/)
6
+ [![Downloads](https://pepy.tech/badge/wavekit)](https://pepy.tech/project/wavekit)
7
+ [![License](https://img.shields.io/github/license/cxzzzz/wavekit.svg)](LICENSE)
8
+
9
+ English | [中文](README_ZH.md)
10
+
11
+ **Wavekit is a Python library for high-level digital waveform analysis.**
12
+ It loads signals from waveform files as clock-aligned `Waveform` objects,
13
+ allowing users to query and operate on them at the cycle and transaction levels,
14
+ making complex hardware behavior easier to analyze.
15
+
16
+ > **AI integration:** [wavekit-mcp](https://github.com/cxzzzz/wavekit-mcp) exposes wavekit analysis through
17
+ > MCP tools for AI-assisted workflows.
18
+
19
+ ## Features
20
+
21
+ - **Flexible signal queries:** find and batch-load related signals from
22
+ hierarchical waveform data using multiple path-matching options.
23
+ - **Cycle-level analysis:** use a range of waveform operations on clock-sampled
24
+ data to analyze cycle-based behavior such as interface backpressure and FIFO
25
+ occupancy.
26
+ - **Transaction-level analysis:** use temporal pattern matching to describe
27
+ signal relationships across multiple clock cycles for protocol analysis,
28
+ transaction extraction, and latency measurement.
29
+ - **Multi-format waveform support:** load and analyze VCD, FST, and FSDB files
30
+ using the same API.
31
+
32
+ ## Installation
33
+
34
+ ```console
35
+ python -m pip install wavekit
36
+ ```
37
+
38
+ wavekit supports Python 3.9 and newer. FSDB support requires the Verdi NPI
39
+ runtime (`libNPI.so`); see the [installation guide](https://cxzzzz.github.io/wavekit/getting-started/installation/) for
40
+ setup details.
41
+
42
+ ## Quick start
43
+
44
+ Calculate the occupancy of a FIFO from a VCD waveform:
45
+
46
+ ```python
47
+ import numpy as np
48
+
49
+ from wavekit import VcdReader
50
+
51
+ with VcdReader('simulation.vcd') as r:
52
+ fifo = r['tb.u_fifo']
53
+ with r.clock_domain(clock='tb.clk'):
54
+ w_ptr = fifo['w_ptr'].w
55
+ r_ptr = fifo['r_ptr'].w
56
+
57
+ depth = 16
58
+ occupancy = (w_ptr + depth - r_ptr) % depth
59
+
60
+ print('Average occupancy:', np.mean(occupancy.value))
61
+ ```
62
+
63
+ See the [first waveform tutorial](https://cxzzzz.github.io/wavekit/getting-started/first-waveform/) for a
64
+ complete runnable FIFO example.
65
+
66
+ ## Documentation
67
+
68
+ See the [documentation](https://cxzzzz.github.io/wavekit/) for the full reference and guides:
69
+
70
+ - [Installation](https://cxzzzz.github.io/wavekit/getting-started/installation/)
71
+ - [First waveform](https://cxzzzz.github.io/wavekit/getting-started/first-waveform/)
72
+ - [Reader guide](https://cxzzzz.github.io/wavekit/guides/reader/)
73
+ - [Signal queries](https://cxzzzz.github.io/wavekit/guides/signal-query/)
74
+ - [Waveform analysis](https://cxzzzz.github.io/wavekit/guides/waveform-analysis/)
75
+ - [Pattern matching](https://cxzzzz.github.io/wavekit/guides/pattern-matching/)
76
+ - [API reference](https://cxzzzz.github.io/wavekit/reference/api/)
77
+ - [Complete examples](https://cxzzzz.github.io/wavekit/examples/)
78
+
79
+ ## Development
80
+
81
+ Install the development dependencies and run the checks with Poetry:
82
+
83
+ ```console
84
+ poetry install
85
+ poetry run pytest
86
+ poetry run ruff check .
87
+ poetry run ruff format --check .
88
+ poetry run mypy
89
+ ```
90
+
91
+ Build the documentation locally with Python 3.10 or newer:
92
+
93
+ ```console
94
+ poetry install --with docs
95
+ poetry run zensical build --clean --strict
96
+ ```
97
+
98
+ See [Contributing](https://cxzzzz.github.io/wavekit/contributing/) for contribution guidelines.
99
+
100
+ ## License
101
+
102
+ This project is licensed under the MIT License. See [LICENSE](LICENSE).
@@ -1,3 +1,5 @@
1
+ import os
2
+ import sys
1
3
  from pathlib import Path
2
4
 
3
5
  import numpy as np
@@ -7,22 +9,37 @@ from setuptools import Extension, setup
7
9
  for artifact in Path('src/wavekit').glob('**/*.so'):
8
10
  artifact.unlink()
9
11
 
12
+ if os.environ.get('PYODIDE'):
13
+ compile_args = ['-fpic', '-O3']
14
+ link_args = ['-O3']
15
+ fsdb_libraries = []
16
+ elif sys.platform == 'linux':
17
+ compile_args = ['-fpic', '-O3']
18
+ link_args = ['-O3']
19
+ fsdb_libraries = ['dl']
20
+ elif sys.platform == 'darwin':
21
+ compile_args = ['-fpic', '-O3']
22
+ link_args = ['-O3']
23
+ fsdb_libraries = []
24
+ else:
25
+ raise RuntimeError(f'Unsupported platform: {sys.platform}')
26
+
10
27
  extensions = [
11
28
  Extension(
12
29
  'wavekit.readers.value_change',
13
30
  sources=['src/wavekit/readers/value_change.pyx'],
14
31
  include_dirs=[np.get_include()],
15
- extra_compile_args=['-fpic', '-O3', '-march=native'],
16
- extra_link_args=['-O3', '-march=native'],
32
+ extra_compile_args=compile_args,
33
+ extra_link_args=link_args,
17
34
  language='c++',
18
35
  ),
19
36
  Extension(
20
37
  'wavekit.readers.fsdb.npi_fsdb_reader',
21
38
  sources=['src/wavekit/readers/fsdb/npi_fsdb_reader.pyx'],
22
39
  include_dirs=[np.get_include()],
23
- libraries=['dl'],
24
- extra_compile_args=['-fpic', '-O3', '-march=native'],
25
- extra_link_args=['-O3', '-march=native'],
40
+ libraries=fsdb_libraries,
41
+ extra_compile_args=compile_args,
42
+ extra_link_args=link_args,
26
43
  language='c++',
27
44
  ),
28
45
  ]
@@ -1,11 +1,12 @@
1
1
  [tool.poetry]
2
2
  name = "wavekit"
3
- version = "0.7.2"
4
- description = "a fundamental package for digital circuit waveform analysis"
3
+ version = "0.8.0"
4
+ description = "High-level digital waveform analysis in Python"
5
5
  authors = ["cxzzzz <cxz19961010@outlook.com>"]
6
6
  readme = "README.md"
7
7
  repository = "https://github.com/cxzzzz/wavekit"
8
- build = "build.py"
8
+ documentation = "https://cxzzzz.github.io/wavekit"
9
+ build = "build_exts.py"
9
10
  include = [
10
11
  { path = "src/wavekit/**/*.so", format = "wheel" },
11
12
  ]
@@ -13,15 +14,19 @@ include = [
13
14
  [tool.poetry.dependencies]
14
15
  python = "^3.9"
15
16
  numpy = "^2.0.0"
16
- vcdvcd = "^2.3.5"
17
- pylibfst = "^0.2.1"
18
- pytest = "^8.2.2"
17
+ wavekit-pywellen = ">=0.25.6"
19
18
  lark = ">=1.2,<2"
20
19
 
21
20
  [tool.poetry.group.dev.dependencies]
21
+ pytest = "^8.2.2"
22
22
  ruff = "^0.6.2"
23
23
  mypy = "^1.11.2"
24
24
 
25
+
26
+ [tool.poetry.group.docs.dependencies]
27
+ zensical = {version = "^0.0.57", python = ">=3.10,<4.0"}
28
+ mkdocstrings-python = {version = "1.18.2", python = ">=3.10,<4.0"}
29
+
25
30
  [tool.ruff]
26
31
  target-version = "py39"
27
32
  line-length = 100
@@ -9,6 +9,7 @@ try:
9
9
  except metadata.PackageNotFoundError:
10
10
  __version__ = 'unknown'
11
11
 
12
+ from .readers.clock_domain import ClockDomain as ClockDomain
12
13
  from .readers.fst.reader import FstReader as FstReader
13
14
  from .readers.hierarchy import Node as Node
14
15
  from .readers.hierarchy import Scope as Scope
@@ -16,7 +17,6 @@ from .readers.hierarchy import Signal as Signal
16
17
  from .readers.hierarchy import SignalCompositeType as SignalCompositeType
17
18
  from .readers.matcher import BraceCapture as BraceCapture
18
19
  from .readers.matcher import Capture as Capture
19
- from .readers.matcher import CaptureKey as CaptureKey
20
20
  from .readers.matcher import ExactCapture as ExactCapture
21
21
  from .readers.matcher import RegexCapture as RegexCapture
22
22
  from .readers.matcher import WildcardCapture as WildcardCapture
@@ -29,13 +29,13 @@ __all__ = [
29
29
  'VcdReader',
30
30
  'FsdbReader',
31
31
  'FstReader',
32
+ 'ClockDomain',
32
33
  'Node',
33
34
  'Scope',
34
35
  'Range',
35
36
  'Signal',
36
37
  'SignalCompositeType',
37
38
  'Capture',
38
- 'CaptureKey',
39
39
  'ExactCapture',
40
40
  'BraceCapture',
41
41
  'RegexCapture',
@@ -149,7 +149,7 @@ def evaluate_expression(expr: str, namespace: dict[str, Any]) -> Any:
149
149
  Parameters
150
150
  ----------
151
151
  expr:
152
- The substituted expression returned by :func:`parse_expression`.
152
+ The substituted expression returned by ``parse_expression``.
153
153
  Physical paths must be resolved and replaced before calling this
154
154
  function.
155
155
  namespace:
@@ -28,7 +28,7 @@ from .steps import (
28
28
  def infer_declarative_axis(steps: list[Step]) -> Waveform | None:
29
29
  """Infer a minimal first-static-waveform scan axis hint from steps."""
30
30
  # Axis inference is intentionally minimal. It only finds a static waveform
31
- # so start/end_cycle can be translated to indices; full clock alignment
31
+ # so start/end_cycle can be translated to indices; full cycle-axis alignment
32
32
  # validation remains lazy in PatternRuntime.note_waveform().
33
33
  for step in steps:
34
34
  if isinstance(step, (WaitStep, ConsumeStep)):
@@ -28,8 +28,8 @@ class Pattern:
28
28
  """Declarative temporal pattern builder over waveform signals.
29
29
 
30
30
  Build steps with ``Pattern().wait(...).capture(...)`` and execute with
31
- module-level :func:`match`. ``Pattern`` stores only the declarative step AST;
32
- programmable checking/extraction uses :func:`match(body)` or :func:`collect(body)`.
31
+ module-level ``match``. ``Pattern`` stores only the declarative step AST;
32
+ programmable checking/extraction uses ``match(body)`` or ``collect(body)``.
33
33
 
34
34
  The first blocking step selects candidate start cycles. Later blocking steps
35
35
  wait within a matched transaction.
@@ -77,7 +77,10 @@ class Pattern:
77
77
  def consume(
78
78
  self,
79
79
  cond: Waveform | Callable[[int, dict[str, Any]], bool] | bool,
80
- channel: Channel | Hashable | Callable[[int, dict[str, Any]], Channel | Hashable],
80
+ channel: Channel
81
+ | Hashable
82
+ | Callable[[int, dict[str, Any]], Channel | Hashable]
83
+ | None = None,
81
84
  *,
82
85
  require: Waveform | Callable[[int, dict[str, Any]], bool] | bool | None = None,
83
86
  require_message: str | Callable[[int, dict[str, Any]], str] | None = None,
@@ -93,10 +96,11 @@ class Pattern:
93
96
  cycle number. ``captures`` is the current match's capture dict.
94
97
  channel:
95
98
  ``Channel``, hashable key, or ``callable(index, captures) -> Channel | Hashable``.
96
- Channel callbacks use the same ``(index, captures)`` arguments.
97
- The resolved ``(channel, cycle)`` can be claimed by at most one match;
98
- earlier start cycles win. This does not reserve the channel while
99
- ``cond`` is false.
99
+ If omitted, this consume step receives a private channel created when
100
+ the step is declared. Channel callbacks use the same ``(index, captures)``
101
+ arguments. The resolved ``(channel, cycle)`` can be claimed by at most
102
+ one match; earlier start cycles win. This does not reserve the channel
103
+ while ``cond`` is false.
100
104
  require:
101
105
  Optional condition checked while ``cond`` is false, or while ``cond`` is
102
106
  true but the current ``(channel, cycle)`` was already claimed. It is not
@@ -110,9 +114,13 @@ class Pattern:
110
114
  Pattern
111
115
  This pattern, for chaining.
112
116
  """
117
+ resolved_channel = Channel() if channel is None else channel
113
118
  self._steps.append(
114
119
  ConsumeStep(
115
- cond=cond, channel=channel, require=require, require_message=require_message
120
+ cond=cond,
121
+ channel=resolved_channel,
122
+ require=require,
123
+ require_message=require_message,
116
124
  )
117
125
  )
118
126
  return self
@@ -235,7 +243,7 @@ class Pattern:
235
243
  Notes
236
244
  -----
237
245
  Exactly one of ``until`` and ``when`` is required. Conditions use the same
238
- ``Waveform`` / ``bool`` / callable forms as :meth:`wait`.
246
+ ``Waveform`` / ``bool`` / callable forms as ``wait``.
239
247
  """
240
248
  if until is not None and when is not None:
241
249
  raise ValueError("Cannot specify both 'until' and 'when' in loop()")
@@ -19,14 +19,18 @@ class MatchStatus:
19
19
 
20
20
  @dataclass(frozen=True)
21
21
  class OK(MatchStatusValue):
22
- pass
22
+ """Successful completion of a pattern candidate."""
23
23
 
24
24
  @dataclass(frozen=True)
25
25
  class Timeout(MatchStatusValue):
26
+ """A pattern candidate did not complete within its allowed duration."""
27
+
26
28
  message: str | None = None
27
29
 
28
30
  @dataclass(frozen=True)
29
31
  class RequireViolated(MatchStatusValue):
32
+ """A non-blocking ``require`` check failed."""
33
+
30
34
  message: str | None = None
31
35
 
32
36
 
@@ -81,7 +85,7 @@ class MatchRecords(Sequence[MatchRecord]):
81
85
  """Columnar batch of pattern match records.
82
86
 
83
87
  ``start`` and ``end`` are point waveforms: ``.value`` stores waveform-array
84
- sample indices, ``.clock`` stores absolute cycle numbers, and ``.time`` stores
88
+ sample indices, ``.cycle`` stores absolute cycle numbers, and ``.time`` stores
85
89
  simulation timestamps. ``duration.value`` is ``end.value - start.value + 1``.
86
90
  """
87
91
 
@@ -112,7 +116,7 @@ class MatchRecords(Sequence[MatchRecord]):
112
116
  value = np.array(
113
117
  [isinstance(status, MatchStatus.OK) for status in self.status.value], dtype=bool
114
118
  )
115
- return Waveform(value, self.start.clock.copy(), self.start.time.copy(), width=1)
119
+ return Waveform(value, self.start.cycle.copy(), self.start.time.copy(), width=1)
116
120
 
117
121
  @property
118
122
  def failed(self) -> Waveform:
@@ -127,7 +131,7 @@ class MatchRecords(Sequence[MatchRecord]):
127
131
  value = np.array(
128
132
  [not isinstance(status, MatchStatus.OK) for status in self.status.value], dtype=bool
129
133
  )
130
- return Waveform(value, self.start.clock.copy(), self.start.time.copy(), width=1)
134
+ return Waveform(value, self.start.cycle.copy(), self.start.time.copy(), width=1)
131
135
 
132
136
  def filter_ok(self) -> MatchRecords:
133
137
  """Return records whose status is ``MatchStatus.OK``.
@@ -215,12 +219,12 @@ class MatchRecords(Sequence[MatchRecord]):
215
219
  return MatchRecord(
216
220
  start=MatchPoint(
217
221
  index=int(self.start.value[index]),
218
- cycle=int(self.start.clock[index]),
222
+ cycle=int(self.start.cycle[index]),
219
223
  time=int(self.start.time[index]),
220
224
  ),
221
225
  end=MatchPoint(
222
226
  index=int(self.end.value[index]),
223
- cycle=int(self.end.clock[index]),
227
+ cycle=int(self.end.cycle[index]),
224
228
  time=int(self.end.time[index]),
225
229
  ),
226
230
  status=self.status.value[index],
@@ -37,7 +37,7 @@ class PatternContext:
37
37
 
38
38
  Programmable bodies run once per scanned start cycle. Use ``ctx.value(...)``
39
39
  to read waveforms, ``ctx.wait(...)`` / ``ctx.consume(...)`` / ``ctx.delay(...)``
40
- to move through time, and return ``ctx.OK`` from :func:`wavekit.pattern.match`
40
+ to move through time, and return ``ctx.OK`` from ``wavekit.pattern.match``
41
41
  bodies to record a successful match.
42
42
  """
43
43
 
@@ -121,7 +121,7 @@ class PatternContext:
121
121
  Parameters
122
122
  ----------
123
123
  waveform:
124
- Waveform to read. It must share the same clock axis as other observed
124
+ Waveform to read. It must share the same cycle axis as other observed
125
125
  pattern waveforms.
126
126
  offset:
127
127
  Relative sample offset from ``ctx.index``. ``0`` reads the current
@@ -142,17 +142,17 @@ class PatternContext:
142
142
  Parameters
143
143
  ----------
144
144
  waveform:
145
- Waveform whose clock axis is read and validated.
145
+ Waveform whose cycle axis is read and validated.
146
146
  offset:
147
147
  Relative sample offset from ``ctx.index``.
148
148
 
149
149
  Returns
150
150
  -------
151
151
  Any
152
- Scalar value from ``waveform.clock[ctx.index + offset]``.
152
+ Scalar value from ``waveform.cycle[ctx.index + offset]``.
153
153
  """
154
154
  index = self._waveform_index(waveform, offset)
155
- return waveform.clock[index]
155
+ return waveform.cycle[index]
156
156
 
157
157
  def time(self, waveform: Waveform, offset: int = 0) -> Any:
158
158
  """Return a waveform timestamp at the current sample plus *offset*.
@@ -400,14 +400,14 @@ class PatternRuntime:
400
400
  if self._start_cycle is None:
401
401
  return 0
402
402
  axis = self._require_axis()
403
- return int(np.searchsorted(axis.clock, self._start_cycle))
403
+ return int(np.searchsorted(axis.cycle, self._start_cycle))
404
404
 
405
405
  @cached_property
406
406
  def scan_end_index(self) -> int:
407
407
  axis = self._require_axis()
408
408
  if self._end_cycle is None:
409
409
  return len(axis.value)
410
- return int(np.searchsorted(axis.clock, self._end_cycle))
410
+ return int(np.searchsorted(axis.cycle, self._end_cycle))
411
411
 
412
412
  def match(self, start_cycle: int | None = None, end_cycle: int | None = None) -> MatchRecords:
413
413
  completed = self._run(start_cycle, end_cycle)
@@ -426,20 +426,20 @@ class PatternRuntime:
426
426
  continue
427
427
  raise PatternError(
428
428
  f'Pattern failed with {inst.status}; '
429
- f'start_cycle={axis.clock[inst.start_index]}, '
430
- f'failure_cycle={axis.clock[inst.end_index]}'
429
+ f'start_cycle={axis.cycle[inst.start_index]}, '
430
+ f'failure_cycle={axis.cycle[inst.end_index]}'
431
431
  )
432
432
  return [inst.return_value for inst in completed if inst.return_value is not None]
433
433
 
434
434
  def _records(self, completed: list[PatternInstance]) -> MatchRecords:
435
435
  axis = self._require_axis()
436
- completed.sort(key=lambda i: (int(axis.clock[i.start_index]), i.order))
436
+ completed.sort(key=lambda i: (int(axis.cycle[i.start_index]), i.order))
437
437
  start_index_arr = np.array([i.start_index for i in completed], dtype=np.int64)
438
438
  end_index_arr = np.array([i.end_index for i in completed], dtype=np.int64)
439
439
  start_cycle_arr = np.array(
440
- [int(axis.clock[i.start_index]) for i in completed], dtype=np.int64
440
+ [int(axis.cycle[i.start_index]) for i in completed], dtype=np.int64
441
441
  )
442
- end_cycle_arr = np.array([int(axis.clock[i.end_index]) for i in completed], dtype=np.int64)
442
+ end_cycle_arr = np.array([int(axis.cycle[i.end_index]) for i in completed], dtype=np.int64)
443
443
  start_time_arr = np.array(
444
444
  [int(axis.time[i.start_index]) for i in completed], dtype=np.int64
445
445
  )
@@ -493,10 +493,10 @@ class PatternRuntime:
493
493
  self._axis = waveform
494
494
  self._validated_waveform_ids.add(waveform_id)
495
495
  return
496
- if len(waveform.clock) != len(self._axis.clock):
497
- raise PatternError('Waveform clock arrays have different lengths')
498
- if not np.array_equal(waveform.clock, self._axis.clock):
499
- raise PatternError('Waveform clock arrays are not aligned')
496
+ if len(waveform.cycle) != len(self._axis.cycle):
497
+ raise PatternError('Waveform cycle arrays have different lengths')
498
+ if not np.array_equal(waveform.cycle, self._axis.cycle):
499
+ raise PatternError('Waveform cycle arrays are not aligned')
500
500
  self._validated_waveform_ids.add(waveform_id)
501
501
 
502
502
  def resolve_channel(
@@ -0,0 +1 @@
1
+ """Waveform reader backends and hierarchy types."""