netlist-carpentry 0.1.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 (70) hide show
  1. netlist_carpentry/__init__.py +67 -0
  2. netlist_carpentry/api/__init__.py +0 -0
  3. netlist_carpentry/api/read/__init__.py +0 -0
  4. netlist_carpentry/api/read/_abstract_reader.py +12 -0
  5. netlist_carpentry/api/read/gen_nl.py +29 -0
  6. netlist_carpentry/api/read/read_utils.py +81 -0
  7. netlist_carpentry/api/read/yosys_netlist.py +452 -0
  8. netlist_carpentry/api/read/yosys_netlist_types.py +51 -0
  9. netlist_carpentry/api/write/__init__.py +0 -0
  10. netlist_carpentry/api/write/py2v.py +482 -0
  11. netlist_carpentry/api/write/write_utils.py +14 -0
  12. netlist_carpentry/core/__init__.py +0 -0
  13. netlist_carpentry/core/circuit.py +506 -0
  14. netlist_carpentry/core/exceptions.py +80 -0
  15. netlist_carpentry/core/graph/__init__.py +5 -0
  16. netlist_carpentry/core/graph/constraint.py +31 -0
  17. netlist_carpentry/core/graph/match.py +308 -0
  18. netlist_carpentry/core/graph/pattern.py +834 -0
  19. netlist_carpentry/core/graph/pattern_generator.py +207 -0
  20. netlist_carpentry/core/graph/utils.py +26 -0
  21. netlist_carpentry/core/graph/visualization.py +102 -0
  22. netlist_carpentry/core/netlist_elements/__init__.py +0 -0
  23. netlist_carpentry/core/netlist_elements/element_path.py +415 -0
  24. netlist_carpentry/core/netlist_elements/element_type.py +86 -0
  25. netlist_carpentry/core/netlist_elements/instance.py +573 -0
  26. netlist_carpentry/core/netlist_elements/mixins/__init__.py +0 -0
  27. netlist_carpentry/core/netlist_elements/mixins/evaluation.py +98 -0
  28. netlist_carpentry/core/netlist_elements/mixins/graph_building.py +81 -0
  29. netlist_carpentry/core/netlist_elements/mixins/metadata.py +163 -0
  30. netlist_carpentry/core/netlist_elements/mixins/module_base.py +49 -0
  31. netlist_carpentry/core/netlist_elements/mixins/module_bfs.py +141 -0
  32. netlist_carpentry/core/netlist_elements/mixins/module_dfs.py +98 -0
  33. netlist_carpentry/core/netlist_elements/module.py +1198 -0
  34. netlist_carpentry/core/netlist_elements/netlist_element.py +311 -0
  35. netlist_carpentry/core/netlist_elements/port.py +692 -0
  36. netlist_carpentry/core/netlist_elements/port_segment.py +450 -0
  37. netlist_carpentry/core/netlist_elements/segment_base.py +54 -0
  38. netlist_carpentry/core/netlist_elements/wire.py +584 -0
  39. netlist_carpentry/core/netlist_elements/wire_segment.py +492 -0
  40. netlist_carpentry/core/opt/__init__.py +0 -0
  41. netlist_carpentry/core/opt/constant_folds.py +77 -0
  42. netlist_carpentry/core/opt/driverless_instances.py +65 -0
  43. netlist_carpentry/core/opt/loadless_wires.py +83 -0
  44. netlist_carpentry/core/port_direction.py +68 -0
  45. netlist_carpentry/core/protocols/__init__.py +0 -0
  46. netlist_carpentry/core/protocols/base_types.py +37 -0
  47. netlist_carpentry/core/protocols/netlist_elements.py +305 -0
  48. netlist_carpentry/core/protocols/signals.py +88 -0
  49. netlist_carpentry/core/signal.py +290 -0
  50. netlist_carpentry/py.typed +0 -0
  51. netlist_carpentry/scripts/__init__.py +3 -0
  52. netlist_carpentry/scripts/cascading_or_replacement.py +47 -0
  53. netlist_carpentry/scripts/eqy.sh +9 -0
  54. netlist_carpentry/scripts/eqy_check.py +95 -0
  55. netlist_carpentry/scripts/eqy_proc.sh +13 -0
  56. netlist_carpentry/scripts/hdl/pmux2mux.v +22 -0
  57. netlist_carpentry/scripts/script_builder.py +88 -0
  58. netlist_carpentry/scripts/verilogToJsonSimple.sh +27 -0
  59. netlist_carpentry/utils/__init__.py +0 -0
  60. netlist_carpentry/utils/_gate_lib_base.py +658 -0
  61. netlist_carpentry/utils/cfg.py +44 -0
  62. netlist_carpentry/utils/custom_dict.py +91 -0
  63. netlist_carpentry/utils/custom_list.py +68 -0
  64. netlist_carpentry/utils/gate_lib.py +1628 -0
  65. netlist_carpentry/utils/gate_lib_factory.py +419 -0
  66. netlist_carpentry/utils/log.py +319 -0
  67. netlist_carpentry-0.1.0.dist-info/METADATA +86 -0
  68. netlist_carpentry-0.1.0.dist-info/RECORD +70 -0
  69. netlist_carpentry-0.1.0.dist-info/WHEEL +4 -0
  70. netlist_carpentry-0.1.0.dist-info/licenses/LICENSE +11 -0
