wavekit 0.7.1__tar.gz → 0.7.3__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 (32) hide show
  1. wavekit-0.7.3/PKG-INFO +134 -0
  2. wavekit-0.7.3/README.md +109 -0
  3. {wavekit-0.7.1 → wavekit-0.7.3}/pyproject.toml +9 -2
  4. {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/__init__.py +0 -2
  5. wavekit-0.7.3/src/wavekit/expression.py +165 -0
  6. {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/pattern/dsl.py +17 -9
  7. {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/pattern/result.py +5 -1
  8. {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/pattern/runtime.py +1 -1
  9. wavekit-0.7.3/src/wavekit/readers/__init__.py +1 -0
  10. {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/readers/base.py +219 -157
  11. wavekit-0.7.3/src/wavekit/readers/fsdb/__init__.py +1 -0
  12. {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/readers/fsdb/reader.py +5 -2
  13. wavekit-0.7.3/src/wavekit/readers/fst/__init__.py +1 -0
  14. {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/readers/fst/reader.py +7 -4
  15. {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/readers/hierarchy.py +8 -11
  16. {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/readers/matcher.py +2 -5
  17. wavekit-0.7.3/src/wavekit/readers/vcd/__init__.py +1 -0
  18. {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/readers/vcd/reader.py +8 -5
  19. {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/waveform.py +134 -139
  20. wavekit-0.7.1/PKG-INFO +0 -556
  21. wavekit-0.7.1/README.md +0 -533
  22. {wavekit-0.7.1 → wavekit-0.7.3}/LICENSE +0 -0
  23. {wavekit-0.7.1 → wavekit-0.7.3}/build.py +0 -0
  24. {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/pattern/__init__.py +0 -0
  25. {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/pattern/compiler.py +0 -0
  26. {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/pattern/errors.py +0 -0
  27. {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/pattern/steps.py +0 -0
  28. {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/readers/expr_parser.py +0 -0
  29. {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/readers/fsdb/npi_fsdb.pxd +0 -0
  30. {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/readers/fsdb/npi_fsdb_reader.pyx +0 -0
  31. {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/readers/range.py +0 -0
  32. {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/readers/value_change.pyx +0 -0
wavekit-0.7.3/PKG-INFO ADDED
@@ -0,0 +1,134 @@
1
+ Metadata-Version: 2.4
2
+ Name: wavekit
3
+ Version: 0.7.3
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: pylibfst (>=0.2.1,<0.3.0)
19
+ Requires-Dist: pytest (>=8.2.2,<9.0.0)
20
+ Requires-Dist: vcdvcd (>=2.3.5,<3.0.0)
21
+ Project-URL: Documentation, https://cxzzzz.github.io/wavekit
22
+ Project-URL: Repository, https://github.com/cxzzzz/wavekit
23
+ Description-Content-Type: text/markdown
24
+
25
+ # <img src="docs/en/images/logo.svg" alt="" width="30" height="30" valign="middle"> wavekit
26
+
27
+ [![CI](https://github.com/cxzzzz/wavekit/actions/workflows/python-package.yml/badge.svg)](https://github.com/cxzzzz/wavekit/actions/workflows/python-package.yml)
28
+ [![PyPI version](https://img.shields.io/pypi/v/wavekit.svg)](https://pypi.org/project/wavekit/)
29
+ [![Python Versions](https://img.shields.io/pypi/pyversions/wavekit.svg)](https://pypi.org/project/wavekit/)
30
+ [![Downloads](https://pepy.tech/badge/wavekit)](https://pepy.tech/project/wavekit)
31
+ [![License](https://img.shields.io/github/license/cxzzzz/wavekit.svg)](LICENSE)
32
+
33
+ English | [中文](README_ZH.md)
34
+
35
+ **Wavekit is a Python library for high-level digital waveform analysis.**
36
+ Wavekit supports common digital waveform formats; by combining batch signal
37
+ queries with analysis at the cycle and transaction levels, it helps users
38
+ interpret recorded signal changes in terms of per-cycle state and temporal
39
+ relationships.
40
+
41
+ > **AI integration:** [wavekit-mcp](https://github.com/cxzzzz/wavekit-mcp) exposes wavekit analysis through
42
+ > MCP tools for AI-assisted workflows.
43
+
44
+ ## Features
45
+
46
+ - **Flexible signal queries:** find and batch-load related signals from
47
+ hierarchical waveform data using multiple path-matching options.
48
+ - **Cycle-level analysis:** use a range of waveform operations on clock-sampled
49
+ data to analyze cycle-based behavior such as interface backpressure and FIFO
50
+ occupancy.
51
+ - **Transaction-level analysis:** use temporal pattern matching to describe
52
+ signal relationships across multiple clock cycles for protocol analysis,
53
+ transaction extraction, and latency measurement.
54
+ - **Multi-format waveform support:** load and analyze VCD, FST, and FSDB files
55
+ using the same API.
56
+
57
+ ## Installation
58
+
59
+ ```console
60
+ python -m pip install wavekit
61
+ ```
62
+
63
+ wavekit supports Python 3.9 and newer. FSDB support requires the Verdi NPI
64
+ runtime (`libNPI.so`); see the [installation guide](https://cxzzzz.github.io/wavekit/getting-started/installation/) for
65
+ setup details.
66
+
67
+ ## Quick start
68
+
69
+ Calculate the occupancy of a FIFO from a VCD waveform:
70
+
71
+ ```python
72
+ import numpy as np
73
+
74
+ from wavekit import VcdReader
75
+
76
+ with VcdReader('simulation.vcd') as r:
77
+ clock = 'tb.clk'
78
+ depth = 16
79
+
80
+ w_ptr = r.load_waveform(
81
+ 'tb.u_fifo.w_ptr',
82
+ clock=clock,
83
+ )
84
+ r_ptr = r.load_waveform(
85
+ 'tb.u_fifo.r_ptr',
86
+ clock=clock,
87
+ )
88
+
89
+ occupancy = (w_ptr + depth - r_ptr) % depth
90
+
91
+ print('Average occupancy:', np.mean(occupancy.value))
92
+ ```
93
+
94
+ See the [first waveform tutorial](https://cxzzzz.github.io/wavekit/getting-started/first-waveform/) for a
95
+ complete runnable FIFO example.
96
+
97
+ ## Documentation
98
+
99
+ See the [documentation](https://cxzzzz.github.io/wavekit/) for the full reference and guides:
100
+
101
+ - [Installation](https://cxzzzz.github.io/wavekit/getting-started/installation/)
102
+ - [First waveform](https://cxzzzz.github.io/wavekit/getting-started/first-waveform/)
103
+ - [Reader guide](https://cxzzzz.github.io/wavekit/guides/reader/)
104
+ - [Signal queries](https://cxzzzz.github.io/wavekit/guides/signal-query/)
105
+ - [Waveform analysis](https://cxzzzz.github.io/wavekit/guides/waveform-analysis/)
106
+ - [Pattern matching](https://cxzzzz.github.io/wavekit/guides/pattern-matching/)
107
+ - [API reference](https://cxzzzz.github.io/wavekit/reference/api/)
108
+ - [Complete examples](https://cxzzzz.github.io/wavekit/examples/)
109
+
110
+ ## Development
111
+
112
+ Install the development dependencies and run the checks with Poetry:
113
+
114
+ ```console
115
+ poetry install
116
+ poetry run pytest
117
+ poetry run ruff check .
118
+ poetry run ruff format --check .
119
+ poetry run mypy
120
+ ```
121
+
122
+ Build the documentation locally with Python 3.10 or newer:
123
+
124
+ ```console
125
+ poetry install --with docs
126
+ poetry run zensical build --clean --strict
127
+ ```
128
+
129
+ See [Contributing](https://cxzzzz.github.io/wavekit/contributing/) for contribution guidelines.
130
+
131
+ ## License
132
+
133
+ This project is licensed under the MIT License. See [LICENSE](LICENSE).
134
+
@@ -0,0 +1,109 @@
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
+ Wavekit supports common digital waveform formats; by combining batch signal
13
+ queries with analysis at the cycle and transaction levels, it helps users
14
+ interpret recorded signal changes in terms of per-cycle state and temporal
15
+ relationships.
16
+
17
+ > **AI integration:** [wavekit-mcp](https://github.com/cxzzzz/wavekit-mcp) exposes wavekit analysis through
18
+ > MCP tools for AI-assisted workflows.
19
+
20
+ ## Features
21
+
22
+ - **Flexible signal queries:** find and batch-load related signals from
23
+ hierarchical waveform data using multiple path-matching options.
24
+ - **Cycle-level analysis:** use a range of waveform operations on clock-sampled
25
+ data to analyze cycle-based behavior such as interface backpressure and FIFO
26
+ occupancy.
27
+ - **Transaction-level analysis:** use temporal pattern matching to describe
28
+ signal relationships across multiple clock cycles for protocol analysis,
29
+ transaction extraction, and latency measurement.
30
+ - **Multi-format waveform support:** load and analyze VCD, FST, and FSDB files
31
+ using the same API.
32
+
33
+ ## Installation
34
+
35
+ ```console
36
+ python -m pip install wavekit
37
+ ```
38
+
39
+ wavekit supports Python 3.9 and newer. FSDB support requires the Verdi NPI
40
+ runtime (`libNPI.so`); see the [installation guide](https://cxzzzz.github.io/wavekit/getting-started/installation/) for
41
+ setup details.
42
+
43
+ ## Quick start
44
+
45
+ Calculate the occupancy of a FIFO from a VCD waveform:
46
+
47
+ ```python
48
+ import numpy as np
49
+
50
+ from wavekit import VcdReader
51
+
52
+ with VcdReader('simulation.vcd') as r:
53
+ clock = 'tb.clk'
54
+ depth = 16
55
+
56
+ w_ptr = r.load_waveform(
57
+ 'tb.u_fifo.w_ptr',
58
+ clock=clock,
59
+ )
60
+ r_ptr = r.load_waveform(
61
+ 'tb.u_fifo.r_ptr',
62
+ clock=clock,
63
+ )
64
+
65
+ occupancy = (w_ptr + depth - r_ptr) % depth
66
+
67
+ print('Average occupancy:', np.mean(occupancy.value))
68
+ ```
69
+
70
+ See the [first waveform tutorial](https://cxzzzz.github.io/wavekit/getting-started/first-waveform/) for a
71
+ complete runnable FIFO example.
72
+
73
+ ## Documentation
74
+
75
+ See the [documentation](https://cxzzzz.github.io/wavekit/) for the full reference and guides:
76
+
77
+ - [Installation](https://cxzzzz.github.io/wavekit/getting-started/installation/)
78
+ - [First waveform](https://cxzzzz.github.io/wavekit/getting-started/first-waveform/)
79
+ - [Reader guide](https://cxzzzz.github.io/wavekit/guides/reader/)
80
+ - [Signal queries](https://cxzzzz.github.io/wavekit/guides/signal-query/)
81
+ - [Waveform analysis](https://cxzzzz.github.io/wavekit/guides/waveform-analysis/)
82
+ - [Pattern matching](https://cxzzzz.github.io/wavekit/guides/pattern-matching/)
83
+ - [API reference](https://cxzzzz.github.io/wavekit/reference/api/)
84
+ - [Complete examples](https://cxzzzz.github.io/wavekit/examples/)
85
+
86
+ ## Development
87
+
88
+ Install the development dependencies and run the checks with Poetry:
89
+
90
+ ```console
91
+ poetry install
92
+ poetry run pytest
93
+ poetry run ruff check .
94
+ poetry run ruff format --check .
95
+ poetry run mypy
96
+ ```
97
+
98
+ Build the documentation locally with Python 3.10 or newer:
99
+
100
+ ```console
101
+ poetry install --with docs
102
+ poetry run zensical build --clean --strict
103
+ ```
104
+
105
+ See [Contributing](https://cxzzzz.github.io/wavekit/contributing/) for contribution guidelines.
106
+
107
+ ## License
108
+
109
+ This project is licensed under the MIT License. See [LICENSE](LICENSE).
@@ -1,10 +1,11 @@
1
1
  [tool.poetry]
2
2
  name = "wavekit"
3
- version = "0.7.1"
4
- description = "a fundamental package for digital circuit waveform analysis"
3
+ version = "0.7.3"
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
+ documentation = "https://cxzzzz.github.io/wavekit"
8
9
  build = "build.py"
9
10
  include = [
10
11
  { path = "src/wavekit/**/*.so", format = "wheel" },
@@ -16,11 +17,17 @@ numpy = "^2.0.0"
16
17
  vcdvcd = "^2.3.5"
17
18
  pylibfst = "^0.2.1"
18
19
  pytest = "^8.2.2"
20
+ lark = ">=1.2,<2"
19
21
 
20
22
  [tool.poetry.group.dev.dependencies]
21
23
  ruff = "^0.6.2"
22
24
  mypy = "^1.11.2"
23
25
 
26
+
27
+ [tool.poetry.group.docs.dependencies]
28
+ zensical = {version = "^0.0.57", python = ">=3.10,<4.0"}
29
+ mkdocstrings-python = {version = "1.18.2", python = ">=3.10,<4.0"}
30
+
24
31
  [tool.ruff]
25
32
  target-version = "py39"
26
33
  line-length = 100
@@ -16,7 +16,6 @@ from .readers.hierarchy import Signal as Signal
16
16
  from .readers.hierarchy import SignalCompositeType as SignalCompositeType
17
17
  from .readers.matcher import BraceCapture as BraceCapture
18
18
  from .readers.matcher import Capture as Capture
19
- from .readers.matcher import CaptureKey as CaptureKey
20
19
  from .readers.matcher import ExactCapture as ExactCapture
21
20
  from .readers.matcher import RegexCapture as RegexCapture
22
21
  from .readers.matcher import WildcardCapture as WildcardCapture
@@ -35,7 +34,6 @@ __all__ = [
35
34
  'Signal',
36
35
  'SignalCompositeType',
37
36
  'Capture',
38
- 'CaptureKey',
39
37
  'ExactCapture',
40
38
  'BraceCapture',
41
39
  'RegexCapture',
@@ -0,0 +1,165 @@
1
+ """Parsing and evaluating WaveKit waveform expressions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from typing import Any
7
+
8
+ from lark import Lark, Transformer
9
+
10
+ _EXPRESSION_GRAMMAR = r"""
11
+ ?start: expr
12
+ ?expr: bit_or
13
+ ?bit_or: bit_xor | bit_or VBAR bit_xor
14
+ ?bit_xor: bit_and | bit_xor CIRCUMFLEX bit_and
15
+ ?bit_and: comparison | bit_and AMPERSAND comparison
16
+ ?comparison: shift
17
+ | comparison EQEQ shift
18
+ | comparison NEQ shift
19
+ | comparison LE shift
20
+ | comparison LT shift
21
+ | comparison GE shift
22
+ | comparison GT shift
23
+ ?shift: sum | shift LSHIFT sum | shift RSHIFT sum
24
+ ?sum: product | sum PLUS product | sum MINUS product
25
+ ?product: unary
26
+ | product STAR unary
27
+ | product SLASH unary
28
+ | product FLOOR unary
29
+ | product PERCENT unary
30
+ | product POWER unary
31
+ ?unary: TILDE unary | PLUS unary | MINUS unary | atom
32
+ ?atom: call | path | NUMBER | group
33
+ group: LPAR expr RPAR
34
+ call: NAME LPAR [expr (COMMA expr)*] RPAR
35
+ path: segment (DOT segment)* SUBSCRIPT*
36
+ segment: segment_text | WILDCARD
37
+ segment_text: segment_piece+
38
+ ?segment_piece: NAME | MODULE | BRACE | REGEX
39
+
40
+ MODULE: /\${1,2}/
41
+ BRACE: /\{[^{}]*\}/
42
+ WILDCARD.3: "**" | "*"
43
+ REGEX: /\/(?:\\.|[^\/\\])*\//
44
+ SUBSCRIPT: /\[\d+(?::\d+)?\]/
45
+ NAME: /[A-Za-z_][A-Za-z0-9_]*/
46
+ HEX_NUMBER.2: /0[xX][0-9a-fA-F](?:_?[0-9a-fA-F])*/
47
+ BIN_NUMBER.2: /0[bB][01](?:_?[01])*/
48
+ OCT_NUMBER.2: /0[oO][0-7](?:_?[0-7])*/
49
+ DECIMAL_FLOAT_NUMBER.2: /(?:\d(?:_?\d)*)?\.(?:\d(?:_?\d)*)?(?:[eE][+-]?\d(?:_?\d)*)?/
50
+ EXP_NUMBER.2: /\d(?:_?\d)*[eE][+-]?\d(?:_?\d)*/
51
+ DEC_NUMBER.1: /\d(?:_?\d)*/
52
+ NUMBER: HEX_NUMBER | BIN_NUMBER | OCT_NUMBER | DECIMAL_FLOAT_NUMBER | EXP_NUMBER | DEC_NUMBER
53
+
54
+ VBAR: "|"
55
+ CIRCUMFLEX: "^"
56
+ AMPERSAND: "&"
57
+ EQEQ: "=="
58
+ NEQ: "!="
59
+ LE: "<="
60
+ LT: "<"
61
+ GE: ">="
62
+ GT: ">"
63
+ LSHIFT: "<<"
64
+ RSHIFT: ">>"
65
+ PLUS: "+"
66
+ MINUS: "-"
67
+ STAR: "*"
68
+ SLASH: "/"
69
+ FLOOR: "//"
70
+ PERCENT: "%"
71
+ POWER: "**"
72
+ TILDE: "~"
73
+ LPAR: "("
74
+ RPAR: ")"
75
+ COMMA: ","
76
+ DOT: "."
77
+
78
+ %import common.WS_INLINE
79
+ %ignore WS_INLINE
80
+ """
81
+
82
+ _EXPRESSION_PARSER = Lark(
83
+ _EXPRESSION_GRAMMAR,
84
+ parser='lalr',
85
+ lexer='contextual',
86
+ )
87
+ _EXPRESSION_FUNCTIONS: dict[str, Callable[..., Any]] = {}
88
+
89
+
90
+ def expression_function(func: Callable[..., Any]) -> Callable[..., Any]:
91
+ """Register a Waveform method as callable from an expression string."""
92
+ _EXPRESSION_FUNCTIONS[func.__name__] = func
93
+ return func
94
+
95
+
96
+ class ExpressionTransformer(Transformer):
97
+ def __init__(self, *, function_names: set[str]):
98
+ super().__init__()
99
+ self.function_names = function_names
100
+ self.path_entries: list[tuple[str, str]] = []
101
+
102
+ @staticmethod
103
+ def join(items: list[Any]) -> str:
104
+ return ''.join(str(item) for item in items)
105
+
106
+ def call(self, items: list[Any]) -> str:
107
+ name = str(items[0])
108
+ if name not in self.function_names:
109
+ raise ValueError(f'unknown expression function {name!r}')
110
+ return self.join(items)
111
+
112
+ def path(self, items: list[Any]) -> str:
113
+ path = self.join(items)
114
+ placeholder = f'__wave_{len(self.path_entries)}__'
115
+ self.path_entries.append((placeholder, path))
116
+ return placeholder
117
+
118
+ def __default__(self, data: str, children: list[Any], meta: Any) -> Any:
119
+ return self.join(children)
120
+
121
+ def __default_token__(self, token: Any) -> str:
122
+ return str(token)
123
+
124
+
125
+ def parse_expression(expr: str) -> tuple[str, list[tuple[str, str]]]:
126
+ """Parse an expression and replace each signal path with a placeholder.
127
+
128
+ Parameters
129
+ ----------
130
+ expr:
131
+ WaveKit expression containing physical signal paths.
132
+
133
+ Returns
134
+ -------
135
+ tuple[str, list[tuple[str, str]]]
136
+ The substituted expression and ordered ``(placeholder, path)`` entries.
137
+ """
138
+ tree = _EXPRESSION_PARSER.parse(expr)
139
+ transformer = ExpressionTransformer(
140
+ function_names=set(_EXPRESSION_FUNCTIONS),
141
+ )
142
+ result = transformer.transform(tree)
143
+ return str(result), transformer.path_entries
144
+
145
+
146
+ def evaluate_expression(expr: str, namespace: dict[str, Any]) -> Any:
147
+ """Evaluate an already-parsed expression against preloaded Waveforms.
148
+
149
+ Parameters
150
+ ----------
151
+ expr:
152
+ The substituted expression returned by ``parse_expression``.
153
+ Physical paths must be resolved and replaced before calling this
154
+ function.
155
+ namespace:
156
+ Names available to the expression, normally Waveform objects.
157
+
158
+ Returns
159
+ -------
160
+ Any
161
+ The result produced by the Waveform expression.
162
+ """
163
+ evaluation_namespace = {**_EXPRESSION_FUNCTIONS, **namespace}
164
+ code = compile(expr, '<wavekit-expression>', 'eval')
165
+ return eval(code, {'__builtins__': {}}, evaluation_namespace) # noqa: S307
@@ -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
 
@@ -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
 
@@ -0,0 +1 @@
1
+ """Waveform reader backends and hierarchy types."""