nortl 1.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. nortl/__init__.py +85 -0
  2. nortl/components/__init__.py +8 -0
  3. nortl/components/channel.py +132 -0
  4. nortl/components/timer.py +73 -0
  5. nortl/core/__init__.py +40 -0
  6. nortl/core/checker.py +135 -0
  7. nortl/core/common/__init__.py +4 -0
  8. nortl/core/common/access.py +25 -0
  9. nortl/core/common/debug.py +6 -0
  10. nortl/core/common/naming_helper.py +33 -0
  11. nortl/core/constructs/__init__.py +13 -0
  12. nortl/core/constructs/condition.py +143 -0
  13. nortl/core/constructs/fork_join.py +84 -0
  14. nortl/core/constructs/loop.py +138 -0
  15. nortl/core/engine.py +575 -0
  16. nortl/core/exceptions.py +139 -0
  17. nortl/core/manager/__init__.py +6 -0
  18. nortl/core/manager/scratch_manager.py +128 -0
  19. nortl/core/manager/signal_manager.py +71 -0
  20. nortl/core/modifiers.py +136 -0
  21. nortl/core/module.py +181 -0
  22. nortl/core/operations.py +834 -0
  23. nortl/core/parameter.py +88 -0
  24. nortl/core/process.py +451 -0
  25. nortl/core/protocols.py +628 -0
  26. nortl/core/renderers/__init__.py +0 -0
  27. nortl/core/renderers/operations/__init__.py +34 -0
  28. nortl/core/renderers/operations/arithmetics.py +38 -0
  29. nortl/core/renderers/operations/base.py +111 -0
  30. nortl/core/renderers/operations/comparison.py +44 -0
  31. nortl/core/renderers/operations/logic.py +38 -0
  32. nortl/core/renderers/operations/misc.py +26 -0
  33. nortl/core/renderers/operations/slice.py +30 -0
  34. nortl/core/signal.py +878 -0
  35. nortl/core/state.py +201 -0
  36. nortl/py.typed +0 -0
  37. nortl/renderer/__init__.py +5 -0
  38. nortl/renderer/mermaid_renderer.py +38 -0
  39. nortl/renderer/networkx_renderer.py +29 -0
  40. nortl/renderer/verilog_renderer.py +325 -0
  41. nortl/renderer/verilog_utils/__init__.py +6 -0
  42. nortl/renderer/verilog_utils/formatter.py +29 -0
  43. nortl/renderer/verilog_utils/process.py +226 -0
  44. nortl/renderer/verilog_utils/structural.py +146 -0
  45. nortl/renderer/verilog_utils/utils.py +23 -0
  46. nortl/utils/__init__.py +0 -0
  47. nortl/utils/parse_utils.py +37 -0
  48. nortl/utils/templates/testbench.sv +41 -0
  49. nortl/utils/test_wrapper.py +218 -0
  50. nortl/utils/type_aliases.py +15 -0
  51. nortl/verilog_library/__init__.py +74 -0
  52. nortl/verilog_library/nortl_clock_gate.sv +20 -0
  53. nortl/verilog_library/nortl_count_down_timer.sv +50 -0
  54. nortl/verilog_library/nortl_delay.sv +66 -0
  55. nortl/verilog_library/nortl_edge_detector.sv +34 -0
  56. nortl/verilog_library/nortl_sync.sv +28 -0
  57. nortl-1.4.0.dist-info/METADATA +105 -0
  58. nortl-1.4.0.dist-info/RECORD +60 -0
  59. nortl-1.4.0.dist-info/WHEEL +4 -0
  60. nortl-1.4.0.dist-info/licenses/LICENSE +11 -0
nortl/core/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,5 @@
1
+ from .mermaid_renderer import MermaidRenderer
2
+ from .networkx_renderer import NetworkXRenderer
3
+ from .verilog_renderer import VerilogRenderer
4
+
5
+ __all__ = ['MermaidRenderer', 'NetworkXRenderer', 'VerilogRenderer']
@@ -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,6 @@
1
+ from .structural import VerilogDeclaration, VerilogModule
2
+
3
+ __all__ = [
4
+ 'VerilogDeclaration',
5
+ 'VerilogModule',
6
+ ]
@@ -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