@@ -0,0 +1,67 @@
1
+ # isort: skip_file
2
+ import os
3
+ import shutil
4
+
5
+ from netlist_carpentry.utils.cfg import CFG # Config and log must be loaded before the other modules
6
+ from netlist_carpentry.utils.log import Log, LOG, initialize_logging
7
+ from netlist_carpentry.core.graph import EMPTY_GRAPH
8
+ from netlist_carpentry.core.netlist_elements.wire_segment import (
9
+ WIRE_SEGMENT_0,
10
+ WIRE_SEGMENT_1,
11
+ WIRE_SEGMENT_X,
12
+ WIRE_SEGMENT_Z,
13
+ CONST_MAP_VAL2OBJ,
14
+ CONST_MAP_VAL2VERILOG,
15
+ CONST_MAP_YOSYS2OBJ,
16
+ )
17
+ from netlist_carpentry.core.graph.pattern import EMPTY_PATTERN
18
+ from netlist_carpentry.scripts import NC_SCRIPTS_DIR
19
+ from netlist_carpentry.api.read.read_utils import read_json, read
20
+ from netlist_carpentry.api.write.write_utils import write
21
+ from netlist_carpentry.core.netlist_elements.instance import Instance
22
+ from netlist_carpentry.core.netlist_elements.module import Module
23
+ from netlist_carpentry.core.netlist_elements.port import Port
24
+ from netlist_carpentry.core.netlist_elements.wire import Wire
25
+ from netlist_carpentry.core.circuit import Circuit
26
+ from netlist_carpentry.utils import gate_lib, gate_lib_factory
27
+
28
+ Port.model_rebuild()
29
+
30
+ __all__ = [
31
+ 'CFG',
32
+ 'CONST_MAP_VAL2OBJ',
33
+ 'CONST_MAP_VAL2VERILOG',
34
+ 'CONST_MAP_YOSYS2OBJ',
35
+ 'EMPTY_GRAPH',
36
+ 'EMPTY_PATTERN',
37
+ 'LOG',
38
+ 'NC_DIR',
39
+ 'NC_SCRIPTS_DIR',
40
+ 'WIRE_SEGMENT_0',
41
+ 'WIRE_SEGMENT_1',
42
+ 'WIRE_SEGMENT_X',
43
+ 'WIRE_SEGMENT_Z',
44
+ 'Circuit',
45
+ 'Instance',
46
+ 'Module',
47
+ 'Port',
48
+ 'Wire',
49
+ 'gate_lib',
50
+ 'gate_lib_factory',
51
+ 'read',
52
+ 'read_json',
53
+ 'write',
54
+ ]
55
+
56
+ NC_DIR = os.path.dirname(os.path.abspath(__file__))
57
+
58
+ # Activate rudimentary LOG handling at first import
59
+ if not Log._init_finished:
60
+ initialize_logging(no_file=True)
61
+
62
+ yosys_path = shutil.which('yosys')
63
+ if not yosys_path:
64
+ LOG.warn(
65
+ 'Unable to locate Yosys. Install Yosys, if it is not already installed. '
66
+ + 'Otherwise, check your Path variable, and whether Yosys can be executed via the command "yosys".'
67
+ )
File without changes
File without changes
@@ -0,0 +1,12 @@
1
+ from pathlib import Path
2
+ from typing import Union
3
+
4
+
5
+ class _AbstractReader:
6
+ def __init__(self, path: Union[str, Path]):
7
+ if isinstance(path, str):
8
+ path = Path(path)
9
+ self.path = path
10
+
11
+ def read(self) -> object:
12
+ raise NotImplementedError('Not implemented in abstract class!')
@@ -0,0 +1,29 @@
1
+ import subprocess
2
+ from pathlib import Path
3
+ from typing import Union
4
+
5
+ from netlist_carpentry import NC_SCRIPTS_DIR
6
+
7
+
8
+ def generate_json_netlist(
9
+ input_file_path: Union[str, Path],
10
+ output_file_path: Union[str, Path],
11
+ top_module_name: str = '',
12
+ verbose: bool = False,
13
+ yosys_script_path: Union[str, Path] = '',
14
+ ) -> subprocess.CompletedProcess[bytes]:
15
+ stdout = None if verbose else subprocess.PIPE
16
+ if yosys_script_path != '':
17
+ return subprocess.run(yosys_script_path, stdout=stdout, stderr=subprocess.PIPE)
18
+ pmux2mux_path = NC_SCRIPTS_DIR + '/hdl/pmux2mux.v'
19
+ if isinstance(input_file_path, str):
20
+ input_file_path = Path(input_file_path)
21
+ if isinstance(output_file_path, str):
22
+ output_file_path = Path(output_file_path)
23
+ input_dir = input_file_path.parent
24
+ hdl_file_name = input_file_path.name
25
+ output_dir = output_file_path.parent
26
+ output_dir.mkdir(exist_ok=True)
27
+ yosys_script_path = Path(f'{NC_SCRIPTS_DIR}/verilogToJsonSimple.sh')
28
+ cmd = [yosys_script_path, input_dir, hdl_file_name, output_file_path, pmux2mux_path, top_module_name]
29
+ return subprocess.run(cmd, stdout=stdout, stderr=subprocess.PIPE)
@@ -0,0 +1,81 @@
1
+ from pathlib import Path
2
+ from tempfile import TemporaryDirectory
3
+ from time import time
4
+ from typing import Sequence, Union
5
+
6
+ from netlist_carpentry.api.read.yosys_netlist import YosysNetlistReader
7
+ from netlist_carpentry.core.circuit import Circuit
8
+ from netlist_carpentry.scripts.script_builder import build_and_execute
9
+ from netlist_carpentry.utils.log import LOG
10
+
11
+
12
+ def read_json(json_path: Union[str, Path], circuit_name: str = '') -> Circuit:
13
+ """
14
+ Reads a JSON file and converts it to a Circuit object using the YosysNetlistReader.
15
+
16
+ Args:
17
+ json_path (Union[str, Path]): The path to the JSON file.
18
+ circuit_name (str, optional): The name of the circuit to be created. If not provided, the default name will be used.
19
+
20
+ Returns:
21
+ Circuit: A Circuit object representing the circuit defined in the JSON file.
22
+ """
23
+ return YosysNetlistReader(json_path).transform_to_circuit(circuit_name)
24
+
25
+
26
+ def read(
27
+ verilog_paths: Union[str, Path, Sequence[Union[str, Path]]],
28
+ top: str = '',
29
+ circuit_name: str = '',
30
+ verbose: bool = False,
31
+ out: Union[str, Path] = '',
32
+ ) -> Circuit:
33
+ """
34
+ Reads a Verilog file and converts it to a Circuit object using the YosysNetlistReader.
35
+
36
+ The Verilog file is first converted to a JSON file using Yosys (via the generate_json_netlist function),
37
+ which is then read by the read_json function.
38
+ The Circuit represented by the provided Verilog file is returned as a result.
39
+
40
+ This function also supports setting Verilog parameters using Mako for template processing.
41
+ Accordingly, this function generates an intermediate (rendered) version of the given Verilog file, which is
42
+ then used as source to generate the JSON netlist from.
43
+ This is only relevant, if a module from the file has parameters, which can be set dynamically using the `parameters` parameter.
44
+ Otherwise (if no module has parameters, or they are not specified in Mako syntax), the rendered version is equal to the provided file.
45
+ See the description of the `parameters` parameter for more information.
46
+
47
+ Args:
48
+ verilog_paths (Union[str, Path]): The path to the Verilog file. Alternatively, a list of paths.
49
+ top (str, optional): The name of the top-level module in the Verilog file. If not provided, no top module
50
+ is set, which means that the circuit will not have a specified hierarchy until set manually via Circuit.set_top().
51
+ circuit_name (str, optional): The name of the circuit to be created. If not provided, the default name will be used.
52
+ verbose (bool, optional): Whether to show output from the Yosys tool. Defaults to False.
53
+ out (Union[str, Path]): A path to a directory, where the generated JSON file will be located. Defaults to '', in which case
54
+ the generated JSON netlist is saved in a temporary directory.
55
+
56
+ Returns:
57
+ Circuit: A Circuit object representing the circuit defined in the Verilog file.
58
+ """
59
+ if isinstance(verilog_paths, (str, Path)):
60
+ paths = [Path(verilog_paths).resolve()]
61
+ else:
62
+ paths = [Path(p).resolve() for p in verilog_paths]
63
+
64
+ if not paths:
65
+ raise ValueError('No verilog paths provided!')
66
+ paths[0].parent.mkdir(parents=True, exist_ok=True)
67
+ with TemporaryDirectory() as tmpdirname:
68
+ out_path = Path(out) if out else Path(tmpdirname)
69
+ script_path = out_path / 'gen_json.sh'
70
+ json_path = out_path / f'{paths[0].stem}.json'
71
+ LOG.info(f'Generating Yosys netlist from {len(paths)} files...')
72
+ start = time()
73
+ gen_process = build_and_execute(script_path, paths, json_path, verbose=verbose, top=top)
74
+ LOG.info(f'Generated Yosys netlist from {len(paths)} files in {round(time() - start, 2)}s!')
75
+ if gen_process.stderr:
76
+ for err in gen_process.stderr.decode().splitlines():
77
+ LOG.error(err)
78
+ if gen_process.returncode != 0:
79
+ stdout = gen_process.stdout.decode() if gen_process.stdout else ''
80
+ raise RuntimeError(f'Failed to generate JSON netlist:\n{stdout}\n{gen_process.stderr.decode()}')
81
+ return read_json(json_path, circuit_name)
@@ -0,0 +1,452 @@
1
+ import json
2
+ import re
3
+ from pathlib import Path
4
+ from time import time
5
+ from typing import Dict, List, Optional, Set, Tuple, Union
6
+
7
+ from tqdm import tqdm
8
+
9
+ from netlist_carpentry import CFG, LOG
10
+ from netlist_carpentry.api.read._abstract_reader import _AbstractReader
11
+ from netlist_carpentry.api.read.yosys_netlist_types import AllYosysTypes, BitAlias, YosysCell, YosysData, YosysModule
12
+ from netlist_carpentry.core.circuit import Circuit
13
+ from netlist_carpentry.core.netlist_elements.element_path import WireSegmentPath
14
+ from netlist_carpentry.core.netlist_elements.instance import Instance
15
+ from netlist_carpentry.core.netlist_elements.module import Module
16
+ from netlist_carpentry.core.netlist_elements.netlist_element import NetlistElement
17
+ from netlist_carpentry.core.netlist_elements.port import Port
18
+ from netlist_carpentry.core.netlist_elements.wire import Wire
19
+ from netlist_carpentry.core.netlist_elements.wire_segment import CONST_MAP_YOSYS2OBJ, WireSegment
20
+ from netlist_carpentry.core.port_direction import PortDirection
21
+ from netlist_carpentry.core.signal import Signal
22
+ from netlist_carpentry.utils._gate_lib_base import LibUtils
23
+ from netlist_carpentry.utils.gate_lib import DFF, DLatch, get
24
+
25
+
26
+ class YosysNetlistReader(_AbstractReader):
27
+ def __init__(self, path: Union[str, Path]):
28
+ super().__init__(path)
29
+ self.net_number_mapping: Dict[str, Dict[int, WireSegmentPath]] = {}
30
+
31
+ self._module_name_mapping: Dict[str, str] = {}
32
+ self._port_name_mapping: Dict[str, List[Tuple[str, str]]] = {}
33
+ self._instance_name_mapping: Dict[str, Dict[str, str]] = {}
34
+ self._wire_name_mapping: Dict[str, Dict[str, str]] = {}
35
+ self._module_definitions: Set[str] = set()
36
+ self._module_instantiations: Set[str] = set()
37
+ self._wire_cnt = 0
38
+
39
+ # Remains None until the circuit is created via the transform_to_circuit method
40
+ self.circuit: Optional[Circuit] = None
41
+
42
+ @property
43
+ def module_name_mapping(self) -> Dict[str, str]:
44
+ """The mapping from original module names to normalized module names."""
45
+ return self._module_name_mapping
46
+
47
+ @property
48
+ def port_name_mapping(self) -> Dict[str, List[Tuple[str, str]]]:
49
+ """The mapping from original port names to normalized port name tuples."""
50
+ return self._port_name_mapping
51
+
52
+ @property
53
+ def instance_name_mapping(self) -> Dict[str, Dict[str, str]]:
54
+ """The mapping from original instance names to normalized instance names."""
55
+ return self._instance_name_mapping
56
+
57
+ @property
58
+ def wire_name_mapping(self) -> Dict[str, Dict[str, str]]:
59
+ """The mapping from original wire names to normalized wire names."""
60
+ return self._wire_name_mapping
61
+
62
+ @property
63
+ def module_definitions(self) -> Set[str]:
64
+ """The set of module definitions found in the netlist."""
65
+ return self._module_definitions
66
+
67
+ @property
68
+ def module_instantiations(self) -> Set[str]:
69
+ """The set of module instantiations found in the netlist."""
70
+ return self._module_instantiations
71
+
72
+ @property
73
+ def undefined_modules(self) -> Set[str]:
74
+ """
75
+ Return a set of module names that are instantiated but not defined in the netlist.
76
+
77
+ This set indicates submodule instantiations, where no definition is present.
78
+ These instances will be treated as black-box cells, since their implementation
79
+ remains unknown.
80
+ """
81
+ return self.module_instantiations.difference(self.module_definitions)
82
+
83
+ @property
84
+ def uninstantiated_modules(self) -> Set[str]:
85
+ """Return a set of module names that are defined but not instantiated in the netlist.
86
+
87
+ This set indicates module definitions that are never used anywhere.
88
+ These modules might be unnecessary.
89
+ """
90
+ return self.module_definitions.difference(self.module_instantiations)
91
+
92
+ @property
93
+ def module_definitions_and_instances_match(self) -> bool:
94
+ # Check if there are uninstantiated modules (besides the top module)
95
+ top_name: Set[str] = {self.circuit.top.name} if self.circuit is not None and self.circuit.top is not None else set()
96
+ uninstantiated_modules = self.uninstantiated_modules.difference(top_name)
97
+ if uninstantiated_modules:
98
+ diff = self.uninstantiated_modules
99
+ LOG.warn(f'Found modules defined but not instantiated: {diff}')
100
+ # Check if there are undefined modules
101
+ if self.undefined_modules:
102
+ diff = self.undefined_modules
103
+ LOG.error(f'Found modules defined but not instantiated: {diff}')
104
+ return not self.undefined_modules and not uninstantiated_modules
105
+
106
+ def read(self) -> YosysData:
107
+ with open(self.path) as f:
108
+ netlist_dict: YosysData = json.loads(f.read())
109
+ if CFG.simplify_escaped_identifiers:
110
+ return self._preprocess_dict(netlist_dict)
111
+ return netlist_dict
112
+
113
+ def _preprocess_dict(self, nl_dict: YosysData) -> YosysData:
114
+ LOG.debug(f"Replacing all special characters with their internal representation, which is currently set to '{CFG.id_internal}'...")
115
+ start = time()
116
+ nl_dict['modules'] = self._replace_in_module_dict(nl_dict['modules'], '$', CFG.id_internal) # type:ignore
117
+ LOG.debug(f'Replaced all special characters with their internal representation in {time() - start:.2f}s.')
118
+
119
+ for mname in nl_dict['modules']:
120
+ mdict: YosysModule = nl_dict['modules'][mname]
121
+ LOG.debug(f"Preparing dictionary of module '{mname}'...")
122
+ LOG.debug('Fixing net names...')
123
+ start = time()
124
+ if 'netnames' in mdict:
125
+ new_netnames = {}
126
+ for w in tqdm(mdict['netnames'], desc='Net preprocessing progress'):
127
+ new_wire_name = self.simplify_wire_name(mname, w)
128
+ new_netnames[new_wire_name] = mdict['netnames'][w]
129
+ mdict['netnames'] = new_netnames
130
+ LOG.debug(f'Fixed net names in {time() - start:.2f}s.')
131
+ simple_name = self.simplify_module_name(mname)
132
+ if simple_name != mname:
133
+ nl_dict['modules'] = self._replace_in_module_dict(nl_dict['modules'], mname, simple_name) # type:ignore
134
+ LOG.debug(f"Prepared dictionary of module '{mname}' in {time() - start:.2f}s.")
135
+ return nl_dict
136
+
137
+ def _replace_in_module_dict(self, inner_dict: Dict[str, dict], old_val: str, new_val: str) -> Dict[str, dict]: # type:ignore
138
+ """Recursively replace all occurrences of `old_val` with `new_val` in dictionary keys and values."""
139
+ if isinstance(inner_dict, dict): # type:ignore # If it's a dictionary, process keys and values
140
+ return {k.replace(old_val, new_val): self._replace_in_module_dict(v, old_val, new_val) for k, v in inner_dict.items()} # type:ignore
141
+ elif isinstance(inner_dict, list): # type:ignore # If it's a list, process each item
142
+ return [self._replace_in_module_dict(item, old_val, new_val) for item in inner_dict]
143
+ elif isinstance(inner_dict, str): # If it's a string, replace old_val with new_val
144
+ return inner_dict.replace(old_val, new_val)
145
+ else:
146
+ return inner_dict # Return unchanged for other data types
147
+
148
+ def simplify_module_name(self, module_name: str) -> str:
149
+ new_m = module_name
150
+ if not module_name.isidentifier():
151
+ # Main issue is with parametrized module names, indicated by "$paramod\" by Yosys
152
+ if 'paramod\\' in module_name:
153
+ module_names = module_name.replace('paramod\\', '').split('\\')
154
+ new_m = ''
155
+ for mseg in module_names:
156
+ if '=' in mseg:
157
+ paramname, paramvalue = mseg.split('=')
158
+ paramvalue = int(paramvalue.split("'")[1], 2) # type:ignore
159
+ new_m += f'{CFG.id_internal}{paramname}{paramvalue}'
160
+ else:
161
+ new_m += mseg
162
+ else:
163
+ new_m = re.sub(r'\W', CFG.id_internal, new_m)
164
+ if new_m not in self.module_name_mapping:
165
+ self._module_name_mapping[new_m] = module_name
166
+ return new_m
167
+ raise KeyError(
168
+ f'Simplified module name "{module_name}" to "{new_m}", but this name is already associated with module "{self.module_name_mapping[new_m]}"!'
169
+ )
170
+
171
+ def simplify_port_name(self, port_name: str, element_path: str) -> str:
172
+ new_p = port_name
173
+ if not port_name.isidentifier():
174
+ new_p = re.sub(r'\W', CFG.id_internal, new_p)
175
+ if new_p[0].isdigit():
176
+ new_p = CFG.id_internal + new_p
177
+ if new_p not in self.port_name_mapping:
178
+ self._port_name_mapping[new_p] = []
179
+ self._port_name_mapping[new_p].append((port_name, element_path))
180
+ return new_p
181
+
182
+ def simplify_instance_name(self, module_name: str, instance_name: str) -> str:
183
+ new_i = instance_name
184
+ if not instance_name.isidentifier():
185
+ new_i = re.sub(r'\W', CFG.id_internal, new_i)
186
+ if new_i[0].isdigit():
187
+ new_i = CFG.id_internal + new_i
188
+ if module_name not in self.instance_name_mapping:
189
+ self._instance_name_mapping[module_name] = {}
190
+ if new_i not in self._instance_name_mapping[module_name]:
191
+ self._instance_name_mapping[module_name][new_i] = instance_name
192
+ return new_i
193
+ raise KeyError(
194
+ f'Simplified instance name "{instance_name}" to "{new_i}" in module {module_name}, but this name is already associated with instance "{self.instance_name_mapping[new_i]}"!'
195
+ )
196
+
197
+ def simplify_wire_name(self, module_name: str, wire_name: str) -> str:
198
+ new_w = wire_name
199
+ if not wire_name.isidentifier():
200
+ new_w = re.sub(r'\W', CFG.id_internal, new_w)
201
+ if new_w[0].isdigit():
202
+ new_w = CFG.id_internal + new_w
203
+ if module_name not in self.wire_name_mapping:
204
+ self._wire_name_mapping[module_name] = {}
205
+ if new_w not in self._wire_name_mapping[module_name]:
206
+ self._wire_name_mapping[module_name][new_w] = wire_name
207
+ return new_w
208
+ raise KeyError(
209
+ f'Simplified instance name "{wire_name}" to "{new_w}" in module {module_name}, but this name is already associated with instance "{self.wire_name_mapping[new_w]}"!'
210
+ )
211
+
212
+ def transform_to_circuit(self, name: str = '') -> Circuit:
213
+ LOG.info(f'Reading Yosys netlist from file {self.path}...')
214
+ start = time()
215
+ modules_dict = self.read()
216
+ LOG.info(f'Read Yosys netlist from file {self.path} in {round(time() - start, 2)}s!')
217
+ if not name:
218
+ name = str(self.path)
219
+ self.circuit = Circuit(name=name)
220
+
221
+ return self._populate_circuit(modules_dict['modules'], self.circuit)
222
+
223
+ def _populate_circuit(self, modules_dict: Dict[str, YosysModule], circuit: Circuit) -> Circuit:
224
+ self._module_definitions.update(modules_dict.keys())
225
+ for mname in modules_dict:
226
+ s = time()
227
+ LOG.info(f'Building module {mname}...')
228
+ circuit.add_module(self._populate_module(Module(raw_path=mname), modules_dict[mname]))
229
+ # TODO check for multiple top modules!
230
+ if 'attributes' in modules_dict[mname] and 'top' in modules_dict[mname]['attributes']:
231
+ LOG.info(f'Setting module {mname} as new top module as specified in the netlist!')
232
+ circuit.set_top(mname)
233
+ LOG.info(f'Built module {mname} in {round(time() - s, 2)}s!')
234
+ return circuit
235
+
236
+ def _populate_module(self, module: Module, module_dict: YosysModule) -> Module:
237
+ self._build_wires(module, module_dict)
238
+ self._build_ports(module, module_dict)
239
+ self._build_instances(module, module_dict)
240
+ self._build_metadata(module, module_dict)
241
+ self._build_module_parameters(module, module_dict)
242
+
243
+ return module
244
+
245
+ def _build_wires(self, module: Module, module_dict: YosysModule) -> None:
246
+ self.net_number_mapping[module.name] = {}
247
+ if 'netnames' in module_dict:
248
+ LOG.debug(f'Building {len(module_dict["netnames"])} wires...')
249
+ start = time()
250
+ for wire_name, wire_data in tqdm(module_dict['netnames'].items(), desc='Wire building progress'):
251
+ w_path = f'{module.name}.{wire_name}'
252
+ msb_first = 'upto' not in wire_data
253
+ w = Wire(raw_path=w_path, msb_first=msb_first, module=module)
254
+ self._build_metadata(w, wire_data)
255
+ self._build_parameters(w, wire_data)
256
+ w.parameters['signed'] = wire_data['signed'] if 'signed' in wire_data else 0
257
+ if 'bits' in wire_data:
258
+ offset = 0 if 'offset' not in wire_data or wire_data['offset'] is None else wire_data['offset']
259
+ for seg_i, b in enumerate(wire_data['bits'], offset):
260
+ path = f'{w_path}.{seg_i}'
261
+ if isinstance(b, str):
262
+ w.segments.add(seg_i, CONST_MAP_YOSYS2OBJ[b])
263
+ else:
264
+ self.net_number_mapping[module.name][b] = WireSegmentPath(raw=path)
265
+ w.create_wire_segment(seg_i)
266
+ else:
267
+ raise AttributeError(f'No bits entry found for wire {wire_name} in module {module.name}!')
268
+
269
+ module.add_wire(w)
270
+ LOG.debug(f'Built {len(module_dict["netnames"])} wires in {time() - start:.2f}s.')
271
+
272
+ def _build_ports(self, module: Module, module_dict: YosysModule) -> None:
273
+ if 'ports' in module_dict:
274
+ LOG.debug(f'Building {len(module_dict["ports"])} module ports...')
275
+ start = time()
276
+ for port_name, port_data in tqdm(module_dict['ports'].items(), desc='Port building progress'):
277
+ if CFG.simplify_escaped_identifiers:
278
+ port_name = self.simplify_port_name(port_name, f'{module.name}')
279
+ p_path = f'{module.name}.{port_name}'
280
+ direction = PortDirection.get(port_data['direction']) if 'direction' in port_data else PortDirection.UNKNOWN
281
+ msb_first = 'upto' not in port_data or port_data['upto'] != 1
282
+ p = Port(raw_path=p_path, direction=direction, msb_first=msb_first, module_or_instance=module)
283
+ module.add_port(p)
284
+ self._build_metadata(p, port_data)
285
+ self._build_parameters(p, port_data)
286
+ p.parameters['signed'] = port_data['signed'] if 'signed' in port_data else 0
287
+ if 'bits' in port_data:
288
+ offset = 0 if 'offset' not in port_data or port_data['offset'] is None else port_data['offset']
289
+ for i, b in enumerate(port_data['bits'], offset):
290
+ ps = p.create_port_segment(i)
291
+ if isinstance(b, str):
292
+ ps.change_connection(CONST_MAP_YOSYS2OBJ[b].path)
293
+ elif b in self.net_number_mapping[module.name]:
294
+ ws_path = self.net_number_mapping[module.name][b]
295
+ ps.change_connection(ws_path)
296
+ ws: WireSegment = module.get_from_path(ws_path)
297
+ ws.add_port_segment(ps)
298
+ else:
299
+ err_msg = f'No matching wire found for port {port_name} in module {module.name} and net number {b}!'
300
+ debug_msg = f'The netnumber-to-wire dictionary of this module is {self.net_number_mapping[module.name]}'
301
+ LOG.debug(err_msg)
302
+ LOG.debug(debug_msg)
303
+ raise AttributeError(err_msg)
304
+ else:
305
+ raise AttributeError(f'No bits entry found for port {port_name} in module {module.name}!')
306
+ LOG.debug(f'Built {len(module_dict["ports"])} module ports in {time() - start:.2f}s.')
307
+
308
+ def _build_instances(self, module: Module, module_dict: YosysModule) -> None:
309
+ if 'cells' in module_dict:
310
+ LibUtils.change_current_module(module)
311
+ LOG.debug(f'Building {len(module_dict["cells"])} instances...')
312
+ start = time()
313
+ for inst_name, inst_data in tqdm(module_dict['cells'].items(), desc='Instance building progress'):
314
+ self._build_single_instance(module, inst_name, inst_data)
315
+ LOG.debug(f'Built {len(module_dict["cells"])} instances in {time() - start:.2f}s.')
316
+
317
+ def _build_single_instance(self, module: Module, inst_name: str, inst_data: YosysCell) -> None:
318
+ # Replace illegal characters with internal identifer to indicate special characters
319
+ # TODO when writing to Verilog, put original characters back?
320
+ inst_name = inst_name.replace('.', CFG.id_internal).replace(':', CFG.id_internal)
321
+ type_str = inst_data['type'].replace('$', CFG.id_internal)
322
+ if type_str in TYPE_REPLACEMENT_MAP:
323
+ type_str = TYPE_REPLACEMENT_MAP[type_str]
324
+ if self._dict_must_be_prepared(type_str):
325
+ self._prepare_dict(type_str, inst_data)
326
+
327
+ # TODO mapping of different ffs
328
+ inst_path = f'{module.name}.{inst_name}'
329
+ inst = self._get_inst(type_str, inst_path)
330
+ inst.module = module
331
+ self._build_metadata(inst, inst_data)
332
+ self._build_parameters(inst, inst_data)
333
+ self._instance_post_processing(inst, inst_data)
334
+ self._build_instance_ports(module, inst, inst_data)
335
+ module.add_instance(inst)
336
+
337
+ def _build_instance_ports(self, module: Module, inst: Instance, instance_data_dict: YosysCell) -> None:
338
+ if 'port_directions' in instance_data_dict:
339
+ for port_name, port_connection in instance_data_dict['connections'].items():
340
+ # Default ports in primitives from gate library have placeholder segments
341
+ # They must be removed before assigning read data
342
+ if CFG.simplify_escaped_identifiers:
343
+ port_name = self.simplify_port_name(port_name, f'{module.name}.{inst.name}')
344
+ if port_name in inst.ports:
345
+ inst.ports[port_name].segments.clear()
346
+ port_direction = self._build_instance_ports_direction(instance_data_dict['port_directions'], port_name)
347
+ self._build_instance_ports_connections(module, inst, port_name, port_connection, port_direction)
348
+ else:
349
+ LOG.warn(f'Instance port dictionary is not complete for instance {inst.raw_path}!')
350
+
351
+ def _build_instance_ports_direction(self, pdirs: Dict[str, str], pname: str) -> PortDirection:
352
+ return PortDirection.UNKNOWN if pname not in pdirs else PortDirection.get(pdirs[pname])
353
+
354
+ def _build_instance_ports_connections(
355
+ self, module: Module, inst: Instance, port_name: str, connections: List[BitAlias], directions: PortDirection
356
+ ) -> None:
357
+ for i, b in enumerate(connections):
358
+ b_int = self._try_get_int(b)
359
+ if b_int in self.net_number_mapping[module.name]:
360
+ w_path = self.net_number_mapping[module.name][int(b_int)]
361
+ inst.connect(port_name, w_path, directions, i)
362
+ w_seg: WireSegment = module.get_from_path(w_path)
363
+ w_seg.add_port_segment(inst.ports[port_name][i])
364
+ elif b in CONST_MAP_YOSYS2OBJ.keys() and isinstance(b, str):
365
+ inst.connect(port_name, CONST_MAP_YOSYS2OBJ[b].path, directions, i)
366
+ else:
367
+ err_msg = f'No matching wire found for port {port_name} of instance {inst.raw_path} and net number {b}!'
368
+ debug_msg = f'The netnumber-to-wire dictionary of the module is {self.net_number_mapping[module.name]}'
369
+ LOG.debug(err_msg)
370
+ LOG.debug(debug_msg)
371
+ raise AttributeError(err_msg)
372
+
373
+ def _build_metadata(self, netlist_element: NetlistElement, dict: Dict[str, Dict[str, str]]) -> None:
374
+ if 'attributes' in dict:
375
+ netlist_element.metadata.add_category('yosys')
376
+ for attr_name, attr_val in dict['attributes'].items():
377
+ netlist_element.metadata.yosys[attr_name] = self._try_get_int(attr_val)
378
+
379
+ def _build_module_parameters(self, module: Module, module_dict: YosysModule) -> None:
380
+ if 'parameter_default_values' in module_dict:
381
+ module_dict['parameters'] = module_dict.pop('parameter_default_values')
382
+ self._build_parameters(module, module_dict)
383
+
384
+ def _build_parameters(self, dict_holder: NetlistElement, module_dict: AllYosysTypes) -> None:
385
+ if 'parameters' in module_dict:
386
+ for attr_name, attr_val in module_dict['parameters'].items(): # type:ignore
387
+ dict_holder.parameters[attr_name] = self._try_get_int(attr_val) # type:ignore
388
+
389
+ def _try_get_int(self, str_val: Union[str, int]) -> Union[int, str]:
390
+ if isinstance(str_val, int):
391
+ return str_val
392
+ if all(c == '0' or c == '1' for c in str_val) and str_val:
393
+ return int(str_val, 2)
394
+ return str_val
395
+
396
+ def _get_inst(self, type_str: str, inst_path: str) -> Instance:
397
+ is_primitive = type_str[0] == CFG.id_internal
398
+ if is_primitive and type_str not in self.module_definitions:
399
+ inst_cls: Instance = get(self._map_type2gatelib(type_str)) # type:ignore
400
+ return inst_cls(raw_path=inst_path, is_primitive=True, module=None) # type:ignore
401
+ else:
402
+ self._module_instantiations.add(type_str)
403
+ return Instance(raw_path=inst_path, instance_type=type_str, is_primitive=False, module=None)
404
+
405
+ def _map_type2gatelib(self, type_str: str) -> str:
406
+ if CFG.id_internal in type_str and 'dff' in type_str:
407
+ return f'{CFG.id_internal}dff'
408
+ return type_str
409
+
410
+ def _dict_must_be_prepared(self, inst_type: str) -> int:
411
+ return CFG.id_internal in inst_type and ('dff' in inst_type or 'mux' in inst_type)
412
+
413
+ def _prepare_dict(self, inst_type: str, inst_data: YosysCell) -> None:
414
+ if 'dff' in inst_type:
415
+ self._prepare_dff_dict(inst_type, inst_data)
416
+ if 'mux' in inst_type:
417
+ self._prepare_mux_dict(inst_type, inst_data)
418
+
419
+ def _prepare_dff_dict(self, ff_type: str, ff_dict: YosysCell) -> None:
420
+ if 'a' in ff_type: # FF with asyncronous reset
421
+ ff_dict['port_directions']['RST'] = ff_dict['port_directions'].pop('ARST')
422
+ ff_dict['connections']['RST'] = ff_dict['connections'].pop('ARST')
423
+
424
+ def _prepare_mux_dict(self, mux_type: str, mux_data: YosysCell) -> None:
425
+ mux_data['port_directions']['D_0'] = mux_data['port_directions'].pop('A')
426
+ mux_data['port_directions']['D_1'] = mux_data['port_directions'].pop('B')
427
+ mux_data['connections']['D_0'] = mux_data['connections'].pop('A')
428
+ mux_data['connections']['D_1'] = mux_data['connections'].pop('B')
429
+
430
+ def _instance_post_processing(self, inst: Instance, inst_data: YosysCell) -> None:
431
+ if isinstance(inst, DFF):
432
+ if 'ARST_VALUE' in inst_data['parameters']:
433
+ rst_val = self._try_get_int(inst_data['parameters']['ARST_VALUE'])
434
+ inst.rst_val_int = int(rst_val) # This should always be 1 or 0 -- if not, the exception is helpful :D
435
+ if 'ARST_POLARITY' in inst_data['parameters']:
436
+ rst_pol = self._try_get_int(inst_data['parameters']['ARST_POLARITY'])
437
+ inst.rst_polarity = Signal.get(rst_pol)
438
+ if 'CLK_POLARITY' in inst_data['parameters']:
439
+ clk_pol = self._try_get_int(inst_data['parameters']['CLK_POLARITY'])
440
+ inst.clk_polarity = Signal.get(clk_pol)
441
+ if 'EN_POLARITY' in inst_data['parameters']:
442
+ en_pol = self._try_get_int(inst_data['parameters']['EN_POLARITY'])
443
+ inst.en_polarity = Signal.get(en_pol)
444
+ if isinstance(inst, DLatch):
445
+ if 'EN_POLARITY' in inst_data['parameters']:
446
+ en_pol = self._try_get_int(inst_data['parameters']['EN_POLARITY'])
447
+ inst.en_polarity = Signal.get(en_pol)
448
+
449
+
450
+ TYPE_REPLACEMENT_MAP = {
451
+ '§_BUF_': '§buf',
452
+ }