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/__init__.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""noRTL Code Generation Engine."""
|
|
2
|
+
|
|
3
|
+
from typing import Union
|
|
4
|
+
|
|
5
|
+
from nortl import verilog_library
|
|
6
|
+
from nortl.components import Channel, ElasticChannel, Timer
|
|
7
|
+
from nortl.core import All, Any, Concat, Const, CoreEngine, IfThenElse, Var, Volatile
|
|
8
|
+
from nortl.core.constructs import Condition, ElseCondition, Fork, ForLoop, WhileLoop
|
|
9
|
+
from nortl.core.protocols import ParameterProto, Renderable
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
'All',
|
|
13
|
+
'Any',
|
|
14
|
+
'Concat',
|
|
15
|
+
'Const',
|
|
16
|
+
'CoreEngine',
|
|
17
|
+
'Engine',
|
|
18
|
+
'IfThenElse',
|
|
19
|
+
'Var',
|
|
20
|
+
'Volatile',
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ComponentsMixin(CoreEngine):
|
|
25
|
+
"""Mixin class providing noRTL Components.
|
|
26
|
+
|
|
27
|
+
This allows easier access to the components, without needing to import them.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def create_channel(self, width: int, name: str = 'channel') -> Channel:
|
|
31
|
+
"""Create a channel."""
|
|
32
|
+
return Channel(self, width, name=name)
|
|
33
|
+
|
|
34
|
+
def create_elastic_channel(self, width: int, name: str = 'channel') -> ElasticChannel:
|
|
35
|
+
"""Create an elastic channel."""
|
|
36
|
+
return ElasticChannel(self, width, name=name)
|
|
37
|
+
|
|
38
|
+
def create_timer(self, width: Union[int, ParameterProto] = 16, instance_name_prefix: str = 'I_TIMER', clock_gating: bool = False) -> Timer:
|
|
39
|
+
"""Create a timer."""
|
|
40
|
+
return Timer(self, width=width, instance_name_prefix=instance_name_prefix, clock_gating=clock_gating)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ConstructsMixin(CoreEngine):
|
|
44
|
+
"""Mixin class providing noRTL Constructs.
|
|
45
|
+
|
|
46
|
+
This allows easier access to the flow control constructs, without needing to import them.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def condition(self, condition: Renderable) -> Condition:
|
|
50
|
+
"""Adds a Condition."""
|
|
51
|
+
return Condition(self, condition)
|
|
52
|
+
|
|
53
|
+
def else_condition(self) -> ElseCondition:
|
|
54
|
+
"""Adds a Else Condition."""
|
|
55
|
+
return ElseCondition(self)
|
|
56
|
+
|
|
57
|
+
def fork(self, threadname: str) -> Fork:
|
|
58
|
+
"""Adds a Fork."""
|
|
59
|
+
return Fork(self, threadname)
|
|
60
|
+
|
|
61
|
+
def for_loop(
|
|
62
|
+
self, start: Union[Renderable, int], stop: Union[Renderable, int], step: Union[Renderable, int] = Const(1), counter_width: int = 16
|
|
63
|
+
) -> ForLoop:
|
|
64
|
+
"""Adds a For loop."""
|
|
65
|
+
return ForLoop(self, start, stop, step=step, counter_width=counter_width)
|
|
66
|
+
|
|
67
|
+
def while_loop(self, condition: Renderable) -> WhileLoop:
|
|
68
|
+
"""Adds a While loop."""
|
|
69
|
+
return WhileLoop(self, condition)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class Engine(ComponentsMixin, ConstructsMixin, CoreEngine):
|
|
73
|
+
"""noRTL Engine."""
|
|
74
|
+
|
|
75
|
+
def __init__(self, module_name: str, reset_state_name: str = 'IDLE') -> None:
|
|
76
|
+
"""Initialize a new noRTL Engine.
|
|
77
|
+
|
|
78
|
+
Arguments:
|
|
79
|
+
module_name: Name of the resulting SystemVerilog module.
|
|
80
|
+
reset_state_name: Default name for the reset state.
|
|
81
|
+
"""
|
|
82
|
+
super().__init__(module_name, reset_state_name)
|
|
83
|
+
|
|
84
|
+
for module in verilog_library.get_modules():
|
|
85
|
+
self.add_module(module)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from typing import List, Sequence
|
|
3
|
+
|
|
4
|
+
from nortl.core import Concat, Const, IfThenElse, Volatile
|
|
5
|
+
from nortl.core.operations import to_renderable
|
|
6
|
+
from nortl.core.protocols import EngineProto, Renderable, ScratchSignalProto, SignalProto
|
|
7
|
+
|
|
8
|
+
# FIXME: Create Abstract base class for Channel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Channel:
|
|
12
|
+
"""A simple Channel with a ready/valid handshake to send data from one thread to another.
|
|
13
|
+
|
|
14
|
+
The send and rec functions realize a blocking transfer. This channel cannot be used for a elastic pipeline!
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, engine: EngineProto, width: int, name: str = 'channel') -> None:
|
|
18
|
+
self.engine = engine
|
|
19
|
+
self.width = width
|
|
20
|
+
|
|
21
|
+
# FIXME validate if this channel already exists!
|
|
22
|
+
# Maybe turn into named entity
|
|
23
|
+
|
|
24
|
+
# All our signals cross the thread boundaries, so we have non-id rw access
|
|
25
|
+
self.ready = Volatile(self.engine.define_local(f'channel_{name}_ready', 1, 0), 'identical_rw')
|
|
26
|
+
self.valid = Volatile(self.engine.define_local(f'channel_{name}_valid', 1, 0), 'identical_rw')
|
|
27
|
+
self.data = Volatile(self.engine.define_local(f'channel_{name}_data', width, 0), 'identical_rw')
|
|
28
|
+
|
|
29
|
+
def send(self, data: Renderable | int) -> None:
|
|
30
|
+
"""Sends the data over the channel to the target. The execution is blocked until the data has been received."""
|
|
31
|
+
tx_data = to_renderable(data)
|
|
32
|
+
self.engine.set(self.data, tx_data)
|
|
33
|
+
self.engine.set(self.valid, 1)
|
|
34
|
+
self.engine.wait_for(self.ready == 1)
|
|
35
|
+
self.engine.set(self.valid, 0)
|
|
36
|
+
self.engine.set(self.data, 0)
|
|
37
|
+
self.engine.sync()
|
|
38
|
+
|
|
39
|
+
def receive(self) -> ScratchSignalProto:
|
|
40
|
+
"""Fetches a data item from the channel. The execution is blocked until the data has been received."""
|
|
41
|
+
target = self.engine.define_scratch(self.width)
|
|
42
|
+
self.engine.set(self.ready, 1)
|
|
43
|
+
self.engine.wait_for(self.valid == 1)
|
|
44
|
+
self.engine.set(target, self.data)
|
|
45
|
+
self.engine.set(self.ready, 0)
|
|
46
|
+
self.engine.sync()
|
|
47
|
+
return target
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ElasticChannel:
|
|
51
|
+
"""A Channel with a FIFO for rate matching."""
|
|
52
|
+
|
|
53
|
+
def __init__(self, engine: EngineProto, width: int, name: str = 'channel', depth: int = 16) -> None:
|
|
54
|
+
ptr_width = math.ceil(math.log2(depth))
|
|
55
|
+
|
|
56
|
+
if not ptr_width.is_integer():
|
|
57
|
+
# FIXME this does not work
|
|
58
|
+
raise ValueError('The depth of a FIFO in an ElasticChannel must be 2**N!')
|
|
59
|
+
|
|
60
|
+
self.name = name
|
|
61
|
+
|
|
62
|
+
self.engine = engine
|
|
63
|
+
self.width = width
|
|
64
|
+
self.depth = depth
|
|
65
|
+
|
|
66
|
+
self.read_ptr = Volatile(self.engine.define_local(f'channel_{name}_readctr', ptr_width, 0))
|
|
67
|
+
self.write_ptr = Volatile(self.engine.define_local(f'channel_{name}_writectr', ptr_width, 0))
|
|
68
|
+
|
|
69
|
+
self.level = ((Concat('0b0', self.write_ptr) + self.depth) - self.read_ptr) % self.depth
|
|
70
|
+
|
|
71
|
+
# Create data store
|
|
72
|
+
self.data: List[Volatile[SignalProto]] = []
|
|
73
|
+
for i in range(depth):
|
|
74
|
+
data_item = Volatile(self.engine.define_local(f'channel_{name}_data_{i}', width, 0))
|
|
75
|
+
self.data.append(data_item)
|
|
76
|
+
|
|
77
|
+
def send(self, data: Renderable | int) -> None:
|
|
78
|
+
"""Sends the data over the channel to the target. The execution is blocked until the data has been sent."""
|
|
79
|
+
if isinstance(data, int):
|
|
80
|
+
tx_data: Renderable = Const(data, self.width)
|
|
81
|
+
else:
|
|
82
|
+
tx_data = to_renderable(data)
|
|
83
|
+
|
|
84
|
+
self.engine.wait_for(self.level < (self.depth - 2))
|
|
85
|
+
|
|
86
|
+
for i in range(self.depth):
|
|
87
|
+
self.engine.set(self.data[i], IfThenElse(self.write_ptr == i, tx_data, self.data[i]))
|
|
88
|
+
|
|
89
|
+
self.engine.set(self.write_ptr, self.write_ptr + 1)
|
|
90
|
+
self.engine.sync()
|
|
91
|
+
|
|
92
|
+
def send_multiple(self, data_lst: List[Renderable | int]) -> None:
|
|
93
|
+
"""Sends several data items over the channel to the target. The execution is blocked until the data has been sent.
|
|
94
|
+
|
|
95
|
+
Note that the list order is MSB first!
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
if (length := len(data_lst)) > self.depth - 2:
|
|
99
|
+
raise RuntimeError('send_multiple expects the item list to be smaller than (Channel.depth - 2)!')
|
|
100
|
+
|
|
101
|
+
tx_data: Sequence[Renderable] = tuple(Const(item, self.width) if isinstance(item, int) else to_renderable(item) for item in data_lst)
|
|
102
|
+
|
|
103
|
+
self.engine.wait_for(self.level < (self.depth - 1 - length))
|
|
104
|
+
|
|
105
|
+
tmp_data: List[Renderable] = [i for i in self.data]
|
|
106
|
+
|
|
107
|
+
for j, item in enumerate(tx_data[::-1]):
|
|
108
|
+
for i in range(self.depth):
|
|
109
|
+
tmp_data[i] = IfThenElse(((self.write_ptr + j) % self.depth) == (i), item, tmp_data[i])
|
|
110
|
+
|
|
111
|
+
for i in range(self.depth):
|
|
112
|
+
self.engine.set(self.data[i], tmp_data[i])
|
|
113
|
+
|
|
114
|
+
self.engine.sync()
|
|
115
|
+
self.engine.set(self.write_ptr, self.write_ptr + length)
|
|
116
|
+
|
|
117
|
+
self.engine.sync()
|
|
118
|
+
|
|
119
|
+
def receive(self) -> ScratchSignalProto:
|
|
120
|
+
"""Fetches a data item from the channel. The execution is blocked until the data has been received."""
|
|
121
|
+
target = self.engine.define_scratch(self.width)
|
|
122
|
+
self.engine.wait_for(self.level != 0)
|
|
123
|
+
|
|
124
|
+
res: Renderable = Const(0, self.width)
|
|
125
|
+
|
|
126
|
+
for i in range(self.depth):
|
|
127
|
+
res = IfThenElse(self.read_ptr == i, self.data[i], res)
|
|
128
|
+
|
|
129
|
+
self.engine.set(target, res)
|
|
130
|
+
self.engine.set(self.read_ptr, self.read_ptr + 1)
|
|
131
|
+
self.engine.sync()
|
|
132
|
+
return target
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
from nortl.core.protocols import EngineProto, ParameterProto, Renderable
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Timer:
|
|
7
|
+
"""This class represents a timer in the engine. It provides functions for operating the timer conveniently.
|
|
8
|
+
|
|
9
|
+
It depends on the availability of a verilog module `nortl_count_down_timer`
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self, engine: EngineProto, width: Union[int, ParameterProto] = 16, instance_name_prefix: str = 'I_TIMER', clock_gating: bool = False
|
|
14
|
+
) -> None:
|
|
15
|
+
"""Initializes a timer object in the engine.
|
|
16
|
+
|
|
17
|
+
This function instantiates a (count-down) timer module in the engine and holds the signals for the delay, reload and finish / zero.
|
|
18
|
+
|
|
19
|
+
Arguments:
|
|
20
|
+
engine (CoreEngine): Target engine object
|
|
21
|
+
width (int): width of the counter register
|
|
22
|
+
instance_name_prefix (str): Prefix for the instance name used in verilog. This also prefixes the names of the connected signals
|
|
23
|
+
clock_gating (bool): If true, the unit is connected to the gated clock. If clock gating is disable, this setting is ignored.
|
|
24
|
+
"""
|
|
25
|
+
self.engine = engine
|
|
26
|
+
|
|
27
|
+
timer_idx = 0
|
|
28
|
+
while (instance_name := f'{instance_name_prefix}_{timer_idx}') in engine.module_instances:
|
|
29
|
+
timer_idx = timer_idx + 1
|
|
30
|
+
|
|
31
|
+
self.instance_name = instance_name
|
|
32
|
+
|
|
33
|
+
self.timer_module = self.engine.create_module_instance('nortl_count_down_timer', self.instance_name, clock_gating)
|
|
34
|
+
|
|
35
|
+
self.engine.override_module_parameter(self.instance_name, 'DATA_WIDTH', width)
|
|
36
|
+
|
|
37
|
+
self.delay = self.engine.define_local(f'{self.instance_name}_delay', reset_value=0, width=width) # Todo: Could be an async signal
|
|
38
|
+
self.reload = self.engine.define_local(f'{self.instance_name}_reload', reset_value=0) # Todo: Could be an async signal
|
|
39
|
+
self.zero = self.engine.define_local(f'{self.instance_name}_zero') # Todo: Could be an async signal
|
|
40
|
+
|
|
41
|
+
self.engine.connect_module_port(self.instance_name, 'DELAY', self.delay)
|
|
42
|
+
self.engine.connect_module_port(self.instance_name, 'RELOAD', self.reload)
|
|
43
|
+
self.engine.connect_module_port(self.instance_name, 'ZERO', self.zero)
|
|
44
|
+
|
|
45
|
+
def wait_delay(self, delay: Union[Renderable, int, bool]) -> None:
|
|
46
|
+
"""Wait for a given delay without returning control to the engine. This can be seen as blocking delay.
|
|
47
|
+
|
|
48
|
+
Arguments:
|
|
49
|
+
delay (Renderable): Delay to wait for (in cycles)
|
|
50
|
+
"""
|
|
51
|
+
self.start_delay(delay)
|
|
52
|
+
self.engine.wait_for(self.finished)
|
|
53
|
+
|
|
54
|
+
def start_delay(self, delay: Union[Renderable, int, bool]) -> None:
|
|
55
|
+
"""Start the timer with a given delay. This acts as a non-blocking delay and therefore returns control after starting the timer.
|
|
56
|
+
|
|
57
|
+
Arguments:
|
|
58
|
+
delay (Renderable): Delay to wait for (in cycles)
|
|
59
|
+
"""
|
|
60
|
+
self.engine.set(self.delay, delay)
|
|
61
|
+
self.engine.set(self.reload, 1)
|
|
62
|
+
self.engine.sync()
|
|
63
|
+
self.engine.set(self.reload, 0)
|
|
64
|
+
self.engine.sync()
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def finished(self) -> Renderable:
|
|
68
|
+
"""Returns the signal (Renderable) that signals, if the timer has finished, i.e. the counter register is zero.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Renderable: Zero-Flag of the counter
|
|
72
|
+
"""
|
|
73
|
+
return self.zero
|
nortl/core/__init__.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from .engine import CoreEngine
|
|
2
|
+
from .exceptions import (
|
|
3
|
+
ConflictingAssignmentError,
|
|
4
|
+
ExclusiveReadError,
|
|
5
|
+
ExclusiveWriteError,
|
|
6
|
+
ForbiddenAssignmentError,
|
|
7
|
+
NonIdenticalRWError,
|
|
8
|
+
OwnershipError,
|
|
9
|
+
TransitionLockError,
|
|
10
|
+
TransitionRestrictionError,
|
|
11
|
+
UnfinishedForwardDeclarationError,
|
|
12
|
+
WriteViolationError,
|
|
13
|
+
)
|
|
14
|
+
from .modifiers import Volatile
|
|
15
|
+
from .operations import All, Any, Concat, Const, IfThenElse, Var
|
|
16
|
+
from .parameter import Parameter
|
|
17
|
+
from .signal import Signal
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
'All',
|
|
21
|
+
'Any',
|
|
22
|
+
'Concat',
|
|
23
|
+
'ConflictingAssignmentError',
|
|
24
|
+
'Const',
|
|
25
|
+
'CoreEngine',
|
|
26
|
+
'ExclusiveReadError',
|
|
27
|
+
'ExclusiveWriteError',
|
|
28
|
+
'ForbiddenAssignmentError',
|
|
29
|
+
'IfThenElse',
|
|
30
|
+
'NonIdenticalRWError',
|
|
31
|
+
'OwnershipError',
|
|
32
|
+
'Parameter',
|
|
33
|
+
'Signal',
|
|
34
|
+
'TransitionLockError',
|
|
35
|
+
'TransitionRestrictionError',
|
|
36
|
+
'UnfinishedForwardDeclarationError',
|
|
37
|
+
'Var',
|
|
38
|
+
'Volatile',
|
|
39
|
+
'WriteViolationError',
|
|
40
|
+
]
|
nortl/core/checker.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from logging import getLogger
|
|
2
|
+
from typing import Callable, ClassVar, Dict, List, Literal, Protocol, Set, Type
|
|
3
|
+
from warnings import warn
|
|
4
|
+
|
|
5
|
+
from .exceptions import ExclusiveReadError, ExclusiveWriteError, NonIdenticalRWError
|
|
6
|
+
from .protocols import ACCESS_CHECKS, SIGNAL_ACCESS_CHECKS, StaticAccessProto
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
'StaticAccessChecker',
|
|
10
|
+
'set_severity_level',
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
logger = getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
T_SEVERITY_LEVEL = Literal['raise', 'warn', 'log', 'suppress']
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def set_severity_level(level: T_SEVERITY_LEVEL) -> None:
|
|
20
|
+
"""Set severity level for noRTL access checks."""
|
|
21
|
+
BaseChecker.severity = level
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AccessControlledSignal(Protocol):
|
|
25
|
+
"""Minimal protocol for access controlled signals.
|
|
26
|
+
|
|
27
|
+
This supports both real signals and Volatile modifiers.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def name(self) -> str: ...
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def read_accesses(self) -> Set[StaticAccessProto]: ...
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def write_accesses(self) -> Set[StaticAccessProto]: ...
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class BaseChecker:
|
|
41
|
+
"""Baseclass for checkers."""
|
|
42
|
+
|
|
43
|
+
severity: ClassVar[T_SEVERITY_LEVEL] = 'raise'
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def throw(exception_type: Type[Exception], msg: str) -> None:
|
|
47
|
+
"""Throw exception."""
|
|
48
|
+
match BaseChecker.severity:
|
|
49
|
+
case 'raise':
|
|
50
|
+
raise exception_type(msg)
|
|
51
|
+
case 'warn':
|
|
52
|
+
warn(msg, stacklevel=2)
|
|
53
|
+
case 'log':
|
|
54
|
+
logger.error(f'[{exception_type.__name__}] {msg}', stacklevel=2, stack_info=True)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class StaticAccessChecker(BaseChecker):
|
|
58
|
+
"""Static access checker for signals."""
|
|
59
|
+
|
|
60
|
+
# FIXME: Adapt traceback of the checks to show position in the actual user-code where the exception happened
|
|
61
|
+
|
|
62
|
+
def __init__(self, signal: AccessControlledSignal) -> None:
|
|
63
|
+
self.signal = signal
|
|
64
|
+
self.enabled_checks: List[SIGNAL_ACCESS_CHECKS] = ['exclusive_read', 'exclusive_write', 'identical_rw']
|
|
65
|
+
self._cached_reading_thread_names: Set[str] = set()
|
|
66
|
+
self._cached_writing_thread_names: Set[str] = set()
|
|
67
|
+
|
|
68
|
+
self.check_mapping: Dict[ACCESS_CHECKS, Callable[[], None]] = {
|
|
69
|
+
'exclusive_read': self.check_exclusive_read,
|
|
70
|
+
'exclusive_write': self.check_exclusive_write,
|
|
71
|
+
'identical_rw': self.check_identical_rw,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def reading_thread_names(self) -> Set[str]:
|
|
76
|
+
if len(self._cached_reading_thread_names) != 0:
|
|
77
|
+
return self._cached_reading_thread_names
|
|
78
|
+
|
|
79
|
+
threadnames: Set[str] = set()
|
|
80
|
+
for access in self.signal.read_accesses:
|
|
81
|
+
if access.active and access.thread.running:
|
|
82
|
+
threadnames.add(f'{access.thread.worker.name}.{access.thread.name}')
|
|
83
|
+
|
|
84
|
+
self._cached_reading_thread_names = threadnames
|
|
85
|
+
|
|
86
|
+
return threadnames
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def writing_thread_names(self) -> Set[str]:
|
|
90
|
+
if len(self._cached_writing_thread_names) != 0:
|
|
91
|
+
return self._cached_writing_thread_names
|
|
92
|
+
|
|
93
|
+
threadnames: Set[str] = set()
|
|
94
|
+
for access in self.signal.write_accesses:
|
|
95
|
+
if access.active and access.thread.running:
|
|
96
|
+
threadnames.add(f'{access.thread.worker.name}.{access.thread.name}')
|
|
97
|
+
|
|
98
|
+
self._cached_writing_thread_names = threadnames
|
|
99
|
+
|
|
100
|
+
return threadnames
|
|
101
|
+
|
|
102
|
+
def disable_check(self, check: SIGNAL_ACCESS_CHECKS) -> None:
|
|
103
|
+
self.enabled_checks.remove(check)
|
|
104
|
+
|
|
105
|
+
def check(self, ignore: Set[ACCESS_CHECKS] = set()) -> None:
|
|
106
|
+
"""This function executes all checks and raises errors, if needed."""
|
|
107
|
+
|
|
108
|
+
# Clear out caches
|
|
109
|
+
self._cached_reading_thread_names = set()
|
|
110
|
+
self._cached_writing_thread_names = set()
|
|
111
|
+
|
|
112
|
+
# Actual Check
|
|
113
|
+
for check in self.enabled_checks:
|
|
114
|
+
if check not in ignore:
|
|
115
|
+
self.check_mapping[check]()
|
|
116
|
+
|
|
117
|
+
def check_exclusive_read(self) -> None:
|
|
118
|
+
if len(self.reading_thread_names) > 1:
|
|
119
|
+
self.throw(
|
|
120
|
+
ExclusiveReadError, f'Signal {self.signal} has been read by more than one thread! Reading Threads: {self.reading_thread_names}'
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def check_exclusive_write(self) -> None:
|
|
124
|
+
if len(self.writing_thread_names) > 1:
|
|
125
|
+
self.throw(
|
|
126
|
+
ExclusiveWriteError, f'Signal {self.signal} has been written by more than one thread! Writing Threads: {self.writing_thread_names}'
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def check_identical_rw(self) -> None:
|
|
130
|
+
if len(self.writing_thread_names) == 0 or len(self.reading_thread_names) == 0:
|
|
131
|
+
pass
|
|
132
|
+
elif self.writing_thread_names != self.reading_thread_names:
|
|
133
|
+
self.throw(
|
|
134
|
+
NonIdenticalRWError, f'Signal {self.signal} has been written by {self.writing_thread_names} and read by {self.reading_thread_names}.'
|
|
135
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from nortl.core.protocols import ThreadProto
|
|
2
|
+
|
|
3
|
+
from .debug import DebugEntity
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class StaticAccess(DebugEntity):
|
|
7
|
+
"""Model for an access to a signal.
|
|
8
|
+
|
|
9
|
+
This class is used as a container to hold references to the thread that caused the access and information about what caused the access where in the code.
|
|
10
|
+
The signal object can then hold a list (or deque) of accesses for executing checks for e.g. two parallel-running threads accessing the register.
|
|
11
|
+
|
|
12
|
+
Note that this does not include checks like access counters that verify,
|
|
13
|
+
that the number of reads is equal to the number of writes so that no data gets lost.
|
|
14
|
+
This structure is only used for detecting concurrent accesses at assemble-time of the noRTL engine (where the code assembles the internal noRTL engine model).
|
|
15
|
+
Realizing access counters is a task to be performed at other levels of abstraction, e.g. in the rendered Verilog code where also formal properties
|
|
16
|
+
can be included for verification in simulation.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, thread: ThreadProto) -> None:
|
|
20
|
+
super().__init__()
|
|
21
|
+
self.thread = thread
|
|
22
|
+
self.active = True
|
|
23
|
+
|
|
24
|
+
def disable(self) -> None:
|
|
25
|
+
self.active = False
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from typing import Any, Dict
|
|
2
|
+
|
|
3
|
+
from .debug import DebugEntity
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class NamedEntity(DebugEntity):
|
|
7
|
+
"""Model a named something in Verilog and implement the feature to add metadata.
|
|
8
|
+
|
|
9
|
+
This class will (in future) ensure that there are no naming collisions since all names of verilog objects will be stored here.
|
|
10
|
+
To add traceability, this class inherits from DebugEntity that stores the current execution trace.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, name: str) -> None:
|
|
14
|
+
super().__init__()
|
|
15
|
+
self._metadata: Dict[str, Any] = {}
|
|
16
|
+
self._name = name
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def name(self) -> str:
|
|
20
|
+
"""Just return name."""
|
|
21
|
+
return self._name
|
|
22
|
+
|
|
23
|
+
def get_metadata(self, key: str, default: Any = None) -> Any:
|
|
24
|
+
"""Get metadata item."""
|
|
25
|
+
return self._metadata.get(key, default)
|
|
26
|
+
|
|
27
|
+
def set_metadata(self, key: str, value: Any) -> None:
|
|
28
|
+
"""Set metadata item."""
|
|
29
|
+
self._metadata[key] = value
|
|
30
|
+
|
|
31
|
+
def has_metadata(self, key: str) -> bool:
|
|
32
|
+
"""Test if a certain key is present in metadata."""
|
|
33
|
+
return key in self._metadata
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""This module will contain helpers for writing sub-engines and wrapping existing functions outside of the core."""
|
|
2
|
+
|
|
3
|
+
from .condition import Condition, ElseCondition
|
|
4
|
+
from .fork_join import Fork
|
|
5
|
+
from .loop import ForLoop, WhileLoop
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
'Condition',
|
|
9
|
+
'ElseCondition',
|
|
10
|
+
'ForLoop',
|
|
11
|
+
'Fork',
|
|
12
|
+
'WhileLoop',
|
|
13
|
+
]
|