wavekit 0.8.0__cp313-cp313-pyemscripten_2025_0_wasm32.whl
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/__init__.py +73 -0
- wavekit/expression.py +165 -0
- wavekit/pattern/__init__.py +23 -0
- wavekit/pattern/compiler.py +212 -0
- wavekit/pattern/dsl.py +422 -0
- wavekit/pattern/errors.py +5 -0
- wavekit/pattern/result.py +247 -0
- wavekit/pattern/runtime.py +587 -0
- wavekit/pattern/steps.py +163 -0
- wavekit/readers/__init__.py +1 -0
- wavekit/readers/base.py +989 -0
- wavekit/readers/clock_domain.py +57 -0
- wavekit/readers/expr_parser.py +65 -0
- wavekit/readers/fsdb/__init__.py +1 -0
- wavekit/readers/fsdb/npi_fsdb.pxd +82 -0
- wavekit/readers/fsdb/npi_fsdb_reader.cpython-313-wasm32-emscripten.so +0 -0
- wavekit/readers/fsdb/npi_fsdb_reader.pyx +670 -0
- wavekit/readers/fsdb/reader.py +214 -0
- wavekit/readers/fst/__init__.py +1 -0
- wavekit/readers/fst/reader.py +11 -0
- wavekit/readers/hierarchy.py +449 -0
- wavekit/readers/matcher.py +407 -0
- wavekit/readers/range.py +16 -0
- wavekit/readers/value_change.cpython-313-wasm32-emscripten.so +0 -0
- wavekit/readers/value_change.pyx +125 -0
- wavekit/readers/vcd/__init__.py +1 -0
- wavekit/readers/vcd/reader.py +11 -0
- wavekit/readers/wellen/__init__.py +1 -0
- wavekit/readers/wellen/reader.py +252 -0
- wavekit/waveform.py +1572 -0
- wavekit-0.8.0.dist-info/METADATA +125 -0
- wavekit-0.8.0.dist-info/RECORD +34 -0
- wavekit-0.8.0.dist-info/WHEEL +5 -0
- wavekit-0.8.0.dist-info/licenses/LICENSE +21 -0
wavekit/__init__.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Clock-synchronised analysis for VCD, FST, and FSDB waveforms."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from importlib import metadata
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
__version__ = metadata.version('wavekit')
|
|
9
|
+
except metadata.PackageNotFoundError:
|
|
10
|
+
__version__ = 'unknown'
|
|
11
|
+
|
|
12
|
+
from .readers.clock_domain import ClockDomain as ClockDomain
|
|
13
|
+
from .readers.fst.reader import FstReader as FstReader
|
|
14
|
+
from .readers.hierarchy import Node as Node
|
|
15
|
+
from .readers.hierarchy import Scope as Scope
|
|
16
|
+
from .readers.hierarchy import Signal as Signal
|
|
17
|
+
from .readers.hierarchy import SignalCompositeType as SignalCompositeType
|
|
18
|
+
from .readers.matcher import BraceCapture as BraceCapture
|
|
19
|
+
from .readers.matcher import Capture as Capture
|
|
20
|
+
from .readers.matcher import ExactCapture as ExactCapture
|
|
21
|
+
from .readers.matcher import RegexCapture as RegexCapture
|
|
22
|
+
from .readers.matcher import WildcardCapture as WildcardCapture
|
|
23
|
+
from .readers.range import Range as Range
|
|
24
|
+
from .readers.vcd.reader import VcdReader as VcdReader
|
|
25
|
+
from .waveform import Waveform as Waveform
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
'Waveform',
|
|
29
|
+
'VcdReader',
|
|
30
|
+
'FsdbReader',
|
|
31
|
+
'FstReader',
|
|
32
|
+
'ClockDomain',
|
|
33
|
+
'Node',
|
|
34
|
+
'Scope',
|
|
35
|
+
'Range',
|
|
36
|
+
'Signal',
|
|
37
|
+
'SignalCompositeType',
|
|
38
|
+
'Capture',
|
|
39
|
+
'ExactCapture',
|
|
40
|
+
'BraceCapture',
|
|
41
|
+
'RegexCapture',
|
|
42
|
+
'WildcardCapture',
|
|
43
|
+
'has_fsdb_support',
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
from .readers.fsdb.npi_fsdb_reader import fsdb_runtime_available as _fsdb_runtime_available
|
|
48
|
+
from .readers.fsdb.reader import FsdbReader as FsdbReader
|
|
49
|
+
except Exception as _fsdb_import_error:
|
|
50
|
+
_fsdb_available = False
|
|
51
|
+
|
|
52
|
+
def has_fsdb_support() -> bool:
|
|
53
|
+
"""Check whether the Verdi FSDB runtime is available."""
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
class _FsdbReaderStub:
|
|
57
|
+
"""Placeholder that raises an error when the Verdi FSDB runtime is unavailable."""
|
|
58
|
+
|
|
59
|
+
def __init__(self, *args, **kwargs):
|
|
60
|
+
raise RuntimeError(
|
|
61
|
+
'FsdbReader requires the Verdi FSDB runtime (libNPI.so).\n\n'
|
|
62
|
+
'Set WAVEKIT_NPI_LIB to the library path, set VERDI_HOME to the Verdi '
|
|
63
|
+
'installation directory, or ensure libNPI.so is in LD_LIBRARY_PATH.\n\n'
|
|
64
|
+
f'Import error: {_fsdb_import_error}' # noqa: F821
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
FsdbReader = _FsdbReaderStub # type: ignore[assignment]
|
|
68
|
+
else:
|
|
69
|
+
_fsdb_available = True
|
|
70
|
+
|
|
71
|
+
def has_fsdb_support() -> bool:
|
|
72
|
+
"""Check whether the Verdi FSDB runtime library is installed."""
|
|
73
|
+
return _fsdb_runtime_available()
|
wavekit/expression.py
ADDED
|
@@ -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
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Protocol pattern matching for waveform analysis."""
|
|
2
|
+
|
|
3
|
+
from .dsl import Pattern as Pattern
|
|
4
|
+
from .dsl import collect as collect
|
|
5
|
+
from .dsl import match as match
|
|
6
|
+
from .errors import PatternError as PatternError
|
|
7
|
+
from .result import MatchPoint as MatchPoint
|
|
8
|
+
from .result import MatchRecord as MatchRecord
|
|
9
|
+
from .result import MatchRecords as MatchRecords
|
|
10
|
+
from .result import MatchStatus as MatchStatus
|
|
11
|
+
from .steps import Channel as Channel
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
'Pattern',
|
|
15
|
+
'match',
|
|
16
|
+
'collect',
|
|
17
|
+
'MatchPoint',
|
|
18
|
+
'MatchRecord',
|
|
19
|
+
'MatchRecords',
|
|
20
|
+
'MatchStatus',
|
|
21
|
+
'PatternError',
|
|
22
|
+
'Channel',
|
|
23
|
+
]
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable, Hashable
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from ..waveform import Waveform
|
|
7
|
+
from .errors import PatternError
|
|
8
|
+
from .runtime import PatternContext
|
|
9
|
+
from .steps import (
|
|
10
|
+
BranchStep,
|
|
11
|
+
CaptureStep,
|
|
12
|
+
Channel,
|
|
13
|
+
ChannelValue,
|
|
14
|
+
Condition,
|
|
15
|
+
ConsumeStep,
|
|
16
|
+
DelayStep,
|
|
17
|
+
IntValue,
|
|
18
|
+
LoopStep,
|
|
19
|
+
MessageValue,
|
|
20
|
+
RepeatStep,
|
|
21
|
+
RequireStep,
|
|
22
|
+
SignalValue,
|
|
23
|
+
Step,
|
|
24
|
+
WaitStep,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def infer_declarative_axis(steps: list[Step]) -> Waveform | None:
|
|
29
|
+
"""Infer a minimal first-static-waveform scan axis hint from steps."""
|
|
30
|
+
# Axis inference is intentionally minimal. It only finds a static waveform
|
|
31
|
+
# so start/end_cycle can be translated to indices; full cycle-axis alignment
|
|
32
|
+
# validation remains lazy in PatternRuntime.note_waveform().
|
|
33
|
+
for step in steps:
|
|
34
|
+
if isinstance(step, (WaitStep, ConsumeStep)):
|
|
35
|
+
if isinstance(step.cond, Waveform):
|
|
36
|
+
return step.cond
|
|
37
|
+
if isinstance(step.require, Waveform):
|
|
38
|
+
return step.require
|
|
39
|
+
elif isinstance(step, DelayStep):
|
|
40
|
+
if isinstance(step.require, Waveform):
|
|
41
|
+
return step.require
|
|
42
|
+
elif isinstance(step, CaptureStep):
|
|
43
|
+
if isinstance(step.signal, Waveform):
|
|
44
|
+
return step.signal
|
|
45
|
+
elif isinstance(step, RequireStep):
|
|
46
|
+
if isinstance(step.cond, Waveform):
|
|
47
|
+
return step.cond
|
|
48
|
+
elif isinstance(step, LoopStep):
|
|
49
|
+
if isinstance(step.until, Waveform):
|
|
50
|
+
return step.until
|
|
51
|
+
if isinstance(step.when, Waveform):
|
|
52
|
+
return step.when
|
|
53
|
+
waveform = infer_declarative_axis(step.body_template)
|
|
54
|
+
if waveform is not None:
|
|
55
|
+
return waveform
|
|
56
|
+
elif isinstance(step, RepeatStep):
|
|
57
|
+
waveform = infer_declarative_axis(step.body_template)
|
|
58
|
+
if waveform is not None:
|
|
59
|
+
return waveform
|
|
60
|
+
elif isinstance(step, BranchStep):
|
|
61
|
+
if isinstance(step.cond, Waveform):
|
|
62
|
+
return step.cond
|
|
63
|
+
if step.true_body:
|
|
64
|
+
waveform = infer_declarative_axis(step.true_body)
|
|
65
|
+
if waveform is not None:
|
|
66
|
+
return waveform
|
|
67
|
+
if step.false_body:
|
|
68
|
+
waveform = infer_declarative_axis(step.false_body)
|
|
69
|
+
if waveform is not None:
|
|
70
|
+
return waveform
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def compile_declarative_pattern(steps: list[Step]) -> Callable[[PatternContext], Any]:
|
|
75
|
+
"""Compile declarative pattern steps into a program body."""
|
|
76
|
+
|
|
77
|
+
def eval_condition(cond: Condition, ctx: PatternContext) -> bool:
|
|
78
|
+
if isinstance(cond, Waveform):
|
|
79
|
+
return bool(ctx.value(cond))
|
|
80
|
+
if isinstance(cond, bool):
|
|
81
|
+
return cond
|
|
82
|
+
if callable(cond):
|
|
83
|
+
return bool(cond(ctx.index, ctx.captures))
|
|
84
|
+
raise PatternError(
|
|
85
|
+
f'condition must be a Waveform, bool, or callable, got {type(cond).__name__}'
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def eval_signal(signal: SignalValue, ctx: PatternContext) -> Any:
|
|
89
|
+
if isinstance(signal, Waveform):
|
|
90
|
+
return ctx.value(signal)
|
|
91
|
+
if callable(signal):
|
|
92
|
+
return signal(ctx.index, ctx.captures)
|
|
93
|
+
raise PatternError(f'signal must be a Waveform or callable, got {type(signal).__name__}')
|
|
94
|
+
|
|
95
|
+
def eval_int(value: IntValue, ctx: PatternContext) -> int:
|
|
96
|
+
if callable(value):
|
|
97
|
+
value = value(ctx.index, ctx.captures)
|
|
98
|
+
if isinstance(value, int):
|
|
99
|
+
return value
|
|
100
|
+
raise PatternError(f'integer value must be an int or callable, got {type(value).__name__}')
|
|
101
|
+
|
|
102
|
+
def eval_message(message: MessageValue | None, ctx: PatternContext) -> str | None:
|
|
103
|
+
if message is None or isinstance(message, str):
|
|
104
|
+
return message
|
|
105
|
+
if callable(message):
|
|
106
|
+
return str(message(ctx.index, ctx.captures))
|
|
107
|
+
raise PatternError(f'message must be a str or callable, got {type(message).__name__}')
|
|
108
|
+
|
|
109
|
+
def adapt_message(message: MessageValue | None, ctx: PatternContext):
|
|
110
|
+
if message is None:
|
|
111
|
+
return None
|
|
112
|
+
return lambda message=message: eval_message(message, ctx)
|
|
113
|
+
|
|
114
|
+
def adapt_channel(
|
|
115
|
+
channel: ChannelValue, ctx: PatternContext
|
|
116
|
+
) -> Channel | Hashable | Callable[[], Channel | Hashable]:
|
|
117
|
+
if callable(channel):
|
|
118
|
+
return lambda channel=channel: channel(ctx.index, ctx.captures)
|
|
119
|
+
return channel
|
|
120
|
+
|
|
121
|
+
def run_steps(ctx: PatternContext, step_list: list[Step]) -> None:
|
|
122
|
+
for step in step_list:
|
|
123
|
+
if isinstance(step, WaitStep):
|
|
124
|
+
ctx.wait(
|
|
125
|
+
lambda step=step: eval_condition(step.cond, ctx),
|
|
126
|
+
require=None
|
|
127
|
+
if step.require is None
|
|
128
|
+
else lambda step=step: eval_condition(step.require, ctx),
|
|
129
|
+
require_message=adapt_message(step.require_message, ctx),
|
|
130
|
+
)
|
|
131
|
+
elif isinstance(step, ConsumeStep):
|
|
132
|
+
ctx.consume(
|
|
133
|
+
lambda step=step: eval_condition(step.cond, ctx),
|
|
134
|
+
channel=adapt_channel(step.channel, ctx),
|
|
135
|
+
require=None
|
|
136
|
+
if step.require is None
|
|
137
|
+
else lambda step=step: eval_condition(step.require, ctx),
|
|
138
|
+
require_message=adapt_message(step.require_message, ctx),
|
|
139
|
+
)
|
|
140
|
+
elif isinstance(step, DelayStep):
|
|
141
|
+
ctx.delay(
|
|
142
|
+
eval_int(step.n, ctx),
|
|
143
|
+
require=None
|
|
144
|
+
if step.require is None
|
|
145
|
+
else lambda step=step: eval_condition(step.require, ctx),
|
|
146
|
+
require_message=adapt_message(step.require_message, ctx),
|
|
147
|
+
)
|
|
148
|
+
elif isinstance(step, CaptureStep):
|
|
149
|
+
ctx.capture(step.name, eval_signal(step.signal, ctx), mode=step.mode)
|
|
150
|
+
elif isinstance(step, RequireStep):
|
|
151
|
+
ctx.require(
|
|
152
|
+
lambda step=step: eval_condition(step.cond, ctx),
|
|
153
|
+
message=adapt_message(step.message, ctx),
|
|
154
|
+
)
|
|
155
|
+
elif isinstance(step, BranchStep):
|
|
156
|
+
if eval_condition(step.cond, ctx):
|
|
157
|
+
if step.true_body:
|
|
158
|
+
run_steps(ctx, step.true_body)
|
|
159
|
+
elif step.false_body:
|
|
160
|
+
run_steps(ctx, step.false_body)
|
|
161
|
+
elif isinstance(step, RepeatStep):
|
|
162
|
+
count = eval_int(step.n, ctx)
|
|
163
|
+
if count < 0:
|
|
164
|
+
raise PatternError(f'repeat count must be non-negative, got {count}')
|
|
165
|
+
for _ in range(count):
|
|
166
|
+
run_steps(ctx, step.body_template)
|
|
167
|
+
elif isinstance(step, LoopStep):
|
|
168
|
+
if step.when is not None:
|
|
169
|
+
while eval_condition(step.when, ctx):
|
|
170
|
+
ctx._guard_operation()
|
|
171
|
+
run_steps(ctx, step.body_template)
|
|
172
|
+
elif step.until is not None:
|
|
173
|
+
while True:
|
|
174
|
+
ctx._guard_operation()
|
|
175
|
+
run_steps(ctx, step.body_template)
|
|
176
|
+
if eval_condition(step.until, ctx):
|
|
177
|
+
break
|
|
178
|
+
else:
|
|
179
|
+
raise PatternError("loop step must specify either 'until' or 'when'")
|
|
180
|
+
else:
|
|
181
|
+
raise PatternError(f'Unknown step type: {type(step).__name__}')
|
|
182
|
+
|
|
183
|
+
def body(ctx: PatternContext) -> object:
|
|
184
|
+
first_step = steps[0] if steps else None
|
|
185
|
+
if isinstance(first_step, WaitStep):
|
|
186
|
+
# The first blocking step selects candidate start cycles; later
|
|
187
|
+
# blocking steps describe timing inside a matched transaction.
|
|
188
|
+
if not eval_condition(first_step.cond, ctx):
|
|
189
|
+
if first_step.require is not None:
|
|
190
|
+
ctx.require(
|
|
191
|
+
lambda first_step=first_step: eval_condition(first_step.require, ctx),
|
|
192
|
+
message=adapt_message(first_step.require_message, ctx),
|
|
193
|
+
)
|
|
194
|
+
return None
|
|
195
|
+
run_steps(ctx, steps[1:])
|
|
196
|
+
elif isinstance(first_step, ConsumeStep):
|
|
197
|
+
if not ctx.try_consume(
|
|
198
|
+
lambda first_step=first_step: eval_condition(first_step.cond, ctx),
|
|
199
|
+
adapt_channel(first_step.channel, ctx),
|
|
200
|
+
):
|
|
201
|
+
if first_step.require is not None:
|
|
202
|
+
ctx.require(
|
|
203
|
+
lambda first_step=first_step: eval_condition(first_step.require, ctx),
|
|
204
|
+
message=adapt_message(first_step.require_message, ctx),
|
|
205
|
+
)
|
|
206
|
+
return None
|
|
207
|
+
run_steps(ctx, steps[1:])
|
|
208
|
+
else:
|
|
209
|
+
run_steps(ctx, steps)
|
|
210
|
+
return ctx.OK
|
|
211
|
+
|
|
212
|
+
return body
|