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.
Files changed (60) hide show
  1. nortl/__init__.py +85 -0
  2. nortl/components/__init__.py +8 -0
  3. nortl/components/channel.py +132 -0
  4. nortl/components/timer.py +73 -0
  5. nortl/core/__init__.py +40 -0
  6. nortl/core/checker.py +135 -0
  7. nortl/core/common/__init__.py +4 -0
  8. nortl/core/common/access.py +25 -0
  9. nortl/core/common/debug.py +6 -0
  10. nortl/core/common/naming_helper.py +33 -0
  11. nortl/core/constructs/__init__.py +13 -0
  12. nortl/core/constructs/condition.py +143 -0
  13. nortl/core/constructs/fork_join.py +84 -0
  14. nortl/core/constructs/loop.py +138 -0
  15. nortl/core/engine.py +575 -0
  16. nortl/core/exceptions.py +139 -0
  17. nortl/core/manager/__init__.py +6 -0
  18. nortl/core/manager/scratch_manager.py +128 -0
  19. nortl/core/manager/signal_manager.py +71 -0
  20. nortl/core/modifiers.py +136 -0
  21. nortl/core/module.py +181 -0
  22. nortl/core/operations.py +834 -0
  23. nortl/core/parameter.py +88 -0
  24. nortl/core/process.py +451 -0
  25. nortl/core/protocols.py +628 -0
  26. nortl/core/renderers/__init__.py +0 -0
  27. nortl/core/renderers/operations/__init__.py +34 -0
  28. nortl/core/renderers/operations/arithmetics.py +38 -0
  29. nortl/core/renderers/operations/base.py +111 -0
  30. nortl/core/renderers/operations/comparison.py +44 -0
  31. nortl/core/renderers/operations/logic.py +38 -0
  32. nortl/core/renderers/operations/misc.py +26 -0
  33. nortl/core/renderers/operations/slice.py +30 -0
  34. nortl/core/signal.py +878 -0
  35. nortl/core/state.py +201 -0
  36. nortl/py.typed +0 -0
  37. nortl/renderer/__init__.py +5 -0
  38. nortl/renderer/mermaid_renderer.py +38 -0
  39. nortl/renderer/networkx_renderer.py +29 -0
  40. nortl/renderer/verilog_renderer.py +325 -0
  41. nortl/renderer/verilog_utils/__init__.py +6 -0
  42. nortl/renderer/verilog_utils/formatter.py +29 -0
  43. nortl/renderer/verilog_utils/process.py +226 -0
  44. nortl/renderer/verilog_utils/structural.py +146 -0
  45. nortl/renderer/verilog_utils/utils.py +23 -0
  46. nortl/utils/__init__.py +0 -0
  47. nortl/utils/parse_utils.py +37 -0
  48. nortl/utils/templates/testbench.sv +41 -0
  49. nortl/utils/test_wrapper.py +218 -0
  50. nortl/utils/type_aliases.py +15 -0
  51. nortl/verilog_library/__init__.py +74 -0
  52. nortl/verilog_library/nortl_clock_gate.sv +20 -0
  53. nortl/verilog_library/nortl_count_down_timer.sv +50 -0
  54. nortl/verilog_library/nortl_delay.sv +66 -0
  55. nortl/verilog_library/nortl_edge_detector.sv +34 -0
  56. nortl/verilog_library/nortl_sync.sv +28 -0
  57. nortl-1.4.0.dist-info/METADATA +105 -0
  58. nortl-1.4.0.dist-info/RECORD +60 -0
  59. nortl-1.4.0.dist-info/WHEEL +4 -0
  60. nortl-1.4.0.dist-info/licenses/LICENSE +11 -0
