Skip to content

Commit a1b7338

Browse files
Move HDL reading logic and fix
1 parent 822fc8a commit a1b7338

File tree

2 files changed

+200
-30
lines changed

2 files changed

+200
-30
lines changed

FABulous/fabric_definition/Yosys_obj.py

Lines changed: 196 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,32 @@
1-
from dataclasses import dataclass, field
21
import json
2+
import os
3+
import re
4+
import subprocess
5+
from dataclasses import dataclass, field
36
from pathlib import Path
47
from typing import Literal
58

9+
from loguru import logger
10+
11+
from FABulous.FABulous_CLI.helper import check_if_application_exists
612

13+
"""Type alias for Yosys bit vectors containing integers or logic values."""
714
BitVector = list[int | Literal["0", "1", "x", "z"]]
815

916

1017
@dataclass
1118
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+
1230
direction: Literal["input", "output", "inout"]
1331
bits: BitVector
1432
offset: int = 0
@@ -18,19 +36,45 @@ class YosysPortDetails:
1836

1937
@dataclass
2038
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+
2154
hide_name: Literal[1, 0]
2255
type: str
2356
parameters: dict[str, str]
2457
attributes: dict[str, str | int]
2558
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)
2960
model: str = ""
3061

3162

3263
@dataclass
3364
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+
3478
hide_name: Literal[1, 0]
3579
attributes: dict[str, str]
3680
width: int
@@ -40,6 +84,20 @@ class YosysMemoryDetails:
4084

4185
@dataclass
4286
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+
43101
hide_name: Literal[1, 0]
44102
bits: BitVector
45103
attributes: dict[str, str]
@@ -50,16 +108,41 @@ class YosysNetDetails:
50108

51109
@dataclass
52110
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+
53127
attributes: dict[str, str | int]
54128
parameter_default_values: dict[str, str | int]
55129
ports: dict[str, YosysPortDetails]
56130
cells: dict[str, YosysCellDetails]
57131
memories: dict[str, YosysMemoryDetails]
58132
netnames: dict[str, YosysNetDetails]
59133

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+
"""
63146
self.attributes = attributes
64147
self.parameter_default_values = parameter_default_values
65148
self.ports = {k: YosysPortDetails(**v) for k, v in ports.items()}
@@ -70,14 +153,63 @@ def __init__(
70153

71154
@dataclass
72155
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+
73170
srcPath: Path
74171
creator: str
75172
modules: dict[str, YosysModule]
76173
models: dict
77174

78175
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+
79187
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:
81213
o = json.load(f)
82214
self.creator = o.get("creator", "") # Use .get() for safety
83215
# Provide default empty dicts for potentially missing keys in module data
@@ -95,37 +227,85 @@ def __init__(self, path: Path):
95227
self.models = o.get("models", {}) # Use .get() for safety
96228

97229
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+
"""
98241
for module in self.modules.values():
99242
if "top" in module.attributes:
100243
return module
101244
raise ValueError("No top module found in Yosys JSON")
102245

103246
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+
"""
104256
for module in self.modules.values():
105257
for pDetail in module.ports.values():
106258
if net in pDetail.bits:
107259
return True
108260
return False
109261

110262
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+
"""
111275
for module in self.modules.values():
112276
for net_name, net_details in module.netnames.items():
113277
if net in net_details.bits and "unused_bits" in net_details.attributes:
114278
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(" ")]:
119280
return False
120281

121282
else:
122283
if "0 " in net_details.attributes["unused_bits"]:
123284
return False
124285
return True
125286

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+
"""
129309
src: list[tuple[str, str]] = []
130310
sinks: list[tuple[str, str]] = []
131311
for module in self.modules.values():
@@ -138,9 +318,7 @@ def getNetPortSrcSinks(
138318
sinks.append((cell_name, conn_name))
139319

140320
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")
144322

145323
if len(src) == 0:
146324
src.append(("", "z"))

FABulous/fabric_generator/file_parser.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,9 @@ def parseFabricCSV(fileName: str) -> Fabric:
138138
if i[0].startswith("Tile"):
139139
if "GENERATE" in i:
140140
# import here to avoid circular import
141-
141+
from FABulous.fabric_generator.fabric_automation import (
142+
generateCustomTileConfig,
143+
)
142144
# we generate the tile right before we parse everything
143145
i[1] = str(generate_custom_tile_config(filePath.joinpath(i[1])))
144146

@@ -1020,17 +1022,7 @@ def parseBelFile(
10201022

10211023
if filetype == "verilog":
10221024
# Runs yosys on verilog file, creates netlist, saves to json in same directory.
1023-
json_file = filename.with_suffix(".json")
1024-
runCmd = [
1025-
"yosys",
1026-
f"-qpread_verilog -sv {filename}; proc -noopt; write_json -compat-int {json_file}",
1027-
]
1028-
try:
1029-
subprocess.run(runCmd, check=True)
1030-
except subprocess.CalledProcessError as e:
1031-
logger.error(f"Failed to run yosys command: {e}")
1032-
raise ValueError
1033-
hdlData = YosysJson(json_file)
1025+
hdlData = YosysJson(filename)
10341026

10351027
modules: dict[str, YosysModule] = hdlData.modules
10361028
filtered_ports = {}

0 commit comments

Comments
 (0)