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/exceptions.py
ADDED
|
@@ -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,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)
|
nortl/core/modifiers.py
ADDED
|
@@ -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
|