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,139 @@
1
+ """This module defines exception types for noRTL."""
2
+
3
+ from typing import TypeVar
4
+
5
+ from nortl.core.protocols import AssignmentTarget, Renderable
6
+
7
+ __all__ = [
8
+ 'AccessAfterReleaseError',
9
+ 'ConflictingAssignmentError',
10
+ 'ExclusiveReadError',
11
+ 'ExclusiveWriteError',
12
+ 'ForbiddenAssignmentError',
13
+ 'NonIdenticalRWError',
14
+ 'OwnershipError',
15
+ 'TransitionLockError',
16
+ 'TransitionRestrictionError',
17
+ 'UnfinishedForwardDeclarationError',
18
+ 'WriteViolationError',
19
+ 'read_access',
20
+ 'write_access',
21
+ ]
22
+
23
+
24
+ T_Read = TypeVar('T_Read', bound=Renderable)
25
+ T_Write = TypeVar('T_Write', bound=AssignmentTarget)
26
+
27
+
28
+ # Access Violation Errors
29
+ class AccessViolationError(RuntimeError):
30
+ """Base class for errors caused by invalid read or write access to signals or operation results."""
31
+
32
+
33
+ class ExclusiveReadError(AccessViolationError):
34
+ """This error occurs when a signal is read by more than one thread.
35
+
36
+ This error can be suppressed by disabling or ignoring the 'exclusive_read' check for a signal.
37
+
38
+ To permanently disable the check, use `signal.acess_checker.disable_check('exclusive_read')`.
39
+
40
+ To ignore the check during a read or write access, wrap the signal in a [Volatile][nortl.core.modifiers.Volatile] modifier with
41
+ `Volatile(signal, 'exclusive_read')`.
42
+ """
43
+
44
+
45
+ class ExclusiveWriteError(AccessViolationError):
46
+ """This error occurs when a signal is written by more than one thread.
47
+
48
+ This error can be suppressed by disabling or ignoring the 'exclusive_write' check for a signal.
49
+
50
+ To permanently disable the check, use `signal.acess_checker.disable_check(''exclusive_write)`.
51
+
52
+ To ignore the check during a read or write access, wrap the signal in a [Volatile][nortl.core.modifiers.Volatile] modifier with
53
+ `Volatile(signal, 'exclusive_write')`.
54
+ """
55
+
56
+
57
+ class NonIdenticalRWError(AccessViolationError):
58
+ """This error occurs when a signal is read and written by different threads.
59
+
60
+ This error can be suppressed by disabling or ignoring the 'identical_rw' check for a signal.
61
+
62
+ To permanently disable the check, use `signal.acess_checker.disable_checkidentical_rw('')`.
63
+
64
+ To ignore the check during a read or write access, wrap the signal in a [Volatile][nortl.core.modifiers.Volatile] modifier with
65
+ `Volatile(signal, 'identical_rw')`.
66
+ """
67
+
68
+
69
+ class AccessAfterReleaseError(AccessViolationError):
70
+ """This error occurs when a scratch signal is accessed after being released."""
71
+
72
+
73
+ class WriteViolationError(AccessViolationError):
74
+ """This error occurs when an input signal is written."""
75
+
76
+
77
+ def read_access(object: T_Read) -> T_Read:
78
+ """Perform read access to renderable.
79
+
80
+ This helper function hides the traceback caused during recursive read access.
81
+ """
82
+ try:
83
+ object.read_access()
84
+ except AccessViolationError as e:
85
+ raise e.with_traceback(None)
86
+ return object
87
+
88
+
89
+ def write_access(object: T_Write) -> T_Write:
90
+ """Perform write access to signal.
91
+
92
+ This helper function hides the traceback caused during recursive write access.
93
+ """
94
+ try:
95
+ object.write_access()
96
+ except AccessViolationError as e:
97
+ raise e.with_traceback(None)
98
+ return object
99
+
100
+
101
+ # Transition Errors
102
+ class TransitionError(RuntimeError):
103
+ """Base class for errors related to invalid transitions."""
104
+
105
+
106
+ class TransitionLockError(TransitionError):
107
+ """This error occurs when attempting to add new transitions to a state that has been locked or would need to be locked but cannot."""
108
+
109
+
110
+ class TransitionRestrictionError(TransitionError):
111
+ """This error occurs when attempting to add new transitions to a state that has been restricted to transition to a specific other state."""
112
+
113
+
114
+ # Assignment Errors
115
+ class AssignmentError(RuntimeError):
116
+ """Base class for errors related to invalid assignments."""
117
+
118
+
119
+ class ForbiddenAssignmentError(AssignmentError):
120
+ """This error occurs when attempting to add assignments to a state that does not allow it."""
121
+
122
+
123
+ class ConflictingAssignmentError(AssignmentError):
124
+ """This error occurs when attempting to add multiple conflicting assignments to the same signal in one state."""
125
+
126
+
127
+ # Misc. Errors
128
+ class OwnershipError(RuntimeError):
129
+ """This error occurs when noRTL instances, that don't belong to the same noRTL Engine are mixed up.
130
+
131
+ This can occur when noRTL is used in an interactive context, e.g. a Jupyter notebook.
132
+ """
133
+
134
+
135
+ class UnfinishedForwardDeclarationError(RuntimeError):
136
+ """This error occurs when the worker has a forward-declared state, but the user attempts to switch the current state to a different state.
137
+
138
+ This error is meant to prevent dead-ends in the state graph.
139
+ """
@@ -0,0 +1,6 @@
1
+ """This folder holds objects that are instantiated by the CoreEngine to 'manage' certain other objects such as signals."""
2
+
3
+ from .scratch_manager import ScratchManager
4
+ from .signal_manager import SignalManager
5
+
6
+ __all__ = ['ScratchManager', 'SignalManager']
@@ -0,0 +1,128 @@
1
+ from collections import deque
2
+ from typing import Deque, List, Optional
3
+
4
+ from more_itertools import run_length
5
+
6
+ from nortl.core.operations import Var
7
+ from nortl.core.protocols import EngineProto, ScratchSignalProto, SignalProto, ThreadProto
8
+ from nortl.core.signal import ScratchSignal
9
+
10
+
11
+ class ScratchManager:
12
+ def __init__(self, engine: EngineProto) -> None:
13
+ self.engine = engine
14
+
15
+ self._scratchpad_width: Optional[Var] = None
16
+ self._scratchpad: Optional[SignalProto] = None
17
+
18
+ self.scratch_signals: Deque[ScratchSignalProto] = deque([])
19
+
20
+ @property
21
+ def scratchpad_width(self) -> int:
22
+ """Width of scratch pad signal."""
23
+ if self._scratchpad_width is None:
24
+ self._create_scratch_pad()
25
+ return self._scratchpad_width.value # type: ignore[union-attr]
26
+
27
+ @scratchpad_width.setter
28
+ def scratchpad_width(self, value: int) -> None:
29
+ """Width of scratch pad signal."""
30
+ if self._scratchpad_width is None:
31
+ self._create_scratch_pad()
32
+ if value < self.scratchpad_width:
33
+ raise ValueError('Scratch pad width must not be decreased.')
34
+ self._scratchpad_width.update(value) # type: ignore[union-attr]
35
+
36
+ @property
37
+ def scratchpad(self) -> SignalProto:
38
+ """Scratch pad."""
39
+ if self._scratchpad is None:
40
+ self._create_scratch_pad()
41
+ return self._scratchpad # type: ignore[return-value]
42
+
43
+ def _create_scratch_pad(self) -> None:
44
+ """Create scratchpad signal."""
45
+ self._scratchpad_width = Var(0)
46
+ self._scratchpad = self.engine.define_local('SCRATCH_SIGNAL', width=self._scratchpad_width, reset_value=0)
47
+
48
+ def create(self, width: int) -> ScratchSignal:
49
+ position = self.alloc(width)
50
+
51
+ if position is None:
52
+ raise ValueError('Scratch map full!')
53
+
54
+ new_scratch_signal = self.scratchpad[position + width - 1 : position].as_scratch_signal()
55
+ self.scratch_signals.append(new_scratch_signal)
56
+ return new_scratch_signal # type: ignore[return-value]
57
+
58
+ @property
59
+ def scratch_map(self) -> List[bool]:
60
+ """Returns a list that shows the state of the currently allocated scratchpad.
61
+
62
+ The returned List comprises each bit on the scratchpad as a bool. True-values show, that the bit is currently in use.
63
+ This function is the base for the allocation algorithm that needs to find an appropriate slice of the scratchpad that
64
+ can be used for the next scratch register.
65
+
66
+ This function could be realized in a more incremental way in a future version.
67
+ """
68
+
69
+ smap = self.scratchpad_width * [False]
70
+
71
+ for elem in self.scratch_signals:
72
+ if not elem.released:
73
+ if isinstance(elem.index, slice):
74
+ bits = list(elem.index.indices(self.scratchpad_width))
75
+
76
+ if bits[1] < bits[0]:
77
+ bits[0], bits[1] = bits[1], bits[0]
78
+
79
+ # Include last bit in range
80
+ bits[1] += 1
81
+
82
+ for idx in range(*bits):
83
+ smap[idx] = True
84
+ else:
85
+ smap[elem.index] = True
86
+
87
+ return smap
88
+
89
+ def alloc(self, width: int) -> Optional[int]:
90
+ """Looks for a portion of the scratchpad, where N bits can be used.
91
+
92
+ The function returns the first bit's position.
93
+ """
94
+
95
+ mmap_encoded = run_length.encode(self.scratch_map)
96
+
97
+ position = 0
98
+ for used, length in mmap_encoded:
99
+ if not used and length >= width:
100
+ return position
101
+ position = position + length
102
+
103
+ # We did not find a position until now, so we extend the scratch pad by enough bits to fit the new signal in.
104
+ self.scratchpad_width += width
105
+
106
+ return self.alloc(width)
107
+
108
+ def enter_context(self) -> None:
109
+ """Triggers the enter_context function of all managed scratch signals."""
110
+ for scratch_signal in self.scratch_signals:
111
+ scratch_signal.enter_context()
112
+
113
+ def exit_context(self) -> None:
114
+ """Triggers the exit_context function of all managed scratch signals."""
115
+ for scratch_signal in self.scratch_signals:
116
+ scratch_signal.exit_context()
117
+
118
+ def force_release_signals_by_thread(self, thread: ThreadProto) -> None:
119
+ """Releases all signals of the current thread. This is triggered by the Thread class after the thread has safely ended."""
120
+ for scratch_signal in self.scratch_signals:
121
+ if scratch_signal.owner == thread:
122
+ scratch_signal.release(force=True)
123
+
124
+ self.free_accesses_from_thread(thread)
125
+
126
+ def free_accesses_from_thread(self, thread: ThreadProto) -> None:
127
+ for scratch_signal in self.scratch_signals:
128
+ scratch_signal.free_access_from_thread(thread)
@@ -0,0 +1,71 @@
1
+ from typing import Dict, List, Mapping, Optional, Sequence, Tuple, Union
2
+
3
+ from nortl.core.protocols import SIGNAL_TYPES, EngineProto, ParameterProto, Renderable, ThreadProto
4
+ from nortl.core.signal import Signal
5
+
6
+
7
+ class SignalManager:
8
+ def __init__(self, engine: EngineProto) -> None:
9
+ self.engine = engine
10
+ self._signals: Dict[str, Signal] = {}
11
+ self._combinationals: List[Tuple[Signal, Renderable]] = []
12
+
13
+ @property
14
+ def signals(self) -> Mapping[str, Signal]:
15
+ """Signals."""
16
+ return self._signals
17
+
18
+ @property
19
+ def combinationals(self) -> Sequence[Tuple[Signal, Renderable]]:
20
+ """Sequence of combinational signal assignments."""
21
+ return self._combinationals
22
+
23
+ def get_signal(self, name: str) -> Signal:
24
+ return self.signals[name]
25
+
26
+ def create_signal(
27
+ self,
28
+ type: SIGNAL_TYPES,
29
+ name: str,
30
+ width: Union[int, ParameterProto, Renderable] = 1,
31
+ data_type: str = 'logic',
32
+ is_synchronized: bool = False,
33
+ pulsing: bool = False,
34
+ assignment: Optional[Renderable] = None,
35
+ ) -> Signal:
36
+ """Create a signal.
37
+
38
+ Arguments:
39
+ type: Type of the signal.
40
+ name: Name of the signal.
41
+ width: Width of the signal in bits.
42
+ data_type: Data type of the signal.
43
+ is_synchronized: Indicates, if the signal is synchronous to the used clock domain
44
+ pulsing: If true, the signal automatically resets to zero if not set in the current state
45
+ assignment: Source expression for combinational assignment.
46
+
47
+ Returns:
48
+ The created signal.
49
+
50
+ Raises:
51
+ KeyError: If the signal name already exists.
52
+ """
53
+
54
+ if name in self.signals:
55
+ raise KeyError(f'Signal name {name} already exists.')
56
+ if name in self.engine.parameters:
57
+ raise KeyError(f'Signal name {name} collides with existing parameter name.')
58
+ signal = Signal(
59
+ self.engine, type, name, width=width, data_type=data_type, is_synchronized=is_synchronized, pulsing=pulsing, assignment=assignment
60
+ )
61
+ self._signals[name] = signal
62
+
63
+ # TODO width of assignment source is not respected, would be better to automatically pick it up?
64
+ # Maybe replace default of width with None?
65
+ if assignment is not None:
66
+ self._combinationals.append((signal, assignment))
67
+ return signal
68
+
69
+ def free_accesses_from_thread(self, thread: ThreadProto) -> None:
70
+ for signal in self.signals.values():
71
+ signal.free_access_from_thread(thread)
@@ -0,0 +1,136 @@
1
+ """Modifiers can be applied to Renderables."""
2
+
3
+ from typing import Generic, Literal, Optional, Set, TypeVar, Union
4
+
5
+ from nortl.core.operations import OperationTrait
6
+ from nortl.core.protocols import ACCESS_CHECKS, SIGNAL_ACCESS_CHECKS, AnySignal, AssignmentTarget, EngineProto, Renderable
7
+
8
+ __all__ = [
9
+ 'UnregisteredRead',
10
+ 'Volatile',
11
+ ]
12
+ T_Content = TypeVar('T_Content', bound=Renderable)
13
+ T_Signal = TypeVar('T_Signal', bound=AnySignal)
14
+
15
+
16
+ # Modifiers
17
+ class BaseModifier(Generic[T_Content], OperationTrait):
18
+ """Baseclass for Modifiers."""
19
+
20
+ def __init__(self, content: T_Content) -> None:
21
+ """Initialize an alias.
22
+
23
+ Arguments:
24
+ content: An operation result.
25
+ """
26
+ self._content = content
27
+ super().__init__()
28
+
29
+ @property
30
+ def content(self) -> T_Content:
31
+ """Content of the alias."""
32
+ return self._content
33
+
34
+ # Implement OperationTrait
35
+ @property
36
+ def is_primitive(self) -> bool:
37
+ """Indicates if this object is a Verilog primitive."""
38
+ return self.content.is_primitive
39
+
40
+ @property
41
+ def operand_width(self) -> Optional[int]:
42
+ """Indicates the width when used as an operand, equal to the width of the alias operation.
43
+
44
+ A width of None means that the width is not fixed during execution of noRTL.
45
+ """
46
+ return self.content.operand_width
47
+
48
+ def render(self, target: Optional[str] = None) -> str:
49
+ """Render operation to target language.
50
+
51
+ Arguments:
52
+ target: Target language.
53
+ """
54
+ return self.content.render(target=target)
55
+
56
+ def __format__(self, format_spec: str) -> str:
57
+ return self.render()
58
+
59
+ # TODO Modifier must only be valid within current construct
60
+
61
+
62
+ class UnregisteredRead(Generic[T_Content], BaseModifier[T_Content]):
63
+ """Modifier for signals or operations, that hides them from read accesses.
64
+
65
+ Any read access to signals inside a UnregisteredRead modifier is not registered.
66
+ Note that this does not permanently disable the access checks, when acessing the signals normally.
67
+
68
+ !!! danger
69
+ This modifier is meant for internal purposes. For example, it is used in the noRTL test wrapper for assertions.
70
+ It must not be used in production code!
71
+ """
72
+
73
+ def read_access(self, ignore: Set[ACCESS_CHECKS] = set()) -> None:
74
+ """Register read access from the current thread, state and construct depth.
75
+
76
+ Does nothing for UnregisteredRead.
77
+ """
78
+
79
+
80
+ class Volatile(Generic[T_Signal], BaseModifier[T_Signal]):
81
+ """Modifier for signals, allowing them to be shared between threads.
82
+
83
+ Wrapping a noRTL signal will temporarily ignore the following checks by default:
84
+
85
+ - Exclusive Read Check ([ExclusiveReadError][nortl.core.exceptions.ExclusiveReadError]): The modified object can be read from multiple threads.
86
+ - Non-Identical Read/Write Check ([NonIdenticalRWError][nortl.core.exceptions.NonIdenticalRWError]): The modified object can be read/written from different threads.
87
+
88
+ It's possible to select other checks to ignore, by listing their names, e.g.:
89
+
90
+ ```python
91
+ # Ignore a set of checks
92
+ unsafe_signal = Volatile(signal, 'identical_rw', 'exclusive_write')
93
+
94
+ # Ignore a single check
95
+ unsafe_signal = Volatile(signal, 'identical_rw')
96
+ ```
97
+
98
+ Note that this does not permanently disable the access checks. The read or write access to the signal is still registered.
99
+ If the same signal is used in other places, it may still cause access violations exceptions.
100
+ """
101
+
102
+ def __init__(self, content: T_Signal, *ignore: SIGNAL_ACCESS_CHECKS) -> None:
103
+ super().__init__(content)
104
+ if len(ignore) > 0:
105
+ self._ignore: Set[SIGNAL_ACCESS_CHECKS] = set(ignore)
106
+ else:
107
+ self._ignore = {'exclusive_read', 'identical_rw'}
108
+
109
+ @property
110
+ def ignore(self) -> Set[SIGNAL_ACCESS_CHECKS]:
111
+ """Set of ignored access checks."""
112
+ return self._ignore
113
+
114
+ # Implement OperationTrait
115
+ def read_access(self, ignore: Set[ACCESS_CHECKS] = set()) -> None:
116
+ """Register read access from the current thread, state and construct depth."""
117
+ self.content.read_access(ignore=ignore | self.ignore)
118
+
119
+ # Implement AssignmentTarget
120
+ @property
121
+ def name(self) -> str:
122
+ """Just return name."""
123
+ return self.content.name
124
+
125
+ @property
126
+ def engine(self) -> EngineProto:
127
+ """NoRTL engine that the signal belongs to."""
128
+ return self.content.engine
129
+
130
+ def write_access(self, ignore: Set[ACCESS_CHECKS] = set()) -> None:
131
+ """Register write access from the current thread."""
132
+ self.content.write_access(ignore=ignore | self.ignore)
133
+
134
+ def overlaps_with(self, other: AssignmentTarget) -> Union[bool, Literal['partial']]:
135
+ """Check if signal overlaps with other signal or signal slice."""
136
+ return self.content.overlaps_with(other)
nortl/core/module.py ADDED
@@ -0,0 +1,181 @@
1
+ from typing import Dict, Final, List, Optional, Union
2
+
3
+ from .operations import Const
4
+ from .protocols import ModuleProto, ParameterProto, PermanentSignal, Renderable
5
+
6
+
7
+ class Module:
8
+ """Representation of a Verilog module."""
9
+
10
+ def __init__(self, name: str, hdl_code: str = '') -> None:
11
+ """Initialize a new Verilog module.
12
+
13
+ Arguments:
14
+ name: Name of the module.
15
+ hdl_code: Verilog/VHDL code of the module that should be included in the output.
16
+ """
17
+ self._name: str = name
18
+ self._port_names: List[str] = []
19
+ self._parameters: Dict[str, int] = {}
20
+ self._hdl_code: str = hdl_code
21
+
22
+ # The following variable is used by the verilog renderer and masks out the connections of clk and reset
23
+ # Should be handled with care! Therefore this will not be exposed
24
+ self._ignore_clk_rst_connection: bool = False
25
+
26
+ # This variable is used for synthesizing the clock network in the final module.
27
+ # The verilog renderer will ignore this signal, if it is set to 'None', otherwise, it will
28
+ # create and connect a signal and wire it up to the enable of the clock gate of the top module.
29
+ self._clk_request_port: Union[None, str] = None
30
+
31
+ @property
32
+ def name(self) -> str:
33
+ """Module name."""
34
+ return self._name
35
+
36
+ @property
37
+ def ports(self) -> List[str]:
38
+ """List of ports for this module."""
39
+ return self._port_names
40
+
41
+ @property
42
+ def parameters(self) -> Dict[str, int]:
43
+ """Dictionary of parameter names and their values."""
44
+ return self._parameters
45
+
46
+ @property
47
+ def hdl_code(self) -> str:
48
+ """HDL Code of the module."""
49
+ return self._hdl_code
50
+
51
+ def add_port(self, port_name: str) -> None:
52
+ """Add a new port to the module.
53
+
54
+ Arguments:
55
+ port_name (str): The signal representing the port.
56
+ """
57
+ self._port_names.append(port_name)
58
+
59
+ def has_port(self, port_name: str) -> bool:
60
+ """Test, if a given port (identivfied by name) is in this module.
61
+
62
+ Arguments:
63
+ port_name (str): Port name to be checked
64
+
65
+ Returns:
66
+ bool: True, if port is in module
67
+ """
68
+ return port_name in self._port_names
69
+
70
+ def add_parameter(self, name: str, value: int) -> None:
71
+ """Add a new parameter to the module.
72
+
73
+ Arguments:
74
+ name: Name of the parameter.
75
+ value: Value of the parameter.
76
+ """
77
+ self._parameters[name] = value
78
+
79
+ def set_clk_request(self, port_name: str) -> None:
80
+ """Set the clock request port for the rendering process.
81
+
82
+ Arguments:
83
+ port_name (str): The name of the clock request port.
84
+
85
+ Note that this method should be called after all ports have been added.
86
+ """
87
+ if port_name in self._port_names:
88
+ self._clk_request_port = port_name
89
+ else:
90
+ raise ValueError(f'Port {port_name} not found in module ports.')
91
+
92
+ @property
93
+ def clk_request_port(self) -> Optional[str]:
94
+ """Get the clock request port for the rendering process.
95
+
96
+ Returns:
97
+ Optional[str]: The name of the clock request port or None if not set.
98
+ """
99
+ return self._clk_request_port
100
+
101
+
102
+ class ModuleInstance:
103
+ """Representation of an instance of a Verilog module."""
104
+
105
+ is_primitive: Final = False
106
+
107
+ def __init__(self, module: ModuleProto, name: str, clock_gating: bool = False) -> None:
108
+ """Initialize a new instance of a Verilog module.
109
+
110
+ Arguments:
111
+ module: The module to be instantiated.
112
+ name: Name of the instance.
113
+ clock_gating: If the clock is gated if the current state demands it (and clock gating is enabled in renderer)
114
+ """
115
+ self._module = module
116
+ self._name = name
117
+ self._port_connections: Dict[str, PermanentSignal] = {} # Use signal name as key
118
+ self._parameter_overrides: Dict[str, Union[Const, ParameterProto, Renderable]] = {}
119
+ self._clock_gating = clock_gating
120
+
121
+ if self._module.clk_request_port is not None:
122
+ self._clock_gating = True
123
+
124
+ @property
125
+ def module(self) -> ModuleProto:
126
+ """The module being instantiated."""
127
+ return self._module
128
+
129
+ @property
130
+ def name(self) -> str:
131
+ """Name of the instance."""
132
+ return self._name
133
+
134
+ @property
135
+ def port_connections(self) -> Dict[str, PermanentSignal]:
136
+ """Connections between the ports and signals."""
137
+ return self._port_connections
138
+
139
+ @property
140
+ def parameter_overrides(self) -> Dict[str, Union[Const, ParameterProto, Renderable]]:
141
+ """Dictionary of overridden parameter names and their values."""
142
+ return self._parameter_overrides
143
+
144
+ def connect_port(self, port_name: str, signal: PermanentSignal) -> None:
145
+ """Connect a port of the instance to a signal.
146
+
147
+ Arguments:
148
+ port_name: The port being connected.
149
+ signal: The signal it is being connected to.
150
+ """
151
+ if port_name not in self.module.ports:
152
+ raise ValueError(f'Port {port_name} does not exist in module {self.module.name}')
153
+
154
+ self._port_connections[port_name] = signal # Use signal name as key
155
+
156
+ def get_connected_signal(self, port_name: str) -> PermanentSignal:
157
+ """Returns the signal that is connected to the given port.
158
+
159
+ Arguments:
160
+ port_name (str): The port where the connected signal should be acquired
161
+ """
162
+
163
+ if port_name not in self.module.ports:
164
+ raise ValueError(f'Port {port_name} does not exist in module {self.module.name}')
165
+
166
+ return self._port_connections[port_name]
167
+
168
+ def override_parameter(self, name: str, value: Union[int, ParameterProto, Renderable]) -> None:
169
+ """Override a parameter of the instance.
170
+
171
+ Arguments:
172
+ name: Name of the parameter to be overridden.
173
+ value: New value for the parameter.
174
+ """
175
+ if name not in self.module.parameters:
176
+ raise ValueError(f'Parameter {name} does not exist in module {self.module.name}')
177
+
178
+ if isinstance(value, int):
179
+ self._parameter_overrides[name] = Const(value)
180
+ else:
181
+ self._parameter_overrides[name] = value