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/state.py
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""Describes engine states as Python object."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List, Optional, Sequence, Set, Tuple
|
|
4
|
+
|
|
5
|
+
from typing_extensions import Self
|
|
6
|
+
|
|
7
|
+
from nortl.core.exceptions import ConflictingAssignmentError, ForbiddenAssignmentError, TransitionLockError, TransitionRestrictionError
|
|
8
|
+
|
|
9
|
+
from .common import NamedEntity
|
|
10
|
+
from .protocols import AssignmentTarget, EngineProto, Renderable, StateProto, WorkerProto
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
'State',
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class State(NamedEntity):
|
|
18
|
+
"""Representation of an engine state.
|
|
19
|
+
|
|
20
|
+
An engine state is characterized by a set of assignments to signals and a set of
|
|
21
|
+
conditions, when to enter which next state (transitions).
|
|
22
|
+
|
|
23
|
+
Each state belongs to a worker. Transitions can only be created betwen states of the same worker.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, worker: WorkerProto, name: str, allow_assignments: bool = True):
|
|
27
|
+
"""Initialize a state.
|
|
28
|
+
|
|
29
|
+
Arguments:
|
|
30
|
+
worker: Engine worker.
|
|
31
|
+
name: State name.
|
|
32
|
+
allow_assignments: If the state allows assignments. This is used for internal purposes.
|
|
33
|
+
"""
|
|
34
|
+
super().__init__('')
|
|
35
|
+
self._worker = worker
|
|
36
|
+
|
|
37
|
+
# Use setter for validation
|
|
38
|
+
self.name = name
|
|
39
|
+
|
|
40
|
+
self._allow_assignments = allow_assignments
|
|
41
|
+
|
|
42
|
+
self._assignments: List[Tuple[AssignmentTarget, Renderable]] = []
|
|
43
|
+
self._assigned_signal_names: Set[str] = set()
|
|
44
|
+
|
|
45
|
+
# Transition <Condition>, <next value of state variable>
|
|
46
|
+
self._transitions: List[Tuple[Renderable, Self]] = []
|
|
47
|
+
self._restricted_state: Optional[State] = None
|
|
48
|
+
self._transitions_locked = False
|
|
49
|
+
|
|
50
|
+
# Store for debug prints. These will not be rendered to synthesizeable constructs
|
|
51
|
+
self._prints: List[Tuple[str, Tuple[Renderable, ...]]] = []
|
|
52
|
+
self._printfs: Dict[str, List[Tuple[str, Tuple[Renderable, ...]]]] = {}
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def engine(self) -> EngineProto:
|
|
56
|
+
"""Engine that this state belongs to."""
|
|
57
|
+
return self.worker.engine
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def worker(self) -> WorkerProto:
|
|
61
|
+
"""Engine worker that this state belongs to."""
|
|
62
|
+
return self._worker
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def name(self) -> str:
|
|
66
|
+
"""State name.
|
|
67
|
+
|
|
68
|
+
If the worker for this state is not the main worker, the name of the state must be prefixed with the name of the current worker.
|
|
69
|
+
The prefix is automatically added, if missing.
|
|
70
|
+
"""
|
|
71
|
+
return self._name
|
|
72
|
+
|
|
73
|
+
@name.setter
|
|
74
|
+
def name(self, name: str) -> None:
|
|
75
|
+
"""State name.
|
|
76
|
+
|
|
77
|
+
If the worker for this state is not the main worker, the name of the state must be prefixed with the name of the current worker.
|
|
78
|
+
The prefix is automatically added, if missing.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
# Prefix the worker name if missing. The prefix is omitted for the main worker.
|
|
82
|
+
name = self.worker.create_scoped_name(name)
|
|
83
|
+
|
|
84
|
+
if name == self.name:
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
if name in self.worker.state_names:
|
|
88
|
+
raise KeyError(f'State {name} already exists.')
|
|
89
|
+
|
|
90
|
+
# Update set of state names in worker
|
|
91
|
+
if self.name != '':
|
|
92
|
+
self.worker.state_names.discard(self.name)
|
|
93
|
+
self.worker.state_names.add(name)
|
|
94
|
+
|
|
95
|
+
self._name = name
|
|
96
|
+
|
|
97
|
+
# Assignment Management
|
|
98
|
+
@property
|
|
99
|
+
def allow_assignments(self) -> bool:
|
|
100
|
+
"""If this state allows assignments."""
|
|
101
|
+
return self._allow_assignments
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def assignments(self) -> Sequence[Tuple[AssignmentTarget, Renderable]]:
|
|
105
|
+
"""Sequence of assignments for this state."""
|
|
106
|
+
return self._assignments
|
|
107
|
+
|
|
108
|
+
def add_assignment(self, signal: AssignmentTarget, value: Renderable) -> None:
|
|
109
|
+
"""Add assignment to this state."""
|
|
110
|
+
if not self.allow_assignments:
|
|
111
|
+
raise ForbiddenAssignmentError(f'State {self.name} does not allow assignments.')
|
|
112
|
+
|
|
113
|
+
# Check if signal is already assigned
|
|
114
|
+
if (old_assignment := self.get_assignment(signal)) is not None:
|
|
115
|
+
other_signal, old_value = old_assignment
|
|
116
|
+
|
|
117
|
+
overlap = signal.overlaps_with(other_signal)
|
|
118
|
+
|
|
119
|
+
if overlap == 'partial':
|
|
120
|
+
raise ConflictingAssignmentError(
|
|
121
|
+
f'State {self.name} already has an assignment to signal {signal.name} that partially overlaps with the new assignment.\n'
|
|
122
|
+
f'Previous assignment was {other_signal} = {old_value}, new assignment is {signal} = {value}.'
|
|
123
|
+
'\nRefusing to overwrite the signal.'
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Check equality of the signals by rendering them. This could cause false-negatives, but is the easiest way.
|
|
127
|
+
if overlap is True and value.render() != old_value.render():
|
|
128
|
+
raise ConflictingAssignmentError(
|
|
129
|
+
f'State {self.name} already has an assignment to signal {signal.name}.\n'
|
|
130
|
+
f'Previous value was {old_value}, new value would be {value}. Refusing to overwrite the signal.'
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
self._assignments.append((signal, value))
|
|
134
|
+
self._assigned_signal_names.add(signal.name)
|
|
135
|
+
|
|
136
|
+
def get_assignment(self, signal: AssignmentTarget) -> Optional[Tuple[AssignmentTarget, Renderable]]:
|
|
137
|
+
"""Get current assignment for a signal.
|
|
138
|
+
|
|
139
|
+
Arguments:
|
|
140
|
+
signal: The signal to search for.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
The current assignment for this signal or None, if it is not assigned.
|
|
144
|
+
"""
|
|
145
|
+
# Use a set of the assigned signal names for quick check. In case of a match, search for the signal.
|
|
146
|
+
if signal.name in self._assigned_signal_names:
|
|
147
|
+
for other_signal, value in self.assignments:
|
|
148
|
+
# Check by signal name, will also find slices of the same signal
|
|
149
|
+
if signal.name == other_signal.name:
|
|
150
|
+
return other_signal, value
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
# Transition Management
|
|
154
|
+
@property
|
|
155
|
+
def transitions(self) -> Sequence[Tuple[Renderable, Self]]:
|
|
156
|
+
"""List of transitions to other states."""
|
|
157
|
+
return tuple(self._transitions)
|
|
158
|
+
|
|
159
|
+
def _add_transition(self, condition: Renderable, state: StateProto) -> None:
|
|
160
|
+
"""Add transition to other state."""
|
|
161
|
+
if self._transitions_locked:
|
|
162
|
+
raise TransitionLockError('The transitions for this state have been locked. You cannot add any other transitions.')
|
|
163
|
+
elif self._restricted_state is not None and state is not self._restricted_state:
|
|
164
|
+
raise TransitionRestrictionError(
|
|
165
|
+
f'This state was restricted to transitions to state {self._restricted_state.name}. Unable to add other transitions.'
|
|
166
|
+
)
|
|
167
|
+
self._transitions.append((condition, state)) # type: ignore[arg-type]
|
|
168
|
+
|
|
169
|
+
def _restrict_transition(self, state: StateProto) -> None:
|
|
170
|
+
"""Restrict transitions to only allow one other state."""
|
|
171
|
+
if self._restricted_state is not None and state is not self._restricted_state:
|
|
172
|
+
raise TransitionRestrictionError(
|
|
173
|
+
f'This state was alreay restricted to transitions to state {self._restricted_state.name}. Unable to restrict for another state.'
|
|
174
|
+
)
|
|
175
|
+
self._restricted_state = state # type: ignore[assignment]
|
|
176
|
+
|
|
177
|
+
def _lock_transitions(self) -> None:
|
|
178
|
+
"""Lock the current transitions and prevent any others from being added."""
|
|
179
|
+
self._transitions_locked = True
|
|
180
|
+
|
|
181
|
+
# Misc.
|
|
182
|
+
def render(self, target: Optional[str] = None) -> str:
|
|
183
|
+
"""Returns the state name for Verilog.
|
|
184
|
+
|
|
185
|
+
This function will later contain the logic to transform any string to a correct verilog name.
|
|
186
|
+
"""
|
|
187
|
+
return self._name
|
|
188
|
+
|
|
189
|
+
def __format__(self, format_spec: str) -> str:
|
|
190
|
+
return self.render()
|
|
191
|
+
|
|
192
|
+
def print(self, line: str, *args: Renderable) -> None:
|
|
193
|
+
"""Adds a line to the print list that will be processed during simulation."""
|
|
194
|
+
self._prints.append((line, args))
|
|
195
|
+
|
|
196
|
+
def printf(self, fname: str, line: str, *args: Renderable) -> None:
|
|
197
|
+
"""Store an item that will be output to a file during simulation."""
|
|
198
|
+
if fname in self._printfs:
|
|
199
|
+
self._printfs[fname].append((line, args))
|
|
200
|
+
else:
|
|
201
|
+
self._printfs[fname] = [(line, args)]
|
nortl/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from typing import List, Tuple
|
|
2
|
+
|
|
3
|
+
from nortl.core.protocols import EngineProto, StateProto
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MermaidRenderer:
|
|
7
|
+
"""This class contains the methods to render a nortl to a mermaid state diagram.
|
|
8
|
+
|
|
9
|
+
Note that only states and transitions are shown. For transitions, the conditions are omitted.
|
|
10
|
+
It's purpose is to support illustration only!
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
# FIXME: Include Fork/Joins
|
|
14
|
+
|
|
15
|
+
def __init__(self, engine: EngineProto):
|
|
16
|
+
self.engine = engine
|
|
17
|
+
|
|
18
|
+
self.transitions: List[Tuple[str, str]] = []
|
|
19
|
+
self.indent_level = 0
|
|
20
|
+
|
|
21
|
+
def _add_transition(self, s1: StateProto, s2: StateProto) -> None:
|
|
22
|
+
self.transitions.append((s1.name, s2.name))
|
|
23
|
+
|
|
24
|
+
def render(self) -> str:
|
|
25
|
+
for states in self.engine.states.values():
|
|
26
|
+
for state in states:
|
|
27
|
+
for _, next_state in state.transitions:
|
|
28
|
+
self._add_transition(state, next_state)
|
|
29
|
+
|
|
30
|
+
# Generate the mermaid code
|
|
31
|
+
ret = '---\n'
|
|
32
|
+
ret += f'title {self.engine.module_name}\n'
|
|
33
|
+
ret += '---\n'
|
|
34
|
+
ret += 'stateDiagram-v2\n'
|
|
35
|
+
for s1, s2 in self.transitions:
|
|
36
|
+
ret += f' {s1} --> {s2}\n'
|
|
37
|
+
|
|
38
|
+
return ret
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from typing import List, Tuple
|
|
2
|
+
|
|
3
|
+
import networkx as nx
|
|
4
|
+
|
|
5
|
+
from nortl.core import CoreEngine
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class NetworkXRenderer:
|
|
9
|
+
"""This class contains the methods to render a nortl to verilog code.
|
|
10
|
+
|
|
11
|
+
It only represents states and their transitions without representing the conditions.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, engine: CoreEngine):
|
|
15
|
+
self.engine = engine
|
|
16
|
+
|
|
17
|
+
self.transitions: List[Tuple[str, str]] = []
|
|
18
|
+
self.indent_level = 0
|
|
19
|
+
|
|
20
|
+
def render(self) -> nx.Graph: # type: ignore
|
|
21
|
+
"""Render the engine to a networkx graph."""
|
|
22
|
+
g = nx.DiGraph() # type: ignore
|
|
23
|
+
for states in self.engine.states.values():
|
|
24
|
+
for state in states:
|
|
25
|
+
g.add_node(state.name)
|
|
26
|
+
for _, next_state in state.transitions:
|
|
27
|
+
g.add_edge(state.name, next_state.name)
|
|
28
|
+
|
|
29
|
+
return g
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from nortl.core.protocols import EngineProto
|
|
4
|
+
|
|
5
|
+
from .verilog_utils.process import AlwaysComb, AlwaysFF, VerilogAssignment, VerilogCase, VerilogIf, VerilogPrint, VerilogPrintf
|
|
6
|
+
from .verilog_utils.structural import VerilogDeclaration, VerilogModule
|
|
7
|
+
|
|
8
|
+
# FIXME: Make empty blocks being not rendered at all.
|
|
9
|
+
# FIXME: Remove if(1) statements
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class VerilogRenderer:
|
|
13
|
+
"""This class transforms the engine data into a verilog code.
|
|
14
|
+
|
|
15
|
+
To simplify matters, the verilog_utils folder contains code for rendering individual blocks.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, engine: EngineProto, include_modules: bool = True, clock_gating: bool = False):
|
|
19
|
+
"""Initializes the VerilogRenderer with the engine data and rendering options.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
engine: The CoreEngine object representing the finite state machine.
|
|
23
|
+
include_modules: A boolean indicating whether to include module instantiations in the generated Verilog code. Defaults to True.
|
|
24
|
+
clock_gating: A boolean indicating whether to enable clock gating logic in the generated Verilog code. Defaults to False.
|
|
25
|
+
"""
|
|
26
|
+
self.engine = engine
|
|
27
|
+
self.verilog_module = VerilogModule(self.engine.module_name)
|
|
28
|
+
|
|
29
|
+
self.codelst: List[str] = []
|
|
30
|
+
|
|
31
|
+
self.include_modules = include_modules
|
|
32
|
+
self.clock_gating = clock_gating
|
|
33
|
+
|
|
34
|
+
self.clk_request_signals: List[str] = []
|
|
35
|
+
|
|
36
|
+
def clear(self) -> None:
|
|
37
|
+
"""Clears the internal code list and resets the Verilog module for a new rendering cycle."""
|
|
38
|
+
self.codelst = []
|
|
39
|
+
self.verilog_module = VerilogModule(self.engine.module_name)
|
|
40
|
+
|
|
41
|
+
def render(self) -> str:
|
|
42
|
+
"""Renders the engine into Verilog code.
|
|
43
|
+
|
|
44
|
+
This method orchestrates the creation of the Verilog module, signals, instances, and logic blocks.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
A string containing the complete Verilog code.
|
|
48
|
+
"""
|
|
49
|
+
self.clear()
|
|
50
|
+
|
|
51
|
+
self.create_interface()
|
|
52
|
+
self.create_state_enum()
|
|
53
|
+
self.create_instances()
|
|
54
|
+
self.create_next_state_logic()
|
|
55
|
+
self.create_output_function()
|
|
56
|
+
self.create_prints()
|
|
57
|
+
self.create_combinationals()
|
|
58
|
+
self.create_state_transition()
|
|
59
|
+
|
|
60
|
+
if self.clock_gating:
|
|
61
|
+
self.create_clock_gates()
|
|
62
|
+
|
|
63
|
+
ret: List[str] = []
|
|
64
|
+
if self.include_modules:
|
|
65
|
+
ret.extend(m.hdl_code for m in self.engine.modules.values())
|
|
66
|
+
|
|
67
|
+
ret.append(self.verilog_module.render())
|
|
68
|
+
|
|
69
|
+
return '\n'.join(ret)
|
|
70
|
+
|
|
71
|
+
def create_clock_gates(self) -> None:
|
|
72
|
+
"""Creates the clock gating logic, including gated clock signals and enables.
|
|
73
|
+
|
|
74
|
+
This method adds the necessary signals and connections for implementing clock gating,
|
|
75
|
+
which can reduce power consumption by disabling the clock signal to inactive parts of the engine.
|
|
76
|
+
"""
|
|
77
|
+
# Create gated clock signals and clock enables
|
|
78
|
+
self.verilog_module.signals.append(VerilogDeclaration('logic', 'GCLK'))
|
|
79
|
+
self.verilog_module.signals.append(VerilogDeclaration('logic', 'GCLK_enable'))
|
|
80
|
+
self.verilog_module.signals.append(VerilogDeclaration('logic', 'GCLK_enable_latched'))
|
|
81
|
+
|
|
82
|
+
clk_requests: List[str] = []
|
|
83
|
+
|
|
84
|
+
# Get clock requests from instances
|
|
85
|
+
for name, instance in self.engine.module_instances.items():
|
|
86
|
+
if instance.module.clk_request_port is not None:
|
|
87
|
+
signalname = f'clk_request_{name}'
|
|
88
|
+
self.verilog_module.signals.append(VerilogDeclaration('logic', signalname))
|
|
89
|
+
clk_requests.append(signalname)
|
|
90
|
+
if not instance.module._ignore_clk_rst_connection and instance._clock_gating:
|
|
91
|
+
verilog_inst = self.verilog_module.get_instance(name)
|
|
92
|
+
verilog_inst.add_connection('CLK_I', 'GCLK')
|
|
93
|
+
|
|
94
|
+
self.create_clock_enable(clk_requests)
|
|
95
|
+
|
|
96
|
+
# Change Clock signal of Always_ff blocks
|
|
97
|
+
for blk in self.verilog_module.functionals:
|
|
98
|
+
if isinstance(blk, AlwaysFF):
|
|
99
|
+
blk.clk = 'GCLK'
|
|
100
|
+
|
|
101
|
+
# Instantiate Clock Gate
|
|
102
|
+
clock_gate = VerilogDeclaration('nortl_clock_gate', 'I_CLOCK_GATE')
|
|
103
|
+
clock_gate.add_connection('CLK_I', 'CLK_I')
|
|
104
|
+
clock_gate.add_connection('EN', 'GCLK_enable')
|
|
105
|
+
clock_gate.add_connection('GCLK_O', 'GCLK')
|
|
106
|
+
|
|
107
|
+
self.verilog_module.instances.append(clock_gate)
|
|
108
|
+
|
|
109
|
+
def create_clock_enable(self, clk_requests: List[str]) -> None:
|
|
110
|
+
"""Creates the clock enable signal based on state and clock requests.
|
|
111
|
+
|
|
112
|
+
This method generates the logic for controlling the clock enable signal, which is used to
|
|
113
|
+
disable the clock signal to inactive parts of the engine, reducing power consumption.
|
|
114
|
+
"""
|
|
115
|
+
# Create clock enable by states
|
|
116
|
+
clk_en_proc = AlwaysComb()
|
|
117
|
+
clk_en_proc.add(VerilogAssignment('GCLK_enable', "1'b1"))
|
|
118
|
+
|
|
119
|
+
for worker in self.engine.workers.values():
|
|
120
|
+
state_variable = worker.create_scoped_name('state_nxt')
|
|
121
|
+
cases = VerilogCase(state_variable)
|
|
122
|
+
|
|
123
|
+
for state in worker.states:
|
|
124
|
+
if state.has_metadata('Clock_gating'): # FIXME: Add Metadata to docs
|
|
125
|
+
cases.add_case(state.name)
|
|
126
|
+
cases.add_item(state.name, VerilogAssignment('GCLK_enable', "1'b0"))
|
|
127
|
+
|
|
128
|
+
for condition, _ in state.transitions:
|
|
129
|
+
block = VerilogIf(condition)
|
|
130
|
+
block.true_branch.add(VerilogAssignment('GCLK_enable', "1'b1"))
|
|
131
|
+
cases.add_item(state.name, block)
|
|
132
|
+
|
|
133
|
+
for signal, val in state.assignments:
|
|
134
|
+
block = VerilogIf(signal != val)
|
|
135
|
+
block.true_branch.add(VerilogAssignment('GCLK_enable', "1'b1"))
|
|
136
|
+
cases.add_item(state.name, block)
|
|
137
|
+
|
|
138
|
+
clk_en_proc.add(cases)
|
|
139
|
+
|
|
140
|
+
for req in clk_requests:
|
|
141
|
+
block = VerilogIf(req)
|
|
142
|
+
block.true_branch.add(VerilogAssignment('GCLK_enable', "1'b1"))
|
|
143
|
+
clk_en_proc.add(block)
|
|
144
|
+
|
|
145
|
+
self.verilog_module.functionals.append(clk_en_proc)
|
|
146
|
+
|
|
147
|
+
def create_interface(self) -> None:
|
|
148
|
+
"""Creates the Verilog module interface, including parameters, signals, and ports.
|
|
149
|
+
|
|
150
|
+
This method defines the input and output signals of the Verilog module, as well as any
|
|
151
|
+
parameters that can be used to customize its behavior.
|
|
152
|
+
"""
|
|
153
|
+
# Parameters
|
|
154
|
+
for param in self.engine.parameters.values():
|
|
155
|
+
if param.width is not None:
|
|
156
|
+
self.verilog_module.parameters[f'[{param.width - 1}:0] {param.name}'] = param.default_value
|
|
157
|
+
else:
|
|
158
|
+
self.verilog_module.parameters[param.name] = param.default_value
|
|
159
|
+
|
|
160
|
+
# Signals and Ports
|
|
161
|
+
self.verilog_module.ports.append(VerilogDeclaration('input logic', 'CLK_I'))
|
|
162
|
+
self.verilog_module.ports.append(VerilogDeclaration('input logic', 'RST_ASYNC_I'))
|
|
163
|
+
|
|
164
|
+
for name, signal in self.engine.signals.items():
|
|
165
|
+
if signal.type == 'local':
|
|
166
|
+
verilog_signal = VerilogDeclaration(signal.data_type, name, width=signal.width)
|
|
167
|
+
self.verilog_module.signals.append(verilog_signal)
|
|
168
|
+
else:
|
|
169
|
+
if signal.data_type in ['reg', 'wire', 'logic']:
|
|
170
|
+
verilog_signal = VerilogDeclaration(f'{signal.type} {signal.data_type}', name, signal.width)
|
|
171
|
+
else:
|
|
172
|
+
verilog_signal = VerilogDeclaration(signal.data_type, name, width=signal.width)
|
|
173
|
+
self.verilog_module.ports.append(verilog_signal)
|
|
174
|
+
|
|
175
|
+
def create_state_enum(self) -> None:
|
|
176
|
+
"""Creates the Verilog state enumeration.
|
|
177
|
+
|
|
178
|
+
This method defines the enumeration type for the engine states, which is used to represent
|
|
179
|
+
the current state of the engine in the Verilog code.
|
|
180
|
+
"""
|
|
181
|
+
for worker in self.engine.workers.values():
|
|
182
|
+
state_variable = worker.create_scoped_name('state')
|
|
183
|
+
state_nxt_variable = worker.create_scoped_name('state_nxt')
|
|
184
|
+
state_var = VerilogDeclaration('enum', [state_variable, state_nxt_variable])
|
|
185
|
+
for state in worker.states:
|
|
186
|
+
state_var.add_member(state.name)
|
|
187
|
+
self.verilog_module.signals.append(state_var)
|
|
188
|
+
|
|
189
|
+
def create_instances(self) -> None:
|
|
190
|
+
"""Creates the Verilog module instances.
|
|
191
|
+
|
|
192
|
+
This method instantiates the submodules of the engine, connecting their ports to the
|
|
193
|
+
appropriate signals in the top-level module.
|
|
194
|
+
"""
|
|
195
|
+
for instance in self.engine.module_instances.values():
|
|
196
|
+
decl = VerilogDeclaration(instance.module.name, instance.name)
|
|
197
|
+
|
|
198
|
+
# Parameters
|
|
199
|
+
for name, value in instance.module.parameters.items():
|
|
200
|
+
if name in instance.parameter_overrides:
|
|
201
|
+
decl.add_parameter(name, instance.parameter_overrides[name].render())
|
|
202
|
+
else:
|
|
203
|
+
decl.add_parameter(name, value)
|
|
204
|
+
|
|
205
|
+
# Connections
|
|
206
|
+
if not instance.module._ignore_clk_rst_connection:
|
|
207
|
+
decl.add_connection('CLK_I', 'CLK_I')
|
|
208
|
+
decl.add_connection('RST_ASYNC_I', 'RST_ASYNC_I')
|
|
209
|
+
|
|
210
|
+
for port_name, signal in instance.port_connections.items():
|
|
211
|
+
decl.add_connection(port_name, signal.render())
|
|
212
|
+
|
|
213
|
+
self.verilog_module.instances.append(decl)
|
|
214
|
+
|
|
215
|
+
def create_next_state_logic(self) -> None:
|
|
216
|
+
"""Creates the Verilog next state logic.
|
|
217
|
+
|
|
218
|
+
This method generates the logic for determining the next state of the engine, based on the
|
|
219
|
+
current state and the input signals.
|
|
220
|
+
"""
|
|
221
|
+
next_state_func = AlwaysComb()
|
|
222
|
+
|
|
223
|
+
for worker in self.engine.workers.values():
|
|
224
|
+
state_variable = worker.create_scoped_name('state')
|
|
225
|
+
state_nxt_variable = worker.create_scoped_name('state_nxt')
|
|
226
|
+
|
|
227
|
+
next_state_func.add(VerilogAssignment(state_nxt_variable, state_variable))
|
|
228
|
+
|
|
229
|
+
cases = VerilogCase(state_variable)
|
|
230
|
+
|
|
231
|
+
for state in worker.states:
|
|
232
|
+
cases.add_case(state.name)
|
|
233
|
+
|
|
234
|
+
for condition, next_state in state.transitions:
|
|
235
|
+
item = VerilogIf(condition)
|
|
236
|
+
item.true_branch.add(VerilogAssignment(state_nxt_variable, next_state))
|
|
237
|
+
cases.add_item(state.name, item)
|
|
238
|
+
|
|
239
|
+
next_state_func.add(cases)
|
|
240
|
+
|
|
241
|
+
self.verilog_module.functionals.append(next_state_func)
|
|
242
|
+
|
|
243
|
+
def create_output_function(self) -> None:
|
|
244
|
+
"""Creates the Verilog output function.
|
|
245
|
+
|
|
246
|
+
This method generates the logic for updating the output signals of the engine, based on the
|
|
247
|
+
current state and the input signals.
|
|
248
|
+
"""
|
|
249
|
+
output_func = AlwaysFF('CLK_I', 'RST_ASYNC_I')
|
|
250
|
+
|
|
251
|
+
for signal, reset_val in self.engine.main_worker.reset_state.assignments:
|
|
252
|
+
output_func.add_reset(VerilogAssignment(signal, reset_val))
|
|
253
|
+
|
|
254
|
+
for signal in self.engine.signals.values():
|
|
255
|
+
if signal.pulsing:
|
|
256
|
+
output_func.add(VerilogAssignment(signal, '0'))
|
|
257
|
+
|
|
258
|
+
for worker in self.engine.workers.values():
|
|
259
|
+
state_nxt_variable = worker.create_scoped_name('state_nxt')
|
|
260
|
+
cases = VerilogCase(state_nxt_variable)
|
|
261
|
+
|
|
262
|
+
for state in worker.states:
|
|
263
|
+
if len(state.assignments) > 0:
|
|
264
|
+
cases.add_case(state.name)
|
|
265
|
+
|
|
266
|
+
for signal, val in state.assignments:
|
|
267
|
+
cases.add_item(state.name, VerilogAssignment(signal, val))
|
|
268
|
+
|
|
269
|
+
output_func.add(cases)
|
|
270
|
+
|
|
271
|
+
self.verilog_module.functionals.append(output_func)
|
|
272
|
+
|
|
273
|
+
def create_prints(self) -> None:
|
|
274
|
+
"""Creates the print statements that have been put into each state."""
|
|
275
|
+
output_func = AlwaysFF('CLK_I', 'RST_ASYNC_I')
|
|
276
|
+
|
|
277
|
+
for worker in self.engine.workers.values():
|
|
278
|
+
state_nxt_variable = worker.create_scoped_name('state_nxt')
|
|
279
|
+
cases = VerilogCase(state_nxt_variable)
|
|
280
|
+
|
|
281
|
+
for state in worker.states:
|
|
282
|
+
cases.add_case(state.name)
|
|
283
|
+
|
|
284
|
+
for fname, item in state._printfs.items():
|
|
285
|
+
for line, val in item:
|
|
286
|
+
cases.add_item(state.name, VerilogPrintf(fname, line, *[v.render() for v in val]))
|
|
287
|
+
|
|
288
|
+
for line, val in state._prints:
|
|
289
|
+
cases.add_item(state.name, VerilogPrint(line, *[v.render() for v in val]))
|
|
290
|
+
|
|
291
|
+
output_func.add(cases)
|
|
292
|
+
|
|
293
|
+
self.verilog_module.functionals.append(output_func)
|
|
294
|
+
|
|
295
|
+
def create_combinationals(self) -> None:
|
|
296
|
+
"""Creates the Verilog combinational logic.
|
|
297
|
+
|
|
298
|
+
This method generates the combinational logic for the engine, based on the input signals.
|
|
299
|
+
"""
|
|
300
|
+
combinationals = AlwaysComb()
|
|
301
|
+
|
|
302
|
+
for signal, value in self.engine.combinationals:
|
|
303
|
+
combinationals.add(VerilogAssignment(signal, value))
|
|
304
|
+
|
|
305
|
+
self.verilog_module.functionals.append(combinationals)
|
|
306
|
+
|
|
307
|
+
def create_state_transition(self) -> None:
|
|
308
|
+
"""Creates the Verilog state transition logic.
|
|
309
|
+
|
|
310
|
+
This method generates the logic for transitioning the engine from one state to another.
|
|
311
|
+
"""
|
|
312
|
+
state_transition = AlwaysFF('CLK_I', 'RST_ASYNC_I')
|
|
313
|
+
for worker in self.engine.workers.values():
|
|
314
|
+
state_variable = worker.create_scoped_name('state')
|
|
315
|
+
state_nxt_variable = worker.create_scoped_name('state_nxt')
|
|
316
|
+
|
|
317
|
+
state_transition.add_reset(VerilogAssignment(state_variable, worker.reset_state.name))
|
|
318
|
+
|
|
319
|
+
block = VerilogIf(f'{worker.sync_reset}')
|
|
320
|
+
block.true_branch.add(VerilogAssignment(state_variable, worker.reset_state.name))
|
|
321
|
+
block.false_branch.add(VerilogAssignment(state_variable, state_nxt_variable))
|
|
322
|
+
|
|
323
|
+
state_transition.add(block)
|
|
324
|
+
|
|
325
|
+
self.verilog_module.functionals.append(state_transition)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
class VerilogFormatter:
|
|
2
|
+
def __init__(self, code: str) -> None:
|
|
3
|
+
self.code_lst = code.split('\n')
|
|
4
|
+
self.indent_level = 0
|
|
5
|
+
|
|
6
|
+
def format(self) -> str:
|
|
7
|
+
self.code_lst = [self._indent(line) for line in self.code_lst]
|
|
8
|
+
self.code_lst = [self._newlines(line) for line in self.code_lst]
|
|
9
|
+
return '\n'.join(self.code_lst)
|
|
10
|
+
|
|
11
|
+
def _indent(self, line: str) -> str:
|
|
12
|
+
reasons_for_indent = ['begin', 'case']
|
|
13
|
+
reasons_for_dedent = ['end', 'endcase']
|
|
14
|
+
|
|
15
|
+
if any([x in line.split() for x in reasons_for_dedent]):
|
|
16
|
+
self.indent_level -= 1
|
|
17
|
+
|
|
18
|
+
ret = ' ' * self.indent_level + line
|
|
19
|
+
|
|
20
|
+
if any([x in line.split() for x in reasons_for_indent]):
|
|
21
|
+
self.indent_level += 1
|
|
22
|
+
|
|
23
|
+
return ret
|
|
24
|
+
|
|
25
|
+
def _newlines(self, line: str) -> str:
|
|
26
|
+
reasons_for_newline = ['module', 'always']
|
|
27
|
+
if any(line.strip().startswith(reason) for reason in reasons_for_newline):
|
|
28
|
+
return f'\n{line}'
|
|
29
|
+
return line
|