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/engine.py
ADDED
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
"""Core Engine."""
|
|
2
|
+
|
|
3
|
+
from typing import ClassVar, Dict, Mapping, Optional, Sequence, Set, Tuple, Union
|
|
4
|
+
|
|
5
|
+
from nortl.core.exceptions import OwnershipError, TransitionLockError, read_access, write_access
|
|
6
|
+
from nortl.core.manager import ScratchManager, SignalManager
|
|
7
|
+
from nortl.core.module import Module, ModuleInstance
|
|
8
|
+
from nortl.core.operations import Const, to_renderable
|
|
9
|
+
from nortl.core.parameter import Parameter
|
|
10
|
+
from nortl.core.process import Thread, Worker
|
|
11
|
+
from nortl.core.protocols import AssignmentTarget, ModuleProto, PermanentSignal, Renderable, StateProto, WorkerProto
|
|
12
|
+
from nortl.core.signal import ScratchSignal, Signal, SignalSlice
|
|
13
|
+
from nortl.core.state import State
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
'CoreEngine',
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CoreEngine:
|
|
21
|
+
"""This class represents a Verilog module that realizes a noRTL engine.
|
|
22
|
+
|
|
23
|
+
The construction idea of this class is, that a noRTL engine can be described using the following lists:
|
|
24
|
+
|
|
25
|
+
* A set of ports, signals, parameters and submodules
|
|
26
|
+
* A set of states
|
|
27
|
+
* Each state has
|
|
28
|
+
* a set of assignments of the form "<signal> = <statement>"
|
|
29
|
+
* a set of conditions that describe the transition next states
|
|
30
|
+
|
|
31
|
+
Since we want to also enable parallel running engines, it is necessary to organize the states in a dictionary.
|
|
32
|
+
The dictionaries keys represent the thread and the state lists represent the states of the realized engine.
|
|
33
|
+
By default, we start with a main thread.
|
|
34
|
+
|
|
35
|
+
This class only realizes the features needed to create state machines. Events on signals have to be handled on the next level of abstraction.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
MAIN_WORKER_NAME: ClassVar[str] = 'main'
|
|
39
|
+
MAIN_THREAD_NAME: ClassVar[str] = 'main'
|
|
40
|
+
|
|
41
|
+
def __init__(self, module_name: str, reset_state_name: str = 'IDLE'):
|
|
42
|
+
"""Initialize a engine.
|
|
43
|
+
|
|
44
|
+
Arguments:
|
|
45
|
+
module_name: Verilog module name.
|
|
46
|
+
reset_state_name: Name of the reset state.
|
|
47
|
+
"""
|
|
48
|
+
self.module_name = module_name
|
|
49
|
+
self._reset_state_name = reset_state_name
|
|
50
|
+
|
|
51
|
+
# Parameters, signals and scratch signals
|
|
52
|
+
self._parameters: Dict[str, Parameter] = dict()
|
|
53
|
+
self._signal_manager = SignalManager(self)
|
|
54
|
+
self._scratch_manager = ScratchManager(self)
|
|
55
|
+
self._modules: Dict[str, Module] = {}
|
|
56
|
+
self._module_instances: Dict[str, ModuleInstance] = {}
|
|
57
|
+
|
|
58
|
+
# Workers
|
|
59
|
+
self._workers: Dict[str, Worker] = {}
|
|
60
|
+
|
|
61
|
+
# Create main worker
|
|
62
|
+
self._main_worker = self.create_worker(self.MAIN_WORKER_NAME)
|
|
63
|
+
self._current_worker = self.main_worker
|
|
64
|
+
|
|
65
|
+
# Create main thread
|
|
66
|
+
self._main_thread = self.main_worker.create_thread(self.MAIN_THREAD_NAME)
|
|
67
|
+
|
|
68
|
+
# State management
|
|
69
|
+
@property
|
|
70
|
+
def states(self) -> Mapping[str, Sequence[State]]:
|
|
71
|
+
"""All states of the engine.
|
|
72
|
+
|
|
73
|
+
Contains a list of the states for each worker. They are stored in a dictionary and indexed with the worker name.
|
|
74
|
+
"""
|
|
75
|
+
return {name: worker.states for name, worker in self.workers.items()}
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def state_names(self) -> Set[str]:
|
|
79
|
+
"""Set of the names of all states for this engine."""
|
|
80
|
+
return set().union(*(worker.state_names for worker in self.workers.values()))
|
|
81
|
+
|
|
82
|
+
def create_state(self, name: Optional[str] = None, allow_assignments: bool = True) -> State:
|
|
83
|
+
"""Create a new state for the current worker.
|
|
84
|
+
|
|
85
|
+
Arguments:
|
|
86
|
+
name: Optional state name. If no name is provided, it defaults to '<current_worker>_STATE_x', where 'x' is the current number of states
|
|
87
|
+
for the worker.
|
|
88
|
+
|
|
89
|
+
If the current worker is not the main worker, the name of the state must be prefixed with the name of the current worker.
|
|
90
|
+
The prefix is automatically added, if missing.
|
|
91
|
+
allow_assignments: If the state allows assignments. This is used for internal purposes.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
The created state.
|
|
95
|
+
"""
|
|
96
|
+
return self.current_worker.create_state(name=name, allow_assignments=allow_assignments)
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def current_state(self) -> State:
|
|
100
|
+
"""Current state.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
The current state.
|
|
104
|
+
"""
|
|
105
|
+
return self.current_worker.current_state
|
|
106
|
+
|
|
107
|
+
@current_state.setter
|
|
108
|
+
def current_state(self, state: StateProto) -> None:
|
|
109
|
+
"""Current state.
|
|
110
|
+
|
|
111
|
+
Arguments:
|
|
112
|
+
state: The new current state.
|
|
113
|
+
|
|
114
|
+
Raises:
|
|
115
|
+
UnfinishedForwardDeclarationError: If the next state has been forward-declared and is different from the new current state.
|
|
116
|
+
"""
|
|
117
|
+
self.current_worker.current_state = state
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def next_state(self) -> State:
|
|
121
|
+
"""Forward-declared next state.
|
|
122
|
+
|
|
123
|
+
This simplifies the creation of new states for non-branching sections of the state graph (e.g. via sync() or wait_for()).
|
|
124
|
+
When you use next_state you must set it as current_state, before you can set any other state.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
The forward-declared next state.
|
|
128
|
+
"""
|
|
129
|
+
return self.current_worker.next_state
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def reset_state(self) -> State:
|
|
133
|
+
"""The reset state of the current worker.
|
|
134
|
+
|
|
135
|
+
This is the initial state from which the current worker will start.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
The reset state.
|
|
139
|
+
"""
|
|
140
|
+
return self.current_worker.reset_state
|
|
141
|
+
|
|
142
|
+
# Worker management
|
|
143
|
+
@property
|
|
144
|
+
def workers(self) -> Mapping[str, Worker]:
|
|
145
|
+
"""Mapping of workers."""
|
|
146
|
+
return self._workers
|
|
147
|
+
|
|
148
|
+
def create_worker(self, name: Optional[str] = None) -> Worker:
|
|
149
|
+
"""Create a new worker.
|
|
150
|
+
|
|
151
|
+
Arguments:
|
|
152
|
+
name: Optional name for the worker. If no name is provided, it defaults to 'WORKER_x', where 'x' is the current number of workers.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
The created worker.
|
|
156
|
+
"""
|
|
157
|
+
if name is None:
|
|
158
|
+
name = f'WORKER_{len(self.workers.values())}'
|
|
159
|
+
if name in self.workers:
|
|
160
|
+
raise KeyError(f'Worker {name} already exists.')
|
|
161
|
+
|
|
162
|
+
worker = Worker(self, name, reset_state_name=self._reset_state_name)
|
|
163
|
+
self._workers[name] = worker
|
|
164
|
+
return worker
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def current_worker(self) -> Worker:
|
|
168
|
+
"""Current worker."""
|
|
169
|
+
return self._current_worker
|
|
170
|
+
|
|
171
|
+
@current_worker.setter
|
|
172
|
+
def current_worker(self, worker: WorkerProto) -> None:
|
|
173
|
+
"""Name of the current worker."""
|
|
174
|
+
if worker.engine is not self:
|
|
175
|
+
raise OwnershipError('Worker does not belong to this engine')
|
|
176
|
+
self.current_worker.leave_foreground()
|
|
177
|
+
self._current_worker = worker # type: ignore[assignment]
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def main_worker(self) -> Worker:
|
|
181
|
+
"""Main worker."""
|
|
182
|
+
return self._main_worker
|
|
183
|
+
|
|
184
|
+
# Thread management
|
|
185
|
+
@property
|
|
186
|
+
def current_thread(self) -> Thread:
|
|
187
|
+
"""Current worker thread."""
|
|
188
|
+
return self.current_worker.current_thread
|
|
189
|
+
|
|
190
|
+
@property
|
|
191
|
+
def main_thread(self) -> Thread:
|
|
192
|
+
"""Main thread."""
|
|
193
|
+
return self._main_thread
|
|
194
|
+
|
|
195
|
+
# Signal management
|
|
196
|
+
@property
|
|
197
|
+
def signal_manager(self) -> SignalManager:
|
|
198
|
+
"""Signal manager."""
|
|
199
|
+
return self._signal_manager
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def scratch_manager(self) -> ScratchManager:
|
|
203
|
+
"""Scratch manager."""
|
|
204
|
+
return self._scratch_manager
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def signals(self) -> Mapping[str, Signal]:
|
|
208
|
+
"""Mapping of signals."""
|
|
209
|
+
return self.signal_manager.signals
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
def combinationals(self) -> Sequence[Tuple[Signal, Renderable]]:
|
|
213
|
+
"""Sequence of combinational signal assignments."""
|
|
214
|
+
return self.signal_manager.combinationals
|
|
215
|
+
|
|
216
|
+
def define_input(
|
|
217
|
+
self, name: str, width: Union[int, Parameter, Renderable] = 1, data_type: str = 'logic', is_synchronized: bool = False
|
|
218
|
+
) -> Signal:
|
|
219
|
+
"""Define an input signal.
|
|
220
|
+
|
|
221
|
+
Arguments:
|
|
222
|
+
name: Name of the signal.
|
|
223
|
+
width: Width of the signal in bits.
|
|
224
|
+
data_type: Data type of the signal.
|
|
225
|
+
is_synchronized: If the signal is synchronous to the used clock domain
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
The defined input signal.
|
|
229
|
+
"""
|
|
230
|
+
return self.signal_manager.create_signal('input', name, width=width, data_type=data_type, is_synchronized=is_synchronized)
|
|
231
|
+
|
|
232
|
+
def define_output(
|
|
233
|
+
self,
|
|
234
|
+
name: str,
|
|
235
|
+
width: Union[int, Parameter, Renderable] = 1,
|
|
236
|
+
reset_value: int = 0,
|
|
237
|
+
data_type: str = 'logic',
|
|
238
|
+
value: Union[Renderable, None] = None,
|
|
239
|
+
) -> Signal:
|
|
240
|
+
"""Define an output signal.
|
|
241
|
+
|
|
242
|
+
The noRTL engine implements two variants of output ports: Registers and continuous / combinational assigns.
|
|
243
|
+
Regsiters can be set in different states and combinational assigns are assigned statically.
|
|
244
|
+
|
|
245
|
+
Arguments:
|
|
246
|
+
name: Name of the signal.
|
|
247
|
+
width: Width of the signal in bits.
|
|
248
|
+
reset_value: Reset value of the signal (only for registers).
|
|
249
|
+
data_type: Data type of the signal.
|
|
250
|
+
value: Value that should be assigned continuously (only for combinational assigns).
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
The defined output signal.
|
|
254
|
+
"""
|
|
255
|
+
if value is None:
|
|
256
|
+
# Register
|
|
257
|
+
signal = self.signal_manager.create_signal('output', name, width=width, data_type=data_type, is_synchronized=True)
|
|
258
|
+
self.main_worker.reset_state.add_assignment(signal, to_renderable(reset_value))
|
|
259
|
+
else:
|
|
260
|
+
# Combinational assign
|
|
261
|
+
signal = self.signal_manager.create_signal(
|
|
262
|
+
'output', name, width=width, data_type=data_type, is_synchronized=True, assignment=to_renderable(value)
|
|
263
|
+
)
|
|
264
|
+
return signal
|
|
265
|
+
|
|
266
|
+
def define_local(
|
|
267
|
+
self,
|
|
268
|
+
name: str,
|
|
269
|
+
width: Union[int, Parameter, Renderable] = 1,
|
|
270
|
+
reset_value: int | None = None,
|
|
271
|
+
data_type: str = 'logic',
|
|
272
|
+
pulsing: bool = False,
|
|
273
|
+
value: Union[Renderable, None] = None,
|
|
274
|
+
) -> Signal:
|
|
275
|
+
"""Define a local signal.
|
|
276
|
+
|
|
277
|
+
Arguments:
|
|
278
|
+
name: Name of the signal.
|
|
279
|
+
width: Width of the signal in bits.
|
|
280
|
+
reset_value (int | None): Reset value (if applicable)
|
|
281
|
+
data_type: Data type of the signal.
|
|
282
|
+
pulsing: If true, the signal automatically resets to zero if not set in the current state
|
|
283
|
+
value: value that should be assigned continuously (only for combinational assigns)
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
The defined local signal.
|
|
287
|
+
"""
|
|
288
|
+
if value is None:
|
|
289
|
+
signal = self.signal_manager.create_signal('local', name, width=width, data_type=data_type, is_synchronized=True, pulsing=pulsing)
|
|
290
|
+
if reset_value is not None:
|
|
291
|
+
self.main_worker.reset_state.add_assignment(signal, to_renderable(reset_value))
|
|
292
|
+
else:
|
|
293
|
+
# Combinational assign
|
|
294
|
+
signal = self.signal_manager.create_signal(
|
|
295
|
+
'local', name, width=width, data_type=data_type, is_synchronized=True, assignment=to_renderable(value)
|
|
296
|
+
)
|
|
297
|
+
return signal
|
|
298
|
+
|
|
299
|
+
def define_scratch(self, width: int) -> ScratchSignal:
|
|
300
|
+
"""Define a scratch signal.
|
|
301
|
+
|
|
302
|
+
Arguments:
|
|
303
|
+
width: Width of the signal in bits.
|
|
304
|
+
"""
|
|
305
|
+
return self.scratch_manager.create(width)
|
|
306
|
+
|
|
307
|
+
# Parameter Managment
|
|
308
|
+
@property
|
|
309
|
+
def parameters(self) -> Mapping[str, Parameter]:
|
|
310
|
+
"""Mapping of parameters."""
|
|
311
|
+
return self._parameters
|
|
312
|
+
|
|
313
|
+
def define_parameter(self, name: str, default_value: int = 0, width: Optional[int] = None) -> Parameter:
|
|
314
|
+
"""Defines a parameter for the engine that can be passed on to module instances.
|
|
315
|
+
|
|
316
|
+
Note that the only supported datatype for the parameter is int!
|
|
317
|
+
|
|
318
|
+
Arguments:
|
|
319
|
+
name: Name of the parameter
|
|
320
|
+
default_value: Value of the parameter. Defaults to 0.
|
|
321
|
+
width: Optional width for the parameter.
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
Parameter: Parameter object
|
|
325
|
+
"""
|
|
326
|
+
if name in self.signals:
|
|
327
|
+
raise KeyError(f'Parameter name {name} collides with existing signal name.')
|
|
328
|
+
if name in self.parameters:
|
|
329
|
+
raise KeyError(f'Parameter {name} already exists.')
|
|
330
|
+
|
|
331
|
+
parameter = Parameter(self, name, default_value, width=width)
|
|
332
|
+
self._parameters[name] = parameter
|
|
333
|
+
return parameter
|
|
334
|
+
|
|
335
|
+
# Setting outputs
|
|
336
|
+
def set(self, signal: AssignmentTarget, level: Union[Renderable, int, bool]) -> None:
|
|
337
|
+
"""Set level of an output signal.
|
|
338
|
+
|
|
339
|
+
This is non-blocking, you can set multiple signals at the same time.
|
|
340
|
+
Use sync(), wait_for() or jump_if() to apply all signals and move to the next state.
|
|
341
|
+
|
|
342
|
+
Arguments:
|
|
343
|
+
signal: The signal to be set.
|
|
344
|
+
level: The level to which the signal is set.
|
|
345
|
+
|
|
346
|
+
Raises:
|
|
347
|
+
TypeError: If the signal is not a noRTL signal.
|
|
348
|
+
OwnershipError: If the signal does not belong to this noRTL engine.
|
|
349
|
+
"""
|
|
350
|
+
if not hasattr(signal, 'write_access'):
|
|
351
|
+
raise TypeError(f'{signal} is not a valid assignment target.')
|
|
352
|
+
if signal.name not in self.signals or signal.engine is not self:
|
|
353
|
+
raise OwnershipError(f"Signal '{signal.name}' does not belong to this engine.")
|
|
354
|
+
|
|
355
|
+
self.current_state.add_assignment(write_access(signal), read_access(to_renderable(level)))
|
|
356
|
+
|
|
357
|
+
def set_once(self, signal: AssignmentTarget, level: Union[Renderable, int, bool], reset_level: Union[Renderable, int, bool] = False) -> None:
|
|
358
|
+
"""Set level of an output signal for current state, reset it in next state.
|
|
359
|
+
|
|
360
|
+
This is non-blocking, you can set multiple signals at the same time.
|
|
361
|
+
Use sync(), wait_for() or jump_if() to apply all signals and move to the next state.
|
|
362
|
+
|
|
363
|
+
Using this method will declare the next state. The current state will be restricted to a single transition to the next state.
|
|
364
|
+
You cannot create multiple transitions to other states.
|
|
365
|
+
If you need to do this, use the regular set() method to set signals in this state, and reset them in others.
|
|
366
|
+
|
|
367
|
+
Arguments:
|
|
368
|
+
signal: The signal to be set.
|
|
369
|
+
level: The level to which the signal is set.
|
|
370
|
+
reset_level: The level to which the signal is reset in the next state.
|
|
371
|
+
|
|
372
|
+
Raises:
|
|
373
|
+
TypeError: If the signal is not a noRTL signal.
|
|
374
|
+
OwnershipError: If the signal does not belong to this noRTL engine.
|
|
375
|
+
"""
|
|
376
|
+
if not hasattr(signal, 'write_access'):
|
|
377
|
+
raise TypeError(f'{signal} is not a valid assignment target.')
|
|
378
|
+
if signal.name not in self.signals or signal.engine is not self:
|
|
379
|
+
raise OwnershipError(f"Signal '{signal.name}' does not belong to this engine.")
|
|
380
|
+
|
|
381
|
+
self.current_state.add_assignment(write_access(signal), read_access(to_renderable(level)))
|
|
382
|
+
self.next_state.add_assignment(signal, read_access(to_renderable(reset_level)))
|
|
383
|
+
# Restrict transitions
|
|
384
|
+
self.current_state._restrict_transition(self.next_state)
|
|
385
|
+
|
|
386
|
+
# Creating transitions to other states
|
|
387
|
+
def sync(self) -> None:
|
|
388
|
+
"""Synchronize outputs.
|
|
389
|
+
|
|
390
|
+
This creates a new state and sets it as new current state.
|
|
391
|
+
It also locks the transitions for the current state. You cannot add any more transitions to it.
|
|
392
|
+
"""
|
|
393
|
+
self.current_state._add_transition(Const(True), self.next_state)
|
|
394
|
+
self.current_state._lock_transitions()
|
|
395
|
+
self.current_state = self.next_state
|
|
396
|
+
|
|
397
|
+
def wait_for(self, condition: Renderable) -> None:
|
|
398
|
+
"""Wait until a condition is met.
|
|
399
|
+
|
|
400
|
+
This creates a new state and sets it as new current state.
|
|
401
|
+
It also locks the transitions for the current state. You cannot add any more transitions to it.
|
|
402
|
+
|
|
403
|
+
Arguments:
|
|
404
|
+
condition: The condition to wait for.
|
|
405
|
+
|
|
406
|
+
Raises:
|
|
407
|
+
ValueError: If the condition is not a Renderable instance.
|
|
408
|
+
KeyError: If the condition signal does not exist in the current engine.
|
|
409
|
+
"""
|
|
410
|
+
if not hasattr(condition, 'render'):
|
|
411
|
+
raise ValueError('Condition must be a Renderable instance')
|
|
412
|
+
try:
|
|
413
|
+
condition.render()
|
|
414
|
+
except AttributeError:
|
|
415
|
+
raise KeyError(f"Condition '{condition}' does not exist in this engine.")
|
|
416
|
+
if self.current_state.transitions:
|
|
417
|
+
raise TransitionLockError('wait_for() cannot be used in combination with other conditional transitions.')
|
|
418
|
+
|
|
419
|
+
self.current_state._add_transition(read_access(to_renderable(condition)), self.next_state)
|
|
420
|
+
self.current_state._lock_transitions()
|
|
421
|
+
self.current_state = self.next_state
|
|
422
|
+
|
|
423
|
+
def jump_if(self, condition: Renderable, true_state: StateProto, false_state: Optional[StateProto] = None) -> None:
|
|
424
|
+
"""Jump to a certain state, if condition is met.
|
|
425
|
+
|
|
426
|
+
If the condition is not met, the engine stays in this state. jump_if can be used multiple times, as long as false_state is omitted.
|
|
427
|
+
The conditions will be evaluated in the order in which they are defined.
|
|
428
|
+
|
|
429
|
+
If false_state is provided, a transition to this state will be added.
|
|
430
|
+
After you have used jump_if with a false_state, you can no longer add more transistions.
|
|
431
|
+
|
|
432
|
+
This method will not create new states and stay at the current state. You need to manually define the states provided to true_state or false_state.
|
|
433
|
+
|
|
434
|
+
Arguments:
|
|
435
|
+
condition: The condition to jump if.
|
|
436
|
+
true_state: The state to jump to if the condition is met.
|
|
437
|
+
false_state: The state to jump to if the condition is not met.
|
|
438
|
+
|
|
439
|
+
Raises:
|
|
440
|
+
ValueError: If the condition is not a Renderable instance.
|
|
441
|
+
KeyError: If the condition signal does not exist in the current engine.
|
|
442
|
+
"""
|
|
443
|
+
if not hasattr(condition, 'render'):
|
|
444
|
+
raise ValueError('Condition must be a Renderable instance')
|
|
445
|
+
try:
|
|
446
|
+
condition.render()
|
|
447
|
+
except AttributeError:
|
|
448
|
+
raise KeyError(f"Condition '{condition}' does not exist in this engine.")
|
|
449
|
+
|
|
450
|
+
if true_state not in self.current_worker.states:
|
|
451
|
+
raise ValueError('Target state does not exist in current thread')
|
|
452
|
+
|
|
453
|
+
if false_state is not None:
|
|
454
|
+
if false_state not in self.current_worker.states:
|
|
455
|
+
raise ValueError('Target state does not exist in current thread')
|
|
456
|
+
|
|
457
|
+
self.current_state._add_transition(read_access(to_renderable(condition)), true_state)
|
|
458
|
+
|
|
459
|
+
if false_state is not None:
|
|
460
|
+
self.current_state._add_transition(to_renderable(~condition), false_state)
|
|
461
|
+
self.current_state._lock_transitions()
|
|
462
|
+
|
|
463
|
+
# Debugging Prints
|
|
464
|
+
def print(self, line: str, *args: Renderable) -> None:
|
|
465
|
+
"""Adds a line to the print list of the current state that will be processed during simulation."""
|
|
466
|
+
self.current_state.print(line, *args)
|
|
467
|
+
|
|
468
|
+
def printf(self, fname: str, line: str, *args: Renderable) -> None:
|
|
469
|
+
"""Store an item that will be output to a file during simulation."""
|
|
470
|
+
self.current_state.printf(fname, line, *args)
|
|
471
|
+
|
|
472
|
+
# Module definition
|
|
473
|
+
@property
|
|
474
|
+
def modules(self) -> Mapping[str, Module]:
|
|
475
|
+
"""Mapping of available modules this engine."""
|
|
476
|
+
return self._modules
|
|
477
|
+
|
|
478
|
+
@property
|
|
479
|
+
def module_instances(self) -> Mapping[str, ModuleInstance]:
|
|
480
|
+
"""Mapping of module instances for this engine."""
|
|
481
|
+
return self._module_instances
|
|
482
|
+
|
|
483
|
+
def define_module(self, name: str) -> Module:
|
|
484
|
+
"""Define a new module.
|
|
485
|
+
|
|
486
|
+
Arguments:
|
|
487
|
+
name: Name of the module.
|
|
488
|
+
|
|
489
|
+
Returns:
|
|
490
|
+
The defined module.
|
|
491
|
+
|
|
492
|
+
Raises:
|
|
493
|
+
KeyError: If a module with the same name already exists.
|
|
494
|
+
"""
|
|
495
|
+
if name in self.modules:
|
|
496
|
+
raise KeyError(f'Module {name} already exists.')
|
|
497
|
+
module = Module(name)
|
|
498
|
+
self._modules[name] = module
|
|
499
|
+
return module
|
|
500
|
+
|
|
501
|
+
def add_module(self, module: ModuleProto) -> None:
|
|
502
|
+
"""Add a module from a library.
|
|
503
|
+
|
|
504
|
+
Arguments:
|
|
505
|
+
module: The module to be added.
|
|
506
|
+
|
|
507
|
+
Raises:
|
|
508
|
+
KeyError: If a module with the same name already exists.
|
|
509
|
+
"""
|
|
510
|
+
if module.name in self.modules:
|
|
511
|
+
raise KeyError(f'Module {module.name} already exists.')
|
|
512
|
+
self._modules[module.name] = module # type: ignore[assignment]
|
|
513
|
+
|
|
514
|
+
def create_module_instance(self, module_name: str, instance_name: str, clock_gating: bool = False) -> ModuleInstance:
|
|
515
|
+
"""Create a new instance of a module.
|
|
516
|
+
|
|
517
|
+
Arguments:
|
|
518
|
+
module_name: Name of the module.
|
|
519
|
+
instance_name: Name of the instance.
|
|
520
|
+
clock_gating: If the unit is to be included in clock gating (if clock gating is enabled)
|
|
521
|
+
|
|
522
|
+
Returns:
|
|
523
|
+
The created module instance.
|
|
524
|
+
|
|
525
|
+
Raises:
|
|
526
|
+
KeyError: If the module does not exist or if an instance with the same name already exists.
|
|
527
|
+
"""
|
|
528
|
+
if module_name not in self.modules:
|
|
529
|
+
raise KeyError(f'Module {module_name} does not exist.')
|
|
530
|
+
if instance_name in self.module_instances:
|
|
531
|
+
raise KeyError(f'Module instance {instance_name} already exists.')
|
|
532
|
+
module = self.modules[module_name]
|
|
533
|
+
instance = ModuleInstance(module, instance_name, clock_gating=clock_gating)
|
|
534
|
+
self._module_instances[instance_name] = instance
|
|
535
|
+
return instance
|
|
536
|
+
|
|
537
|
+
def connect_module_port(self, instance_name: str, port_name: str, signal: PermanentSignal) -> None:
|
|
538
|
+
"""Connect a port of a module instance to a signal.
|
|
539
|
+
|
|
540
|
+
Arguments:
|
|
541
|
+
instance_name: Name of the instance.
|
|
542
|
+
port_name: Name of the port.
|
|
543
|
+
signal: The signal to be connected.
|
|
544
|
+
|
|
545
|
+
Raises:
|
|
546
|
+
KeyError: If the instance or port does not exist.
|
|
547
|
+
TypeError: If the signal is not a signal or signal slice. Scratch signals cannot be connected to modules.
|
|
548
|
+
ValueError: If the port does not exist in the module.
|
|
549
|
+
"""
|
|
550
|
+
if instance_name not in self.module_instances:
|
|
551
|
+
raise KeyError(f'Module instance {instance_name} does not exist.')
|
|
552
|
+
if not isinstance(signal, (Signal, SignalSlice)):
|
|
553
|
+
raise TypeError(f'Signal {signal} can not be connected to a module port.')
|
|
554
|
+
instance = self.module_instances[instance_name]
|
|
555
|
+
|
|
556
|
+
if not instance.module.has_port(port_name):
|
|
557
|
+
raise ValueError(f'Port {port_name} does not exist in module instance {instance_name}.')
|
|
558
|
+
|
|
559
|
+
instance.connect_port(port_name, signal)
|
|
560
|
+
|
|
561
|
+
def override_module_parameter(self, instance_name: str, parameter_name: str, value: Union[int, Parameter, Renderable]) -> None:
|
|
562
|
+
"""Override a parameter of a module instance.
|
|
563
|
+
|
|
564
|
+
Arguments:
|
|
565
|
+
instance_name: Name of the instance.
|
|
566
|
+
parameter_name: Name of the parameter.
|
|
567
|
+
value: New value of the parameter.
|
|
568
|
+
|
|
569
|
+
Raises:
|
|
570
|
+
KeyError: If the instance or parameter does not exist.
|
|
571
|
+
"""
|
|
572
|
+
if instance_name not in self.module_instances:
|
|
573
|
+
raise KeyError(f'Module instance {instance_name} does not exist.')
|
|
574
|
+
instance = self.module_instances[instance_name]
|
|
575
|
+
instance.override_parameter(parameter_name, value)
|