@@ -0,0 +1,88 @@
1
+ """HDL Parameters."""
2
+
3
+ from typing import Final, Optional, Set
4
+
5
+ from .operations import OperationTrait
6
+ from .protocols import ACCESS_CHECKS, EngineProto
7
+
8
+ __all__ = [
9
+ 'Parameter',
10
+ ]
11
+
12
+
13
+ class Parameter(OperationTrait):
14
+ """Parameter definition, representing a Verilog parameter.
15
+
16
+ The parameters are to be considered of data type int.
17
+
18
+ Attributes:
19
+ engine: Finite state machine associated with this Parameter.
20
+ name: Name of the Parameter.
21
+ default_value: Default value of the Parameter.
22
+
23
+ """
24
+
25
+ is_primitive: Final = True
26
+
27
+ def __init__(self, engine: EngineProto, name: str, default_value: int, width: Optional[int] = None) -> None:
28
+ """Initialize a Parameter object.
29
+
30
+ Arguments:
31
+ engine: State machine container object.
32
+ name: Parameter name.
33
+ default_value: Default value of the parameter
34
+ width: Width of the parameter.
35
+ """
36
+ if name.startswith('_'):
37
+ raise ValueError('Parameter names must not start with an underscore!')
38
+
39
+ self._engine = engine
40
+ self._name = name
41
+ self._default_value = default_value
42
+ self._width = width
43
+
44
+ @property
45
+ def engine(self) -> EngineProto:
46
+ """Finite state machine."""
47
+ return self._engine
48
+
49
+ @property
50
+ def name(self) -> str:
51
+ """Parameter name."""
52
+ return self._name
53
+
54
+ @property
55
+ def default_value(self) -> int:
56
+ """Default value."""
57
+ return self._default_value
58
+
59
+ @property
60
+ def width(self) -> Optional[int]:
61
+ """Indicates the width of the parameter in bits.
62
+
63
+ Parameters with a fixed width will be rendered as `parameter [<width>-1:0] <name>`.
64
+ """
65
+ return self._width
66
+
67
+ # Implement OperationTrait
68
+ @property
69
+ def operand_width(self) -> Optional[int]:
70
+ """Indicates the width when used as an operand.
71
+
72
+ A width of None means that the width is not fixed during execution of noRTL.
73
+ """
74
+ return self.width
75
+
76
+ def read_access(self, ignore: Set[ACCESS_CHECKS] = set()) -> None:
77
+ """Register read access from the current thread, state and construct depth.
78
+
79
+ Does not invoke any checks for this object.
80
+ """
81
+
82
+ def render(self, target: Optional[str] = None) -> str:
83
+ """Render value to target language.
84
+
85
+ Arguments:
86
+ target: Target language.
87
+ """
88
+ return self.name
nortl/core/process.py ADDED
@@ -0,0 +1,451 @@
1
+ import math
2
+ from typing import List, Optional, Sequence, Set
3
+
4
+ from typing_extensions import Self
5
+
6
+ from nortl.core.exceptions import OwnershipError, UnfinishedForwardDeclarationError
7
+
8
+ from .common import NamedEntity
9
+ from .modifiers import Volatile
10
+ from .operations import Const, Var
11
+ from .protocols import EngineProto, Renderable, SignalProto, StateProto, WorkerProto
12
+ from .state import State
13
+
14
+
15
+ class Worker(NamedEntity):
16
+ """Worker for noRTL engine.
17
+
18
+ Each worker represents a state machine, that runs independently from the others.
19
+ The workers store their states. Creating state transitions between different workers is not possible.
20
+
21
+ The noRTL engine contains one main worker by default. Additional workers can be created manually.
22
+ The main worker is responsible for managing the reset state for all signals; the other workers do not contain any assignments in the reset state.
23
+
24
+ Each worker must have one or more threads.
25
+ """
26
+
27
+ def __init__(self, engine: EngineProto, name: str, reset_state_name: str = 'IDLE') -> None:
28
+ """Initialize a new Worker.
29
+
30
+ Arguments:
31
+ engine: noRTL engine.
32
+ name: Name of the worker.
33
+ reset_state_name: Name of the reset state for this worker.
34
+
35
+ !!! warning
36
+
37
+ Workers are not meant to be instantiated manually. Use the [`CoreEngine.create_worker()`][nortl.core.engine.CoreEngine.create_worker] method instead.
38
+ """
39
+ super().__init__(name)
40
+
41
+ self._engine = engine
42
+ self._is_main_worker = name == engine.MAIN_WORKER_NAME
43
+
44
+ # State tracking
45
+ self._states: List[State] = []
46
+ self._state_names: Set[str] = set()
47
+
48
+ # Thread tracking
49
+ self._threads: List['Thread'] = [] # Threads mapped to this worker
50
+
51
+ # Create reset state and set to current state
52
+ self._reset_state = self.create_state(reset_state_name, allow_assignments=self.is_main_worker)
53
+ self._current_state = self.reset_state
54
+ self._next_state: Optional[State] = None
55
+
56
+ # Internal control signals
57
+ # The main worker does not use these signals
58
+ self._reset_signal: Optional[Volatile[SignalProto]] = None
59
+ self._start_signal: Optional[Volatile[SignalProto]] = None
60
+ self._select_signal: Optional[Volatile[SignalProto]] = None
61
+ self._idle_signal: Optional[Volatile[SignalProto]] = None
62
+ self._select_signal_width: Optional[Var] = None
63
+
64
+ # Synchronous reset, inactive by default
65
+ self._sync_reset: Optional[Renderable] = None
66
+
67
+ @property
68
+ def engine(self) -> EngineProto:
69
+ """NoRTL engine for this worker."""
70
+ return self._engine
71
+
72
+ @property
73
+ def is_main_worker(self) -> bool:
74
+ """If this worker is the main worker of the noRTL engine."""
75
+ return self._is_main_worker
76
+
77
+ def create_scoped_name(self, name: str) -> str:
78
+ """Create scoped name for signals or other things.
79
+
80
+ If this worker is not the main worker, the name is prefixed with the name of the worker.
81
+ """
82
+ # FIXME decide if worker prefix is omitted for main worker
83
+ if not self.is_main_worker and not name.startswith(self.name):
84
+ name = f'{self.name}_{name}'
85
+ return name
86
+
87
+ @property
88
+ def sync_reset(self) -> Renderable:
89
+ """Synchronous reset for worker."""
90
+ if self._sync_reset is None:
91
+ return Const(0)
92
+ return self._sync_reset
93
+
94
+ @sync_reset.setter
95
+ def sync_reset(self, value: Renderable) -> None:
96
+ """Synchronous reset for worker."""
97
+ if self._sync_reset is not None:
98
+ raise RuntimeError(f'Synchronous reset for worker {self.name} was already set to {self.sync_reset}.')
99
+ self._sync_reset = value
100
+
101
+ # Control Signals for Threads
102
+ # FIXME technically, these signals are only required for Fork/Join management. They serve no purpose for raw Worker + Thread
103
+ @property
104
+ def reset(self) -> SignalProto:
105
+ """Control signal to reset worker.
106
+
107
+ If the worker control signal is used, it is automatically added to the synchronous reset.
108
+ """
109
+ if self.is_main_worker:
110
+ raise RuntimeError('Main worker does not use reset signal.')
111
+ elif self._reset_signal is None:
112
+ self._initialize_control_signals()
113
+ return self._reset_signal # type: ignore[return-value]
114
+
115
+ @property
116
+ def start(self) -> SignalProto:
117
+ """Control signal to start worker."""
118
+ if self.is_main_worker:
119
+ raise RuntimeError('Main worker does not use start signal.')
120
+ elif self._start_signal is None:
121
+ self._initialize_control_signals()
122
+ return self._start_signal # type: ignore[return-value]
123
+
124
+ @property
125
+ def select(self) -> SignalProto:
126
+ """Control signal that selects a thread."""
127
+ if self.is_main_worker:
128
+ raise RuntimeError('Main worker does not use select signal.')
129
+ elif self._select_signal is None:
130
+ self._initialize_control_signals()
131
+ return self._select_signal # type: ignore[return-value]
132
+
133
+ @property
134
+ def idle(self) -> SignalProto:
135
+ """Status signal that indicates if the worker is in it's idle state."""
136
+ if self.is_main_worker:
137
+ raise RuntimeError('Main worker does not use idle signal.')
138
+ elif self._idle_signal is None:
139
+ self._initialize_control_signals()
140
+ return self._idle_signal # type: ignore[return-value]
141
+
142
+ def _initialize_control_signals(self) -> None:
143
+ """Initializes built-in signals for the worker."""
144
+
145
+ # Local Signals
146
+ self._reset_signal = Volatile(
147
+ self.engine.define_local(self.create_scoped_name('reset'), width=1, reset_value=0, pulsing=True), 'identical_rw', 'exclusive_write'
148
+ )
149
+ self._start_signal = Volatile(
150
+ self.engine.define_local(self.create_scoped_name('start'), width=1, reset_value=0, pulsing=True), 'identical_rw', 'exclusive_write'
151
+ )
152
+ self._select_signal_width = Var(self._get_select_width())
153
+ self._select_signal = Volatile(
154
+ self.engine.define_local(self.create_scoped_name('select'), width=self._select_signal_width, reset_value=0),
155
+ 'identical_rw',
156
+ 'exclusive_write',
157
+ )
158
+
159
+ # the following signal has to be set to zero once a thread is started:
160
+ # FIXME: Set signal to zero and to one after first state
161
+ self._idle_signal = Volatile(self.engine.define_local(self.create_scoped_name('idle'), reset_value=1), 'identical_rw', 'exclusive_write')
162
+
163
+ # Tie synchrounous reset to the new reset signal
164
+ self.sync_reset = self.reset
165
+
166
+ def _get_select_width(self) -> int:
167
+ """Calculate necessary width for thread select signal."""
168
+ return math.ceil(math.log2(len(self.threads)) + 1)
169
+
170
+ def _resize_select_signal(self) -> None:
171
+ """Updates the internal signal width of the select variable."""
172
+ if self._select_signal_width is not None:
173
+ self._select_signal_width.update(self._get_select_width())
174
+
175
+ # State management
176
+ @property
177
+ def states(self) -> Sequence[State]:
178
+ """List of states for this worker."""
179
+ return self._states
180
+
181
+ @property
182
+ def state_names(self) -> Set[str]:
183
+ """Set of the names of all states for this worker."""
184
+ return self._state_names
185
+
186
+ def create_state(self, name: Optional[str] = None, allow_assignments: bool = True) -> State:
187
+ """Create a state.
188
+
189
+ Arguments:
190
+ name: Optional state name. If no name is provided, it defaults to '<worker_name>_STATE_<id>', where 'id' is the current number of states
191
+ for the worker and 'worker_name' is the name of this worker.
192
+
193
+ If this worker is not the main worker, the name of the state must be prefixed with the name of the current worker.
194
+ The prefix is automatically added, if missing.
195
+ allow_assignments: If the state allows assignments. This is used for internal purposes.
196
+
197
+ Returns:
198
+ The created state.
199
+ """
200
+ # Generate default state name
201
+ if name is None:
202
+ name = f'STATE_{len(self.states)}'
203
+
204
+ if len(self.states) > 0 and len(self.threads) == 0:
205
+ raise RuntimeError('Worker has no thread, unable to create new states.')
206
+
207
+ # State will validate the name
208
+ state = State(self, name, allow_assignments=allow_assignments)
209
+ self._states.append(state)
210
+ return state
211
+
212
+ @property
213
+ def current_state(self) -> State:
214
+ """Current state.
215
+
216
+ Returns:
217
+ The current state.
218
+ """
219
+ return self._current_state
220
+
221
+ @current_state.setter
222
+ def current_state(self, state: StateProto) -> None:
223
+ """Current state.
224
+
225
+ Arguments:
226
+ state: The new current state.
227
+
228
+ Raises:
229
+ UnfinishedForwardDeclarationError: If the next state has been forward-declared and is different from the new current state.
230
+ """
231
+ if state.engine is not self.engine:
232
+ raise OwnershipError('State does not belong to this engine.')
233
+ if state not in self.states:
234
+ raise OwnershipError('State does not belong to this worker.')
235
+ if self._next_state is not None and state is not self._next_state:
236
+ raise UnfinishedForwardDeclarationError(
237
+ 'You have forward-declared the next state (by using next_state), but are now trying to set another state as the current one. '
238
+ 'This may result in dead-end states and is therefore forbidden. Please switch to the next_state and modify it first.'
239
+ )
240
+ if state is not self.current_state:
241
+ self._next_state = None # Clear next state
242
+ self._current_state = state # type: ignore[assignment]
243
+
244
+ @property
245
+ def next_state(self) -> State:
246
+ """Forward-declared next state.
247
+
248
+ This simplifies the creation of new states for non-branching sections of the state graph (e.g. via sync() or wait_for()).
249
+ When you use next_state you must set it as current_state, before you can set any other state.
250
+
251
+ Returns:
252
+ The forward-declared next state.
253
+ """
254
+ if self._next_state is None:
255
+ self._next_state = self.create_state()
256
+
257
+ return self._next_state
258
+
259
+ @property
260
+ def reset_state(self) -> State:
261
+ """The reset state of the engine.
262
+
263
+ This is the initial state from which the engine will start.
264
+
265
+ Returns:
266
+ The reset state.
267
+ """
268
+ return self._reset_state
269
+
270
+ def leave_foreground(self) -> None:
271
+ """This method must be called when the current worker of the engine is changed.
272
+
273
+ Checks if the any forward-declared next-state pending.
274
+ """
275
+ if self._next_state is not None:
276
+ raise UnfinishedForwardDeclarationError(
277
+ 'You have forward-declared the next state for the current worker (by using next_state), but are now trying to set another worker as the current one. '
278
+ 'This may result in dead-end states and is therefore forbidden. Please finish the current worker first, by switching to the next_state and modifying it.'
279
+ )
280
+
281
+ # Thread Management
282
+ @property
283
+ def threads(self) -> Sequence['Thread']:
284
+ """Stack of worker threads."""
285
+ return self._threads
286
+
287
+ def create_thread(self, name: Optional[str] = None) -> 'Thread':
288
+ """Create a new thread.
289
+
290
+ Arguments:
291
+ name: Name of the thread.
292
+
293
+ Returns:
294
+ The created thread.
295
+ """
296
+ # Generate default name
297
+ if name is None:
298
+ if len(self.threads) == 0:
299
+ name = self.engine.MAIN_THREAD_NAME
300
+ else:
301
+ name = f'thread_{len(self.threads)}'
302
+
303
+ thread = Thread(self, name)
304
+ self._threads.append(thread)
305
+
306
+ if not self.is_main_worker:
307
+ self._resize_select_signal() # Adjust width of select signal
308
+ return thread
309
+
310
+ @property
311
+ def current_thread(self) -> 'Thread':
312
+ """Current thread for this worker."""
313
+ if len(self.threads) == 0:
314
+ raise RuntimeError('Worker has no threads.')
315
+ return self.threads[-1]
316
+
317
+ @property
318
+ def working(self) -> bool:
319
+ """If the worker is working."""
320
+ return any([t.running for t in self.threads])
321
+
322
+ # Misc.
323
+ # FIXME these helper methods are only used by Fork/Join
324
+ def raise_reset(self) -> None:
325
+ self.engine.set(self.reset, 1)
326
+
327
+ def clear_reset(self) -> None:
328
+ self.engine.set(self.reset, 0)
329
+ self.engine.set(self.idle, 1)
330
+
331
+
332
+ class Thread(NamedEntity):
333
+ """Thread for noRTL engine.
334
+
335
+ Threads are used to control signal ownership.
336
+ They prevent simultaneous access to signals from two workers, that would cause wrong behavior.
337
+ """
338
+
339
+ def __init__(self, worker: WorkerProto, name: str) -> None:
340
+ """Initialize a new thread.
341
+
342
+ Arguments:
343
+ worker: Worker for this thread.
344
+ name: Name of the thread.
345
+
346
+ !!! warning
347
+
348
+ Threads are not meant to be instantiated manually. Use the [`Worker.create_thread()`][nortl.core.process.Worker.create_thread] method instead.
349
+ """
350
+ super().__init__(name)
351
+
352
+ self._worker: WorkerProto = worker
353
+ self._is_main_thread = worker.is_main_worker
354
+ self._active: bool = self.is_main_thread # Main thread is always active
355
+
356
+ self._spawned_threads: List[Self] = []
357
+
358
+ # Link back the parent thread
359
+ self.parent_thread: Optional[Self] = None
360
+ if name != worker.engine.MAIN_THREAD_NAME:
361
+ self.parent_thread = self.worker.engine.current_thread # type: ignore[assignment]
362
+
363
+ @property
364
+ def engine(self) -> EngineProto:
365
+ """NoRTL engine for this thread."""
366
+ return self.worker.engine
367
+
368
+ @property
369
+ def worker(self) -> WorkerProto:
370
+ """Engine worker."""
371
+ return self._worker
372
+
373
+ @property
374
+ def is_main_thread(self) -> bool:
375
+ """If this thread is the main thread of the noRTL engine."""
376
+ return self.worker.is_main_worker
377
+
378
+ @property
379
+ def active(self) -> bool:
380
+ """If this thread is currently active."""
381
+ # FIXME decide if activity can be tracked automatically
382
+ return self._active
383
+
384
+ @active.setter
385
+ def active(self, value: bool) -> None:
386
+ """If this thread is currently active."""
387
+ if self.is_main_thread:
388
+ raise RuntimeError('Main thread cannot be deactivated.')
389
+ self._active = value
390
+
391
+ def join(self) -> None:
392
+ """Join a thread.
393
+
394
+ This waits for the thread to be finished.
395
+ """
396
+ self.engine.wait_for(self.finished)
397
+ self.active = False
398
+ self.engine.scratch_manager.force_release_signals_by_thread(self)
399
+ self.engine.signal_manager.free_accesses_from_thread(self)
400
+
401
+ @property
402
+ def finished(self) -> Renderable:
403
+ return self.worker.idle
404
+
405
+ @property
406
+ def running(self) -> bool:
407
+ if self.parent_thread == self.engine.current_thread:
408
+ return self.active
409
+
410
+ if self.active:
411
+ return True
412
+
413
+ global_call_stack = self.engine.current_thread.call_stack
414
+
415
+ for t in self.call_stack:
416
+ if t not in global_call_stack:
417
+ if t.active:
418
+ return True
419
+
420
+ return False
421
+
422
+ def cancel(self) -> None:
423
+ """This method sends a synchronous reset to the Thread's worker and recursively to all threads, that are spawned from this thread."""
424
+ for subthread in self._spawned_threads:
425
+ subthread.cancel()
426
+
427
+ self.worker.raise_reset()
428
+ self.engine.sync()
429
+ self.worker.clear_reset()
430
+ self.engine.sync()
431
+
432
+ self.active = False
433
+ self.engine.scratch_manager.force_release_signals_by_thread(self)
434
+ self.engine.signal_manager.free_accesses_from_thread(self)
435
+
436
+ def finish(self) -> None:
437
+ """Finishes a Thread."""
438
+ self.engine.jump_if(Const(1), self.worker.reset_state)
439
+ self.engine.set(self.worker.idle, 1)
440
+
441
+ @property
442
+ def call_stack(self) -> List[Self]:
443
+ ret = []
444
+
445
+ thread = self
446
+
447
+ while thread.parent_thread is not None:
448
+ ret.append(thread.parent_thread)
449
+ thread = thread.parent_thread
450
+
451
+ return ret