nortl 1.4.0__py3-none-any.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.
- nortl/__init__.py +85 -0
- nortl/components/__init__.py +8 -0
- nortl/components/channel.py +132 -0
- nortl/components/timer.py +73 -0
- nortl/core/__init__.py +40 -0
- nortl/core/checker.py +135 -0
- nortl/core/common/__init__.py +4 -0
- nortl/core/common/access.py +25 -0
- nortl/core/common/debug.py +6 -0
- nortl/core/common/naming_helper.py +33 -0
- nortl/core/constructs/__init__.py +13 -0
- nortl/core/constructs/condition.py +143 -0
- nortl/core/constructs/fork_join.py +84 -0
- nortl/core/constructs/loop.py +138 -0
- nortl/core/engine.py +575 -0
- nortl/core/exceptions.py +139 -0
- nortl/core/manager/__init__.py +6 -0
- nortl/core/manager/scratch_manager.py +128 -0
- nortl/core/manager/signal_manager.py +71 -0
- nortl/core/modifiers.py +136 -0
- nortl/core/module.py +181 -0
- nortl/core/operations.py +834 -0
- nortl/core/parameter.py +88 -0
- nortl/core/process.py +451 -0
- nortl/core/protocols.py +628 -0
- nortl/core/renderers/__init__.py +0 -0
- nortl/core/renderers/operations/__init__.py +34 -0
- nortl/core/renderers/operations/arithmetics.py +38 -0
- nortl/core/renderers/operations/base.py +111 -0
- nortl/core/renderers/operations/comparison.py +44 -0
- nortl/core/renderers/operations/logic.py +38 -0
- nortl/core/renderers/operations/misc.py +26 -0
- nortl/core/renderers/operations/slice.py +30 -0
- nortl/core/signal.py +878 -0
- nortl/core/state.py +201 -0
- nortl/py.typed +0 -0
- nortl/renderer/__init__.py +5 -0
- nortl/renderer/mermaid_renderer.py +38 -0
- nortl/renderer/networkx_renderer.py +29 -0
- nortl/renderer/verilog_renderer.py +325 -0
- nortl/renderer/verilog_utils/__init__.py +6 -0
- nortl/renderer/verilog_utils/formatter.py +29 -0
- nortl/renderer/verilog_utils/process.py +226 -0
- nortl/renderer/verilog_utils/structural.py +146 -0
- nortl/renderer/verilog_utils/utils.py +23 -0
- nortl/utils/__init__.py +0 -0
- nortl/utils/parse_utils.py +37 -0
- nortl/utils/templates/testbench.sv +41 -0
- nortl/utils/test_wrapper.py +218 -0
- nortl/utils/type_aliases.py +15 -0
- nortl/verilog_library/__init__.py +74 -0
- nortl/verilog_library/nortl_clock_gate.sv +20 -0
- nortl/verilog_library/nortl_count_down_timer.sv +50 -0
- nortl/verilog_library/nortl_delay.sv +66 -0
- nortl/verilog_library/nortl_edge_detector.sv +34 -0
- nortl/verilog_library/nortl_sync.sv +28 -0
- nortl-1.4.0.dist-info/METADATA +105 -0
- nortl-1.4.0.dist-info/RECORD +60 -0
- nortl-1.4.0.dist-info/WHEEL +4 -0
- nortl-1.4.0.dist-info/licenses/LICENSE +11 -0
nortl/core/operations.py
ADDED
|
@@ -0,0 +1,834 @@
|
|
|
1
|
+
"""Operations.
|
|
2
|
+
|
|
3
|
+
This module implements the "magic" operations that can be applied to signals or constants.
|
|
4
|
+
The actual rendering (which decides how they are converted to Verilog) is decoupled from this.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from abc import ABCMeta, abstractmethod
|
|
8
|
+
from math import ceil, log2
|
|
9
|
+
from types import GeneratorType
|
|
10
|
+
from typing import Callable, Dict, Final, Generator, Iterable, Literal, Never, Optional, Protocol, Sequence, Set, Type, Union, overload
|
|
11
|
+
|
|
12
|
+
from nortl.core.protocols import ACCESS_CHECKS, Renderable
|
|
13
|
+
from nortl.core.renderers.operations import (
|
|
14
|
+
Addition,
|
|
15
|
+
And,
|
|
16
|
+
Division,
|
|
17
|
+
Equality,
|
|
18
|
+
ExclusiveOr,
|
|
19
|
+
Greater,
|
|
20
|
+
GreaterOrEqual,
|
|
21
|
+
Inversion,
|
|
22
|
+
LeftShift,
|
|
23
|
+
Less,
|
|
24
|
+
LessOrEqual,
|
|
25
|
+
Modulo,
|
|
26
|
+
Multiplication,
|
|
27
|
+
Negative,
|
|
28
|
+
Or,
|
|
29
|
+
Positive,
|
|
30
|
+
RightShift,
|
|
31
|
+
Slice,
|
|
32
|
+
Substraction,
|
|
33
|
+
Unequality,
|
|
34
|
+
)
|
|
35
|
+
from nortl.utils.parse_utils import parse_int
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
'All',
|
|
39
|
+
'Any',
|
|
40
|
+
'Concat',
|
|
41
|
+
'Const',
|
|
42
|
+
'IfThenElse',
|
|
43
|
+
'OperationTrait',
|
|
44
|
+
'SingleOperation',
|
|
45
|
+
'TwoSideOperation',
|
|
46
|
+
'Var',
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
Operand = Union[Renderable, int, bool]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class RendererProto(Protocol):
|
|
54
|
+
def __init__(self, container: object) -> None: ...
|
|
55
|
+
def __call__(self, target: Optional[str] = None) -> str: ...
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def greater_operand_width(a: Renderable, b: Renderable) -> Optional[int]:
|
|
59
|
+
"""Determines the result width of a logic operation by choosing the greater of the two operand widths."""
|
|
60
|
+
if a.operand_width is None or b.operand_width is None:
|
|
61
|
+
return None
|
|
62
|
+
return max(a.operand_width, b.operand_width)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class OperationTrait(metaclass=ABCMeta):
|
|
66
|
+
"""Trait for signals, constants or statements that allow construction of arithmetic and logic operations."""
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
@abstractmethod
|
|
70
|
+
def is_primitive(self) -> bool:
|
|
71
|
+
"""Indicates if this object is a Verilog primitive."""
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
@abstractmethod
|
|
75
|
+
def operand_width(self) -> Optional[int]:
|
|
76
|
+
"""Indicates the width when used as an operand.
|
|
77
|
+
|
|
78
|
+
A width of None means that the width is not fixed during execution of noRTL.
|
|
79
|
+
This will be the case for [parameters][nortl.core.parameter.Parameter] or [constants][nortl.core.operations.Const] without explicit width.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
@abstractmethod
|
|
83
|
+
def read_access(self, ignore: Set[ACCESS_CHECKS] = set()) -> None:
|
|
84
|
+
"""Register read access from the current thread, state and construct depth.
|
|
85
|
+
|
|
86
|
+
For signals, this will register an read access from the current thread.
|
|
87
|
+
For scratch signals, this will in addition check if the signal was released, based on the construct depth.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
@abstractmethod
|
|
91
|
+
def render(self, target: Optional[str] = None) -> str: ...
|
|
92
|
+
|
|
93
|
+
def __format__(self, format_spec: str) -> str:
|
|
94
|
+
return self.render()
|
|
95
|
+
|
|
96
|
+
# Arithemtic Operations
|
|
97
|
+
# TODO all two side operations loose the width
|
|
98
|
+
def __add__(self, value: Operand, /) -> 'TwoSideOperation':
|
|
99
|
+
return TwoSideOperation(left=self, right=value, renderer=Addition)
|
|
100
|
+
|
|
101
|
+
def __sub__(self, value: Operand, /) -> 'TwoSideOperation':
|
|
102
|
+
return TwoSideOperation(left=self, right=value, renderer=Substraction)
|
|
103
|
+
|
|
104
|
+
def __mul__(self, value: Operand, /) -> 'TwoSideOperation':
|
|
105
|
+
return TwoSideOperation(left=self, right=value, renderer=Multiplication)
|
|
106
|
+
|
|
107
|
+
def __truediv__(self, value: Operand, /) -> 'TwoSideOperation':
|
|
108
|
+
return TwoSideOperation(left=self, right=value, renderer=Division)
|
|
109
|
+
|
|
110
|
+
def __mod__(self, value: Operand, /) -> 'TwoSideOperation':
|
|
111
|
+
return TwoSideOperation(left=self, right=value, renderer=Modulo)
|
|
112
|
+
|
|
113
|
+
# Arithmetic Operations (Right-Side)
|
|
114
|
+
def __radd__(self, value: Operand, /) -> 'TwoSideOperation':
|
|
115
|
+
return TwoSideOperation(left=value, right=self, renderer=Addition)
|
|
116
|
+
|
|
117
|
+
def __rsub__(self, value: Operand, /) -> 'TwoSideOperation':
|
|
118
|
+
return TwoSideOperation(left=value, right=self, renderer=Substraction)
|
|
119
|
+
|
|
120
|
+
def __rmul__(self, value: Operand, /) -> 'TwoSideOperation':
|
|
121
|
+
return TwoSideOperation(left=value, right=self, renderer=Multiplication)
|
|
122
|
+
|
|
123
|
+
def __rtruediv__(self, value: Operand, /) -> 'TwoSideOperation':
|
|
124
|
+
return TwoSideOperation(left=value, right=self, renderer=Division)
|
|
125
|
+
|
|
126
|
+
def __rmod__(self, value: Operand, /) -> 'TwoSideOperation':
|
|
127
|
+
return TwoSideOperation(left=value, right=self, renderer=Modulo)
|
|
128
|
+
|
|
129
|
+
# Logic Operations
|
|
130
|
+
def __and__(self, value: Operand, /) -> 'TwoSideOperation':
|
|
131
|
+
return TwoSideOperation(left=self, right=value, renderer=And, width=greater_operand_width)
|
|
132
|
+
|
|
133
|
+
def __or__(self, value: Operand, /) -> 'TwoSideOperation':
|
|
134
|
+
return TwoSideOperation(left=self, right=value, renderer=Or, width=greater_operand_width)
|
|
135
|
+
|
|
136
|
+
def __xor__(self, value: Operand, /) -> 'TwoSideOperation':
|
|
137
|
+
return TwoSideOperation(left=self, right=value, renderer=ExclusiveOr, width=greater_operand_width)
|
|
138
|
+
|
|
139
|
+
def __lshift__(self, value: Operand, /) -> 'TwoSideOperation':
|
|
140
|
+
return TwoSideOperation(left=self, right=value, renderer=LeftShift)
|
|
141
|
+
|
|
142
|
+
def __rshift__(self, value: Operand, /) -> 'TwoSideOperation':
|
|
143
|
+
return TwoSideOperation(left=self, right=value, renderer=RightShift)
|
|
144
|
+
|
|
145
|
+
# Logic Operations (Right Side)
|
|
146
|
+
def __rand__(self, value: Operand, /) -> 'TwoSideOperation':
|
|
147
|
+
return TwoSideOperation(left=value, right=self, renderer=And, width=greater_operand_width)
|
|
148
|
+
|
|
149
|
+
def __ror__(self, value: Operand, /) -> 'TwoSideOperation':
|
|
150
|
+
return TwoSideOperation(left=value, right=self, renderer=Or, width=greater_operand_width)
|
|
151
|
+
|
|
152
|
+
def __rxor__(self, value: Operand, /) -> 'TwoSideOperation':
|
|
153
|
+
return TwoSideOperation(left=value, right=self, renderer=ExclusiveOr, width=greater_operand_width)
|
|
154
|
+
|
|
155
|
+
def __rlshift__(self, value: Operand, /) -> 'TwoSideOperation':
|
|
156
|
+
return TwoSideOperation(left=value, right=self, renderer=LeftShift)
|
|
157
|
+
|
|
158
|
+
def __rrshift__(self, value: Operand, /) -> 'TwoSideOperation':
|
|
159
|
+
return TwoSideOperation(left=value, right=self, renderer=RightShift)
|
|
160
|
+
|
|
161
|
+
# Misc.
|
|
162
|
+
def __neg__(self) -> 'SingleOperation':
|
|
163
|
+
return SingleOperation(value=self, renderer=Negative)
|
|
164
|
+
|
|
165
|
+
def __pos__(self) -> 'SingleOperation':
|
|
166
|
+
return SingleOperation(value=self, renderer=Positive)
|
|
167
|
+
|
|
168
|
+
# Inversion
|
|
169
|
+
def __invert__(self) -> 'SingleOperation':
|
|
170
|
+
return SingleOperation(value=self, renderer=Inversion)
|
|
171
|
+
|
|
172
|
+
# Comparison
|
|
173
|
+
def __eq__(self, value: Operand, /) -> 'TwoSideOperation': # type: ignore[override]
|
|
174
|
+
return TwoSideOperation(left=self, right=value, renderer=Equality, width=1)
|
|
175
|
+
|
|
176
|
+
def __ne__(self, value: Operand, /) -> 'TwoSideOperation': # type: ignore[override]
|
|
177
|
+
return TwoSideOperation(left=self, right=value, renderer=Unequality, width=1)
|
|
178
|
+
|
|
179
|
+
def __lt__(self, value: Operand, /) -> 'TwoSideOperation':
|
|
180
|
+
return TwoSideOperation(left=self, right=value, renderer=Less, width=1)
|
|
181
|
+
|
|
182
|
+
def __le__(self, value: Operand, /) -> 'TwoSideOperation':
|
|
183
|
+
return TwoSideOperation(left=self, right=value, renderer=LessOrEqual, width=1)
|
|
184
|
+
|
|
185
|
+
def __gt__(self, value: Operand, /) -> 'TwoSideOperation':
|
|
186
|
+
return TwoSideOperation(left=self, right=value, renderer=Greater, width=1)
|
|
187
|
+
|
|
188
|
+
def __ge__(self, value: Operand, /) -> 'TwoSideOperation':
|
|
189
|
+
return TwoSideOperation(left=self, right=value, renderer=GreaterOrEqual, width=1)
|
|
190
|
+
|
|
191
|
+
# Bit Slicing
|
|
192
|
+
def __getitem__(self, index: Union[int, slice]) -> 'OperationTrait':
|
|
193
|
+
return SliceOperation(value=self, index=index, renderer=Slice)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@overload
|
|
197
|
+
def to_renderable(value: Operand, allow_string_literal: Literal[False] = ...) -> Renderable: ...
|
|
198
|
+
@overload
|
|
199
|
+
def to_renderable(value: Union[Operand, str], allow_string_literal: Literal[True] = ...) -> Renderable: ...
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def to_renderable(value: Union[Operand, str], allow_string_literal: bool = False) -> Renderable:
|
|
203
|
+
"""Convert value to renderable object.
|
|
204
|
+
|
|
205
|
+
Arguments:
|
|
206
|
+
value: The value that shall be converted into a Renderable.
|
|
207
|
+
allow_string_literal: If string literals are allowed for constants.
|
|
208
|
+
"""
|
|
209
|
+
if hasattr(value, 'render'):
|
|
210
|
+
return value # type: ignore[return-value]
|
|
211
|
+
elif (isinstance(value, str) and allow_string_literal) or isinstance(value, (int, bool)):
|
|
212
|
+
return Const(value)
|
|
213
|
+
else:
|
|
214
|
+
raise TypeError(f'Unable to convert value {value} into a renderable object.')
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
# Alias for backwards-compatibility
|
|
218
|
+
toRenderable = to_renderable # noqa: N816
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# Literal Values
|
|
222
|
+
class LiteralValue(OperationTrait):
|
|
223
|
+
"""Base class for literal values."""
|
|
224
|
+
|
|
225
|
+
is_primitive: Final = False
|
|
226
|
+
|
|
227
|
+
def __init__(self, value: Union[int, bool, str], width: Optional[int] = None) -> None:
|
|
228
|
+
"""Initialize a literal value wrapper.
|
|
229
|
+
|
|
230
|
+
Arguments:
|
|
231
|
+
value: The value of this literal value.
|
|
232
|
+
width: Optional width in bits.
|
|
233
|
+
"""
|
|
234
|
+
self._width: Optional[int] = width
|
|
235
|
+
|
|
236
|
+
# Parse value and try to parse width
|
|
237
|
+
if isinstance(value, str):
|
|
238
|
+
value, parsed_width = parse_int(value)
|
|
239
|
+
elif isinstance(value, bool):
|
|
240
|
+
value, parsed_width = int(value), 1
|
|
241
|
+
else:
|
|
242
|
+
value, parsed_width = int(value), None
|
|
243
|
+
|
|
244
|
+
# Check required width
|
|
245
|
+
required_width = parsed_width if parsed_width is not None else ceil(log2(max(1, value)))
|
|
246
|
+
if self.width is not None and required_width > self.width:
|
|
247
|
+
raise ValueError(f'Unable to create {self.__class__}: Value {value} exceeds width with {required_width} > {self.width}.')
|
|
248
|
+
|
|
249
|
+
self._value = value
|
|
250
|
+
self._width = width if width is not None else parsed_width
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def value(self) -> int:
|
|
254
|
+
"""Value."""
|
|
255
|
+
return self._value
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def width(self) -> Optional[int]:
|
|
259
|
+
"""Width in bits.
|
|
260
|
+
|
|
261
|
+
A width of None means that the width is not fixed during execution of noRTL.
|
|
262
|
+
"""
|
|
263
|
+
return self._width
|
|
264
|
+
|
|
265
|
+
# Implement OperationTrait
|
|
266
|
+
@property
|
|
267
|
+
def operand_width(self) -> Optional[int]:
|
|
268
|
+
"""Indicates the width when used as an operand.
|
|
269
|
+
|
|
270
|
+
A width of None means that the width is not fixed during execution of noRTL.
|
|
271
|
+
"""
|
|
272
|
+
return self.width
|
|
273
|
+
|
|
274
|
+
def read_access(self, ignore: Set[ACCESS_CHECKS] = set()) -> None:
|
|
275
|
+
"""Register read access from the current thread, state and construct depth.
|
|
276
|
+
|
|
277
|
+
Does not invoke any checks for this object.
|
|
278
|
+
"""
|
|
279
|
+
|
|
280
|
+
def render(self, target: Optional[str] = None) -> str:
|
|
281
|
+
"""Render constant value to target language.
|
|
282
|
+
|
|
283
|
+
Arguments:
|
|
284
|
+
target: Target language.
|
|
285
|
+
"""
|
|
286
|
+
if self.width is not None:
|
|
287
|
+
return f"{self.width}'h{self.value:0{ceil(self.width / 4)}X}"
|
|
288
|
+
else:
|
|
289
|
+
return f'{self.value}'
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class Const(LiteralValue):
|
|
293
|
+
"""Constant value, representing an integer.
|
|
294
|
+
|
|
295
|
+
Constants can optionally be initialized with a fixed width.
|
|
296
|
+
This is relevant, when the constant is used within a [Concat][nortl.core.operations.Concat].
|
|
297
|
+
|
|
298
|
+
Constants can be created from integers, bools, or parsed from strings.
|
|
299
|
+
When parsing the value from a string in binary, octal or hexadecimal representation, the width can be automatically inferred.
|
|
300
|
+
|
|
301
|
+
For hexadecimal numbers, the width will be a multiple of 4. For octal numbers, the width will be a multiple of 3.
|
|
302
|
+
If the width argument is provided, it will override the inferred with.
|
|
303
|
+
|
|
304
|
+
!!! note
|
|
305
|
+
|
|
306
|
+
If you need to create constants with a fixed width, that is not a multiple of whole bytes, it is recommended to use binary representation.
|
|
307
|
+
|
|
308
|
+
Examples:
|
|
309
|
+
`Const('0b001')` will be parsed as value 1, width 3.
|
|
310
|
+
|
|
311
|
+
`Const('0o70')` will be parsed as value 56, width 6.
|
|
312
|
+
|
|
313
|
+
`Const('0x00')` will be parsed as value 0, width 8.
|
|
314
|
+
|
|
315
|
+
`Const('0x00', 6)` will be parsed as value 0, width 6, due to explicit width. Alternatively, `Const(0, 6)` could be used.
|
|
316
|
+
"""
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
class Var(LiteralValue):
|
|
320
|
+
"""Variable value, representing an integer.
|
|
321
|
+
|
|
322
|
+
This class behaves similar to a [Const][nortl.core.operations.Const], but can be updated.
|
|
323
|
+
This makes it useful to resize the width of signals.
|
|
324
|
+
|
|
325
|
+
!!! danger
|
|
326
|
+
|
|
327
|
+
Variables allow lazily determining the final value for a "constant" in the resulting Verilog code.
|
|
328
|
+
Internally, they are used to define the width of the scratch pad signal for the [ScratchManager][nortl.core.manager.scratch_manager.ScratchManager], allowing the scratch manager to increase it over time.
|
|
329
|
+
|
|
330
|
+
Variables can be used in all places, that accept Renderabls, but must be used carefully.
|
|
331
|
+
It is recommended to use a Variable only in a single place.
|
|
332
|
+
"""
|
|
333
|
+
|
|
334
|
+
def update(self, value: Union[int, str, bool]) -> None:
|
|
335
|
+
"""Update variable value."""
|
|
336
|
+
# Parse value and try to parse width
|
|
337
|
+
if isinstance(value, str):
|
|
338
|
+
value, parsed_width = parse_int(value)
|
|
339
|
+
elif isinstance(value, bool):
|
|
340
|
+
value, parsed_width = int(value), 1
|
|
341
|
+
else:
|
|
342
|
+
value, parsed_width = int(value), None
|
|
343
|
+
|
|
344
|
+
# Check required width
|
|
345
|
+
required_width = parsed_width if parsed_width is not None else ceil(log2(max(1, value)))
|
|
346
|
+
if self.width is not None and required_width > self.width:
|
|
347
|
+
raise ValueError(f'Unable to update value: New value {value} exceeds variable width with {required_width} > {self.width}.')
|
|
348
|
+
self._value = value
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
class RawText(OperationTrait):
|
|
352
|
+
"""Wrapper for raw text.
|
|
353
|
+
|
|
354
|
+
!!! danger
|
|
355
|
+
|
|
356
|
+
This class is meant for internal purposes. It will forward the raw text value to any rendering output. It can cause syntax errors or create risky code, if you refer to any signals (bypassing the access checker).
|
|
357
|
+
"""
|
|
358
|
+
|
|
359
|
+
is_primitive: Final = True
|
|
360
|
+
|
|
361
|
+
def __init__(self, value: str) -> None:
|
|
362
|
+
"""Initialize a primitive value wrapper.
|
|
363
|
+
|
|
364
|
+
Arguments:
|
|
365
|
+
value: The value of this primitive value.
|
|
366
|
+
"""
|
|
367
|
+
self.value = value
|
|
368
|
+
|
|
369
|
+
# Implement OperationTrait
|
|
370
|
+
@property
|
|
371
|
+
def operand_width(self) -> Never:
|
|
372
|
+
"""Indicates the width when used as an operand."""
|
|
373
|
+
raise AttributeError('Width attribute of RawText must not be processed as an operand.')
|
|
374
|
+
|
|
375
|
+
def read_access(self, ignore: Set[ACCESS_CHECKS] = set()) -> None:
|
|
376
|
+
"""Register read access from the current thread, state and construct depth.
|
|
377
|
+
|
|
378
|
+
Does not invoke any checks for this object.
|
|
379
|
+
"""
|
|
380
|
+
|
|
381
|
+
def render(self, target: Optional[str] = None) -> str:
|
|
382
|
+
"""Render constant value to target language.
|
|
383
|
+
|
|
384
|
+
Arguments:
|
|
385
|
+
target: Target language.
|
|
386
|
+
"""
|
|
387
|
+
return self.value
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
# Operation Wrappers
|
|
391
|
+
class BaseOperation(OperationTrait):
|
|
392
|
+
"""Base class for operations."""
|
|
393
|
+
|
|
394
|
+
_renderer: RendererProto
|
|
395
|
+
|
|
396
|
+
def __init__(self) -> None:
|
|
397
|
+
super().__init__()
|
|
398
|
+
self._cache: Dict[Optional[str], str] = {}
|
|
399
|
+
|
|
400
|
+
@property
|
|
401
|
+
@abstractmethod
|
|
402
|
+
def operands(self) -> Sequence[Renderable]:
|
|
403
|
+
"""All operands of the operation."""
|
|
404
|
+
|
|
405
|
+
# Implement OperationTrait
|
|
406
|
+
def read_access(self, ignore: Set[ACCESS_CHECKS] = set()) -> None:
|
|
407
|
+
"""Register read access from the current thread, state and construct depth.
|
|
408
|
+
|
|
409
|
+
Recursively registers an read access for all operands.
|
|
410
|
+
"""
|
|
411
|
+
for operand in self.operands:
|
|
412
|
+
operand.read_access(ignore=ignore)
|
|
413
|
+
|
|
414
|
+
self._read = True
|
|
415
|
+
|
|
416
|
+
def render(self, target: Optional[str] = None) -> str:
|
|
417
|
+
"""Render operation to target language.
|
|
418
|
+
|
|
419
|
+
Arguments:
|
|
420
|
+
target: Target language.
|
|
421
|
+
"""
|
|
422
|
+
if target not in self._cache:
|
|
423
|
+
self._cache[target] = self._renderer(target)
|
|
424
|
+
return self._cache[target]
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
class SingleOperation(BaseOperation):
|
|
428
|
+
"""Operation wrapper for single-value operations.
|
|
429
|
+
|
|
430
|
+
This object holds the value (can be an integer, signal name or another operation wrapper).
|
|
431
|
+
It also instantiates a renderer that determines what specific kind of operation this represents.
|
|
432
|
+
"""
|
|
433
|
+
|
|
434
|
+
is_primitive: Final = False
|
|
435
|
+
|
|
436
|
+
def __init__(self, value: Union[Renderable, int, bool], renderer: Type[RendererProto]) -> None:
|
|
437
|
+
"""Initialize a single operation wrapper.
|
|
438
|
+
|
|
439
|
+
Arguments:
|
|
440
|
+
value: The value of this wrapper.
|
|
441
|
+
renderer: Type of renderer to use. The renderer decides how the value is represented.
|
|
442
|
+
"""
|
|
443
|
+
self._value = to_renderable(value)
|
|
444
|
+
super().__init__() # Requires operands to exist
|
|
445
|
+
|
|
446
|
+
self._renderer = renderer(self)
|
|
447
|
+
|
|
448
|
+
@property
|
|
449
|
+
def value(self) -> Renderable:
|
|
450
|
+
"""Value to which the operation is applied."""
|
|
451
|
+
return self._value
|
|
452
|
+
|
|
453
|
+
@property
|
|
454
|
+
def operands(self) -> Sequence[Renderable]:
|
|
455
|
+
"""All operands of the operation."""
|
|
456
|
+
return (self.value,)
|
|
457
|
+
|
|
458
|
+
# Implement OperationTrait
|
|
459
|
+
@property
|
|
460
|
+
def operand_width(self) -> Optional[int]:
|
|
461
|
+
"""Indicates the width when used as an operand, equal to the width of the input value.
|
|
462
|
+
|
|
463
|
+
A width of None means that the width is not fixed during execution of noRTL.
|
|
464
|
+
"""
|
|
465
|
+
return self.value.operand_width
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
class TwoSideOperation(BaseOperation):
|
|
469
|
+
"""Operation wrapper for operations based on two sides or values.
|
|
470
|
+
|
|
471
|
+
This object holds the two values (can be integers, signal names or operation wrappers).
|
|
472
|
+
It also instantiates a renderer that determines what specific kind of operation this represents.
|
|
473
|
+
"""
|
|
474
|
+
|
|
475
|
+
is_primitive: Final = False
|
|
476
|
+
|
|
477
|
+
def __init__(
|
|
478
|
+
self,
|
|
479
|
+
left: Union[Renderable, int, bool],
|
|
480
|
+
right: Union[Renderable, int, bool],
|
|
481
|
+
renderer: Type[RendererProto],
|
|
482
|
+
width: Optional[Union[int, Callable[[Renderable, Renderable], Optional[int]]]] = None,
|
|
483
|
+
) -> None:
|
|
484
|
+
"""Initialize a two-side operation wrapper.
|
|
485
|
+
|
|
486
|
+
Arguments:
|
|
487
|
+
left: The first of the two values.
|
|
488
|
+
right: The second of the two values.
|
|
489
|
+
renderer: Type of renderer to use. The renderer decides how the value is represented.
|
|
490
|
+
width: Width of the operation result.
|
|
491
|
+
Can be an integer, None, or a function that determines it from 2 Renderables.
|
|
492
|
+
"""
|
|
493
|
+
self._left = to_renderable(left)
|
|
494
|
+
self._right = to_renderable(right)
|
|
495
|
+
super().__init__() # Requires operands to exist
|
|
496
|
+
|
|
497
|
+
self._renderer = renderer(self)
|
|
498
|
+
|
|
499
|
+
# Determine width
|
|
500
|
+
if isinstance(width, int) or width is None:
|
|
501
|
+
self._operand_width = width
|
|
502
|
+
else:
|
|
503
|
+
self._operand_width = width(self.left, self.right)
|
|
504
|
+
|
|
505
|
+
@property
|
|
506
|
+
def left(self) -> Renderable:
|
|
507
|
+
"""Left or first value for the operation."""
|
|
508
|
+
return self._left
|
|
509
|
+
|
|
510
|
+
@property
|
|
511
|
+
def right(self) -> Renderable:
|
|
512
|
+
"""Right or second value for the operation."""
|
|
513
|
+
return self._right
|
|
514
|
+
|
|
515
|
+
@property
|
|
516
|
+
def operands(self) -> Sequence[Renderable]:
|
|
517
|
+
"""All operands of the operation."""
|
|
518
|
+
return (self.left, self.right)
|
|
519
|
+
|
|
520
|
+
# Implement OperationTrait
|
|
521
|
+
@property
|
|
522
|
+
def operand_width(self) -> Optional[int]:
|
|
523
|
+
"""Indicates the width when used as an operand.
|
|
524
|
+
|
|
525
|
+
A width of None means that the width is not fixed during execution of noRTL.
|
|
526
|
+
"""
|
|
527
|
+
return self._operand_width
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
class SliceOperation(SingleOperation):
|
|
531
|
+
"""Operation wrapper for slicing operations.
|
|
532
|
+
|
|
533
|
+
This object holds an values (can be an integer, signal name or operation wrapper) and an index.
|
|
534
|
+
"""
|
|
535
|
+
|
|
536
|
+
def __init__(self, value: Union[Renderable, int, bool], index: Union[int, slice], renderer: Type[RendererProto] = Slice) -> None:
|
|
537
|
+
"""Initialize a slicing operation wrapper.
|
|
538
|
+
|
|
539
|
+
Arguments:
|
|
540
|
+
value: The value of this wrapper.
|
|
541
|
+
index: The slicing index. Can be a single integer or slice. Note that the stop value is inclusive, as it is in Verilog.
|
|
542
|
+
This differs from typical behavior in Python. The step size must be 1.
|
|
543
|
+
renderer: Type of renderer to use. The renderer decides how the value is represented.
|
|
544
|
+
"""
|
|
545
|
+
super().__init__(value, renderer)
|
|
546
|
+
|
|
547
|
+
if isinstance(index, slice):
|
|
548
|
+
if index.start is None:
|
|
549
|
+
raise ValueError('Slice start must be defined.')
|
|
550
|
+
elif index.stop is None:
|
|
551
|
+
raise ValueError('Slice stop must be defined.')
|
|
552
|
+
elif index.step is not None and index.step != 1:
|
|
553
|
+
raise ValueError('Slice step size must be 1.')
|
|
554
|
+
self._operand_width: int = max(index.start, index.stop) - min(index.start, index.stop) + 1
|
|
555
|
+
else:
|
|
556
|
+
self._operand_width = 1
|
|
557
|
+
|
|
558
|
+
self._index = index
|
|
559
|
+
|
|
560
|
+
@property
|
|
561
|
+
def index(self) -> Union[int, slice]:
|
|
562
|
+
"""Slicing index."""
|
|
563
|
+
return self._index
|
|
564
|
+
|
|
565
|
+
# Implement OperationTrait
|
|
566
|
+
@property
|
|
567
|
+
def operand_width(self) -> Optional[int]:
|
|
568
|
+
"""Indicates the width when used as an operand.
|
|
569
|
+
|
|
570
|
+
A width of None means that the width is not fixed during execution of noRTL.
|
|
571
|
+
"""
|
|
572
|
+
return self._operand_width
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
# Explicit Operations
|
|
576
|
+
class Concat(BaseOperation):
|
|
577
|
+
"""Concatenation expression.
|
|
578
|
+
|
|
579
|
+
This class is used to model a concatenation of signals (or constants) in Verilog, e.g. `data = {arg2, arg1, arg0};`.
|
|
580
|
+
|
|
581
|
+
The order of arguments passed into the Concat is translated as-is to Verilog.
|
|
582
|
+
|
|
583
|
+
The concatenation supports constant values as strings, to simplify defining the width of constants.
|
|
584
|
+
Instead of needing to write `Concat(Const('0b000'), ...)` you can use `Concat('0b000', ...)`.
|
|
585
|
+
|
|
586
|
+
!!! example
|
|
587
|
+
|
|
588
|
+
The following example:
|
|
589
|
+
|
|
590
|
+
```python
|
|
591
|
+
response = engine.define_output('response', width=8)
|
|
592
|
+
status = engine.define_local('status', width=3)
|
|
593
|
+
|
|
594
|
+
# ...
|
|
595
|
+
engine.set(response, Concat('0b0000', status, '0b0'))
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
Will be translated as such:
|
|
599
|
+
|
|
600
|
+
```verilog
|
|
601
|
+
module my_engine (
|
|
602
|
+
// ...
|
|
603
|
+
output logic [7:0] response
|
|
604
|
+
);
|
|
605
|
+
logic [2:0] status;
|
|
606
|
+
|
|
607
|
+
// ... somewhere in the state machine
|
|
608
|
+
response <= {4'b0000, status, 1'b0};
|
|
609
|
+
```
|
|
610
|
+
"""
|
|
611
|
+
|
|
612
|
+
is_primitive: Final = False
|
|
613
|
+
|
|
614
|
+
def __init__(self, *args: Union[Renderable, str]) -> None:
|
|
615
|
+
"""Initialize a concatenation expression.
|
|
616
|
+
|
|
617
|
+
Arguments:
|
|
618
|
+
args: One or more renderable items. All arguments must have a fixed width. Supports constant values as string literals.
|
|
619
|
+
"""
|
|
620
|
+
|
|
621
|
+
self._parts = tuple(to_renderable(arg, allow_string_literal=True) for arg in args)
|
|
622
|
+
super().__init__() # Requires operands to exist
|
|
623
|
+
|
|
624
|
+
# Calculate and validate width
|
|
625
|
+
self._operand_width: int = 0
|
|
626
|
+
for part in self.parts:
|
|
627
|
+
if part.operand_width is None:
|
|
628
|
+
raise ValueError(f'Argument {part} for concatenation does not have a fixed width. This can lead to unpredictable results.')
|
|
629
|
+
self._operand_width += part.operand_width
|
|
630
|
+
|
|
631
|
+
@property
|
|
632
|
+
def parts(self) -> Sequence[Renderable]:
|
|
633
|
+
"""Parts of the concatenation expression."""
|
|
634
|
+
return self._parts
|
|
635
|
+
|
|
636
|
+
@property
|
|
637
|
+
def operands(self) -> Sequence[Renderable]:
|
|
638
|
+
"""All operands of the operation."""
|
|
639
|
+
return self.parts
|
|
640
|
+
|
|
641
|
+
# Implement OperationTrait
|
|
642
|
+
@property
|
|
643
|
+
def operand_width(self) -> int:
|
|
644
|
+
"""Indicates the width when used as an operand."""
|
|
645
|
+
return self._operand_width
|
|
646
|
+
|
|
647
|
+
# Implement OperationTrait
|
|
648
|
+
def render(self, target: Optional[str] = None) -> str:
|
|
649
|
+
"""Render constant value to target language.
|
|
650
|
+
|
|
651
|
+
Arguments:
|
|
652
|
+
target: Target language.
|
|
653
|
+
"""
|
|
654
|
+
if target not in self._cache:
|
|
655
|
+
rendered_parts = [p.render(target) for p in self.parts]
|
|
656
|
+
self._cache[target] = f'{{{", ".join(rendered_parts)}}}'
|
|
657
|
+
return self._cache[target]
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
class IfThenElse(BaseOperation):
|
|
661
|
+
"""Ternary If-Then-Else expression.
|
|
662
|
+
|
|
663
|
+
This class is used to model the `condition ? true_value : false_value` operator in Verilog.
|
|
664
|
+
"""
|
|
665
|
+
|
|
666
|
+
is_primitive: Final = False
|
|
667
|
+
|
|
668
|
+
def __init__(self, cond: Renderable, true_value: Operand, false_value: Operand) -> None:
|
|
669
|
+
"""Initialize a new If-Then-Else expression.
|
|
670
|
+
|
|
671
|
+
Arguments:
|
|
672
|
+
cond: Condition, selecting between `true_value` and `false_value`
|
|
673
|
+
true_value: Operand that is selected if `cond` is True.
|
|
674
|
+
false_value: Operand that is selected if `cond` is False.
|
|
675
|
+
"""
|
|
676
|
+
self._condition = to_renderable(cond)
|
|
677
|
+
self._true_value = to_renderable(true_value)
|
|
678
|
+
self._false_value = to_renderable(false_value)
|
|
679
|
+
super().__init__() # Requires operands to exist
|
|
680
|
+
|
|
681
|
+
# Validate widths and determine result width
|
|
682
|
+
if (cond_width := self._condition.operand_width) != 1:
|
|
683
|
+
raise ValueError(f'Condition for ternary expression has a width of {cond_width}. It should have a width of 1.')
|
|
684
|
+
|
|
685
|
+
# Use larger of operand widths
|
|
686
|
+
operand_width = -1
|
|
687
|
+
if (true_width := self.true_value.operand_width) is not None:
|
|
688
|
+
operand_width = max(operand_width, true_width)
|
|
689
|
+
if (false_width := self.false_value.operand_width) is not None:
|
|
690
|
+
operand_width = max(operand_width, false_width)
|
|
691
|
+
self._operand_width = operand_width if operand_width > 0 else None
|
|
692
|
+
|
|
693
|
+
@property
|
|
694
|
+
def condition(self) -> Renderable:
|
|
695
|
+
"""Condition, selecting between `true_value` and `fals_value`."""
|
|
696
|
+
return self._condition
|
|
697
|
+
|
|
698
|
+
@property
|
|
699
|
+
def true_value(self) -> Renderable:
|
|
700
|
+
"""Operand that is selected if `cond` is True."""
|
|
701
|
+
return self._true_value
|
|
702
|
+
|
|
703
|
+
@property
|
|
704
|
+
def false_value(self) -> Renderable:
|
|
705
|
+
"""Operand that is selected if `cond` is False."""
|
|
706
|
+
return self._false_value
|
|
707
|
+
|
|
708
|
+
@property
|
|
709
|
+
def operands(self) -> Sequence[Renderable]:
|
|
710
|
+
"""All operands of the operation."""
|
|
711
|
+
return (self.condition, self.true_value, self.false_value)
|
|
712
|
+
|
|
713
|
+
# Implement OperationTrait
|
|
714
|
+
@property
|
|
715
|
+
def operand_width(self) -> Optional[int]:
|
|
716
|
+
"""Indicates the width when used as an operand.
|
|
717
|
+
|
|
718
|
+
A width of None means that the width is not fixed during execution of noRTL.
|
|
719
|
+
"""
|
|
720
|
+
return self._operand_width
|
|
721
|
+
|
|
722
|
+
def render(self, target: Optional[str] = None) -> str:
|
|
723
|
+
"""Render constant value to target language.
|
|
724
|
+
|
|
725
|
+
Arguments:
|
|
726
|
+
target: Target language.
|
|
727
|
+
"""
|
|
728
|
+
if target not in self._cache:
|
|
729
|
+
self._cache[target] = f'{self.condition} ? {self.true_value} : {self.false_value}'
|
|
730
|
+
return self._cache[target]
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
class LogicalOperation(BaseOperation):
|
|
734
|
+
is_primitive: Final = False
|
|
735
|
+
|
|
736
|
+
@overload
|
|
737
|
+
def __init__(self, arg: Generator[Renderable, None, None], /) -> None: ...
|
|
738
|
+
|
|
739
|
+
@overload
|
|
740
|
+
def __init__(self, arg: Renderable, /, *extra_args: Renderable) -> None: ...
|
|
741
|
+
|
|
742
|
+
def __init__(self, arg: Union[Renderable, Generator[Renderable, None, None]], /, *extra_args: Renderable) -> None:
|
|
743
|
+
"""Initialize a logic operation.
|
|
744
|
+
|
|
745
|
+
Arguments:
|
|
746
|
+
arg: First renderable item or generator expression.
|
|
747
|
+
extra_args: More more renderable items. All arguments must have a fixed width. Supports constant values as string literals.
|
|
748
|
+
"""
|
|
749
|
+
if isinstance(arg, GeneratorType):
|
|
750
|
+
args: Iterable[Renderable] = arg
|
|
751
|
+
if len(extra_args) > 0:
|
|
752
|
+
raise ValueError('Logical operation can either be created from a generator expression or multiple Renderables.')
|
|
753
|
+
else:
|
|
754
|
+
args = (arg, *extra_args) # type: ignore[arg-type]
|
|
755
|
+
|
|
756
|
+
self._operands = tuple(to_renderable(arg, allow_string_literal=True) for arg in args)
|
|
757
|
+
|
|
758
|
+
super().__init__() # Requires operands to exist
|
|
759
|
+
|
|
760
|
+
# Calculate and validate width
|
|
761
|
+
for part in self.operands:
|
|
762
|
+
if part.operand_width != 1:
|
|
763
|
+
raise ValueError(f'Argument {part} for logical operation does not have a fixed width of 1. This is not allowed.')
|
|
764
|
+
|
|
765
|
+
@property
|
|
766
|
+
def operands(self) -> Sequence[Renderable]:
|
|
767
|
+
"""All operands of the operation."""
|
|
768
|
+
return self._operands
|
|
769
|
+
|
|
770
|
+
# Implement OperationTrait
|
|
771
|
+
@property
|
|
772
|
+
def operand_width(self) -> Literal[1]:
|
|
773
|
+
"""Indicates the width when used as an operand."""
|
|
774
|
+
return 1
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
class Any(LogicalOperation):
|
|
778
|
+
"""Any operation. Will be logic high, if any of the arguments is logic high.
|
|
779
|
+
|
|
780
|
+
It will be rendered as a "Logical Or" (`||`) in Verilog.
|
|
781
|
+
All arguments in the operation must have a fixed with of 1.
|
|
782
|
+
|
|
783
|
+
The operation can be created from one or more Renderable arguments.
|
|
784
|
+
It's also possible to directly pass in a generator expression.
|
|
785
|
+
|
|
786
|
+
Examples:
|
|
787
|
+
```python
|
|
788
|
+
Any(a, b, c)
|
|
789
|
+
|
|
790
|
+
Any(signal > 0 for signal in signals)
|
|
791
|
+
```
|
|
792
|
+
"""
|
|
793
|
+
|
|
794
|
+
# Implement OperationTrait
|
|
795
|
+
def render(self, target: Optional[str] = None) -> str:
|
|
796
|
+
"""Render constant value to target language.
|
|
797
|
+
|
|
798
|
+
Arguments:
|
|
799
|
+
target: Target language.
|
|
800
|
+
"""
|
|
801
|
+
if target not in self._cache:
|
|
802
|
+
rendered_parts = [p.render(target) for p in self.operands]
|
|
803
|
+
self._cache[target] = f'({" || ".join(rendered_parts)})'
|
|
804
|
+
return self._cache[target]
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
class All(LogicalOperation):
|
|
808
|
+
"""All operation. Will be logic high, if all of the arguments is logic high.
|
|
809
|
+
|
|
810
|
+
It will be rendered as a "Logical And" (`&&`) in Verilog.
|
|
811
|
+
All arguments in the operation must have a fixed with of 1.
|
|
812
|
+
|
|
813
|
+
The operation can be created from one or more Renderable arguments.
|
|
814
|
+
It's also possible to directly pass in a generator expression.
|
|
815
|
+
|
|
816
|
+
Examples:
|
|
817
|
+
```python
|
|
818
|
+
All(a, b, c)
|
|
819
|
+
|
|
820
|
+
All(signal > 0 for signal in signals)
|
|
821
|
+
```
|
|
822
|
+
"""
|
|
823
|
+
|
|
824
|
+
# Implement OperationTrait
|
|
825
|
+
def render(self, target: Optional[str] = None) -> str:
|
|
826
|
+
"""Render constant value to target language.
|
|
827
|
+
|
|
828
|
+
Arguments:
|
|
829
|
+
target: Target language.
|
|
830
|
+
"""
|
|
831
|
+
if target not in self._cache:
|
|
832
|
+
rendered_parts = [p.render(target) for p in self.operands]
|
|
833
|
+
self._cache[target] = f'({" && ".join(rendered_parts)})'
|
|
834
|
+
return self._cache[target]
|