1
- from dataclasses import dataclass , field
2
1
import json
2
+ import os
3
+ import re
4
+ import subprocess
5
+ from dataclasses import dataclass , field
3
6
from pathlib import Path
4
7
from typing import Literal
5
8
9
+ from loguru import logger
10
+
11
+ from FABulous .FABulous_CLI .helper import check_if_application_exists
6
12
13
+ """Type alias for Yosys bit vectors containing integers or logic values."""
7
14
BitVector = list [int | Literal ["0" , "1" , "x" , "z" ]]
8
15
9
16
10
17
@dataclass
11
18
class YosysPortDetails :
19
+ """
20
+ Represents port details in a Yosys module.
21
+
22
+ Attributes:
23
+ direction: Port direction - "input", "output", or "inout"
24
+ bits: Bit vector representing the port's signals
25
+ offset: Bit offset for multi-bit ports (default: 0)
26
+ upto: Upper bound for bit ranges (default: 0)
27
+ signed: Whether the port is signed (0=unsigned, 1=signed, default: 0)
28
+ """
29
+
12
30
direction : Literal ["input" , "output" , "inout" ]
13
31
bits : BitVector
14
32
offset : int = 0
@@ -18,19 +36,45 @@ class YosysPortDetails:
18
36
19
37
@dataclass
20
38
class YosysCellDetails :
39
+ """
40
+ Represents a cell instance in a Yosys module.
41
+
42
+ Cells are instantiated components like logic gates, flip-flops, or user-defined modules.
43
+
44
+ Attributes:
45
+ hide_name: Whether to hide the cell name in output (1=hide, 0=show)
46
+ type: Cell type/primitive name (e.g., "AND", "DFF", custom module name)
47
+ parameters: Cell parameters as string key-value pairs
48
+ attributes: Cell attributes including metadata and synthesis directives
49
+ connections: Port connections mapping port names to bit vectors
50
+ port_directions: Direction of each port ("input", "output", "inout")
51
+ model: Associated model name (optional, default: "")
52
+ """
53
+
21
54
hide_name : Literal [1 , 0 ]
22
55
type : str
23
56
parameters : dict [str , str ]
24
57
attributes : dict [str , str | int ]
25
58
connections : dict [str , BitVector ]
26
- port_directions : dict [str , Literal ["input" , "output" , "inout" ]] = field (
27
- default_factory = dict
28
- )
59
+ port_directions : dict [str , Literal ["input" , "output" , "inout" ]] = field (default_factory = dict )
29
60
model : str = ""
30
61
31
62
32
63
@dataclass
33
64
class YosysMemoryDetails :
65
+ """
66
+ Represents memory block details in a Yosys module.
67
+
68
+ Memory blocks are inferred or explicitly instantiated memory elements.
69
+
70
+ Attributes:
71
+ hide_name: Whether to hide the memory name in output (1=hide, 0=show)
72
+ attributes: Memory attributes and metadata
73
+ width: Data width in bits
74
+ start_offset: Starting address offset
75
+ size: Memory size (number of addressable locations)
76
+ """
77
+
34
78
hide_name : Literal [1 , 0 ]
35
79
attributes : dict [str , str ]
36
80
width : int
@@ -40,6 +84,20 @@ class YosysMemoryDetails:
40
84
41
85
@dataclass
42
86
class YosysNetDetails :
87
+ """
88
+ Represents net/wire details in a Yosys module.
89
+
90
+ Nets are the connections between cells and ports in the design.
91
+
92
+ Attributes:
93
+ hide_name: Whether to hide the net name in output (1=hide, 0=show)
94
+ bits: Bit vector representing the net's signals
95
+ attributes: Net attributes including unused bit information
96
+ offset: Bit offset for multi-bit nets (default: 0)
97
+ upto: Upper bound for bit ranges (default: 0)
98
+ signed: Whether the net is signed (0=unsigned, 1=signed, default: 0)
99
+ """
100
+
43
101
hide_name : Literal [1 , 0 ]
44
102
bits : BitVector
45
103
attributes : dict [str , str ]
@@ -50,16 +108,41 @@ class YosysNetDetails:
50
108
51
109
@dataclass
52
110
class YosysModule :
111
+ """
112
+ Represents a module in a Yosys design.
113
+
114
+ A module contains the structural description of a digital circuit including
115
+ its interface (ports), internal components (cells), memory blocks, and
116
+ interconnections (nets).
117
+
118
+ Attributes:
119
+ attributes: Module attributes and metadata (e.g., "top" for top module)
120
+ parameter_default_values: Default values for module parameters
121
+ ports: Dictionary mapping port names to YosysPortDetails
122
+ cells: Dictionary mapping cell names to YosysCellDetails
123
+ memories: Dictionary mapping memory names to YosysMemoryDetails
124
+ netnames: Dictionary mapping net names to YosysNetDetails
125
+ """
126
+
53
127
attributes : dict [str , str | int ]
54
128
parameter_default_values : dict [str , str | int ]
55
129
ports : dict [str , YosysPortDetails ]
56
130
cells : dict [str , YosysCellDetails ]
57
131
memories : dict [str , YosysMemoryDetails ]
58
132
netnames : dict [str , YosysNetDetails ]
59
133
60
- def __init__ (
61
- self , * , attributes , parameter_default_values , ports , cells , memories , netnames
62
- ):
134
+ def __init__ (self , * , attributes , parameter_default_values , ports , cells , memories , netnames ):
135
+ """
136
+ Initialize a YosysModule from parsed JSON data.
137
+
138
+ Args:
139
+ attributes: Module attributes dictionary
140
+ parameter_default_values: Parameter defaults dictionary
141
+ ports: Ports dictionary (will be converted to YosysPortDetails objects)
142
+ cells: Cells dictionary (will be converted to YosysCellDetails objects)
143
+ memories: Memories dictionary (will be converted to YosysMemoryDetails objects)
144
+ netnames: Netnames dictionary (will be converted to YosysNetDetails objects)
145
+ """
63
146
self .attributes = attributes
64
147
self .parameter_default_values = parameter_default_values
65
148
self .ports = {k : YosysPortDetails (** v ) for k , v in ports .items ()}
@@ -70,14 +153,63 @@ def __init__(
70
153
71
154
@dataclass
72
155
class YosysJson :
156
+ """
157
+ Root object representing a complete Yosys JSON file.
158
+
159
+ This class provides the main interface for loading and analyzing Yosys JSON
160
+ netlists. It contains all modules in the design and provides utility methods
161
+ for common netlist analysis tasks.
162
+
163
+ Attributes:
164
+ srcPath: Path to the source JSON file
165
+ creator: Tool that created the JSON (usually "Yosys")
166
+ modules: Dictionary mapping module names to YosysModule objects
167
+ models: Dictionary of behavioral models (implementation-specific)
168
+ """
169
+
73
170
srcPath : Path
74
171
creator : str
75
172
modules : dict [str , YosysModule ]
76
173
models : dict
77
174
78
175
def __init__ (self , path : Path ):
176
+ """
177
+ Load and parse a HDL file to a Yosys JSON object.
178
+
179
+ Args:
180
+ path: Path to a HDL file
181
+
182
+ Raises:
183
+ FileNotFoundError: If the JSON file doesn't exist
184
+ json.JSONDecodeError: If the file contains invalid JSON
185
+ """
186
+
79
187
self .srcPath = path
80
- with open (path , "r" ) as f :
188
+ yosys = check_if_application_exists (os .getenv ("FAB_YOSYS_PATH" , "yosys" ))
189
+
190
+ json_file = self .srcPath .with_suffix (".json" )
191
+
192
+ if self .srcPath .suffix in [".v" , ".sv" ]:
193
+ runCmd = [
194
+ yosys ,
195
+ "-q" ,
196
+ f"-p read_verilog -sv { self .srcPath } ; proc -noopt; write_json -compat-int { json_file } " ,
197
+ ]
198
+ elif self .srcPath .suffix in [".vhd" , ".vhdl" ]:
199
+ runCmd = [
200
+ yosys ,
201
+ "-m" ,
202
+ "ghdl-q" ,
203
+ f"-p ghdl { self .srcPath } ; proc -noopt; write_json -compat-int { json_file } " ,
204
+ ]
205
+ else :
206
+ raise ValueError (f"Unsupported HDL file type: { self .srcPath .suffix } " )
207
+ try :
208
+ subprocess .run (runCmd , check = True )
209
+ except subprocess .CalledProcessError as e :
210
+ logger .opt (exception = subprocess .CalledProcessError (1 , runCmd )).error (f"Failed to run yosys command: { e } " )
211
+
212
+ with open (json_file , "r" ) as f :
81
213
o = json .load (f )
82
214
self .creator = o .get ("creator" , "" ) # Use .get() for safety
83
215
# Provide default empty dicts for potentially missing keys in module data
@@ -95,37 +227,85 @@ def __init__(self, path: Path):
95
227
self .models = o .get ("models" , {}) # Use .get() for safety
96
228
97
229
def getTopModule (self ) -> YosysModule :
230
+ """
231
+ Find and return the top-level module in the design.
232
+
233
+ The top module is identified by having a "top" attribute.
234
+
235
+ Returns:
236
+ YosysModule: The top-level module
237
+
238
+ Raises:
239
+ ValueError: If no top module is found in the design
240
+ """
98
241
for module in self .modules .values ():
99
242
if "top" in module .attributes :
100
243
return module
101
244
raise ValueError ("No top module found in Yosys JSON" )
102
245
103
246
def isTopModuleNet (self , net : int ) -> bool :
247
+ """
248
+ Check if a net ID corresponds to a top-level module port.
249
+
250
+ Args:
251
+ net: Net ID to check
252
+
253
+ Returns:
254
+ bool: True if the net is connected to a top module port, False otherwise
255
+ """
104
256
for module in self .modules .values ():
105
257
for pDetail in module .ports .values ():
106
258
if net in pDetail .bits :
107
259
return True
108
260
return False
109
261
110
262
def isNetUsed (self , net : int ) -> bool :
263
+ """
264
+ Determine if a net is actually used in the design.
265
+
266
+ This method checks the "unused_bits" attribute to determine if a specific
267
+ net bit is marked as unused by Yosys optimization.
268
+
269
+ Args:
270
+ net: Net ID to check
271
+
272
+ Returns:
273
+ bool: True if the net is used, False if it's marked as unused
274
+ """
111
275
for module in self .modules .values ():
112
276
for net_name , net_details in module .netnames .items ():
113
277
if net in net_details .bits and "unused_bits" in net_details .attributes :
114
278
if r := re .search (r"\w+\[(\d+)\]" , net_name ):
115
- if int (r .group (1 )) in [
116
- int (i )
117
- for i in net_details .attributes ["unused_bits" ].split (" " )
118
- ]:
279
+ if int (r .group (1 )) in [int (i ) for i in net_details .attributes ["unused_bits" ].split (" " )]:
119
280
return False
120
281
121
282
else :
122
283
if "0 " in net_details .attributes ["unused_bits" ]:
123
284
return False
124
285
return True
125
286
126
- def getNetPortSrcSinks (
127
- self , net : int
128
- ) -> tuple [tuple [str , str ], list [tuple [str , str ]]]:
287
+ def getNetPortSrcSinks (self , net : int ) -> tuple [tuple [str , str ], list [tuple [str , str ]]]:
288
+ """
289
+ Find the source and sink connections for a given net.
290
+
291
+ This method analyzes the netlist to determine what drives a net (source)
292
+ and what it connects to (sinks).
293
+
294
+ Args:
295
+ net: Net ID to analyze
296
+
297
+ Returns:
298
+ tuple: A tuple containing:
299
+ - Source: (cell_name, port_name) tuple for the driving cell/port
300
+ - Sinks: List of (cell_name, port_name) tuples for driven cells/ports
301
+
302
+ Raises:
303
+ ValueError: If net is not found or has multiple drivers
304
+
305
+ Note:
306
+ If no driver is found, the source will be ("", "z") indicating
307
+ a high-impedance or undriven net.
308
+ """
129
309
src : list [tuple [str , str ]] = []
130
310
sinks : list [tuple [str , str ]] = []
131
311
for module in self .modules .values ():
@@ -138,9 +318,7 @@ def getNetPortSrcSinks(
138
318
sinks .append ((cell_name , conn_name ))
139
319
140
320
if len (sinks ) == 0 :
141
- raise ValueError (
142
- f"Net { net } not found in Yosys JSON or is a top module port output"
143
- )
321
+ raise ValueError (f"Net { net } not found in Yosys JSON or is a top module port output" )
144
322
145
323
if len (src ) == 0 :
146
324
src .append (("" , "z" ))
0 commit comments