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
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)