Skip to content

Commit 3065ef0

Browse files
committed
[utils] Add python script to find potential issues in netlist
This script parses the generated netlist for suspicious constructs and counts the number of size_only instances. The script has been used with one synthesis tool only and might need adjustments for other synthesis tools. Further, the script relies on the preserved cells in the technology specific prim library to be called "u_size_only_*". Signed-off-by: Michael Gautschi <[email protected]>
1 parent 2a8cfaa commit 3065ef0

File tree

3 files changed

+256
-0
lines changed

3 files changed

+256
-0
lines changed

hw/ip/prim/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,3 +366,7 @@ This can be checked by the synthesis tool, e.g. `check_design -unloaded_comb/-un
366366
5. `lc_en_i`, `mubi_i` signals can only be connected to variables, or legal values (`MuBi4True`, `MuBi4False`, `On`, `Off`)
367367

368368
If all checks are successful, the same constraints can be applied to the full design.
369+
The script `utils/design/check-netlist.py` [check-netlist] can be used to report a summary of size_only cells in a netlist.
370+
It can also be used to check for suspicious constructs such as the checks (4) and (5) but it does **not** replace a final manual inspection of the netlist.
371+
372+
[check-netlist]: https://github.com/lowRISC/opentitan/tree/master/util/design#netlist-checker-script

util/design/README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,3 +297,44 @@ TODO
297297
## KECCAK Coefficient Generation Tool
298298

299299
TODO
300+
301+
## Netlist checker script
302+
303+
`check-netlist.py netlist.v` will report the number of preserved, `size_only` cells and parse the
304+
netlist for suspicious synthesis optimizations such as constant propagation accross preserved
305+
instances.
306+
307+
On the `prim_sdc_example.sv` design, the script produces the following output:
308+
309+
```
310+
================================================================================
311+
DISCLAIMER:
312+
This script parses a synthesized netlist for suspicious constructs.
313+
It does not guarantee that there are no issues in the netlist(!)
314+
================================================================================
315+
316+
================================================================================
317+
Final Summary:
318+
--------------------------------------------------------------------------------
319+
Found the following size_only instances:
320+
--------------------------------------------------------------------------------
321+
u_size_only_xor 120
322+
u_size_only_xnor 56
323+
u_size_only_and 56
324+
u_size_only_mux 0
325+
u_size_only_flop 252
326+
u_size_only_buf 328
327+
u_size_only_clock_gate 2
328+
others 2
329+
--------------------------------------------------------------------------------
330+
Total 816
331+
================================================================================
332+
Found 0 potential netlist problems in 0 modules!
333+
================================================================================
334+
```
335+
336+
If the script reports potential issues, it is likely because the synthesis constraints are not set
337+
correctly. Please refer to the sections [creating-a-technology-library] and [synthesis-constraints].
338+
339+
[creating-a-technology-library]: https://github.com/lowRISC/opentitan/tree/master/hw/ip/prim#creating-a-technology-library
340+
[synthesis-constraints]: https://github.com/lowRISC/opentitan/tree/master/hw/ip/prim#important-synthesis-constraints-to-keep-important-redundant-constructs

util/design/check-netlist.py

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
#!/usr/bin/env python3
2+
# Copyright lowRISC contributors (OpenTitan project).
3+
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
4+
# SPDX-License-Identifier: Apache-2.0
5+
r"""Checks the generated netlist for suspicious synthesis optimizations
6+
7+
Specifically, this script parses the netlist for suspicious patterns. These
8+
checks do *not* replace a final manual inspection of the netlist, but help to
9+
find problems early in the design!
10+
"""
11+
12+
import sys
13+
import re
14+
import argparse
15+
16+
17+
def hr(char: str) -> None:
18+
print(char * 80)
19+
20+
21+
class Parser:
22+
# List of patterns we want to use for finding size_only constraints.
23+
size_only_patterns = [
24+
"u_size_only",
25+
"u_size_only_xor", "u_size_only_xnor", "u_size_only_and",
26+
"u_size_only_mux", "u_size_only_flop", "u_size_only_buf",
27+
"u_size_only_clock_gate"
28+
]
29+
30+
# Regex to find ".lc_en_i, ( ... )" and capture the content
31+
pattern_lc_in = re.compile(r'\.lc_en_i \((.*?)\)')
32+
33+
# Regex to find ".mubi_i, ( ... )" and capture the content
34+
pattern_mubi_in = re.compile(r'\.mubi_i \((.*?)\)')
35+
36+
# Regex to find "assign mubi_o[*] = " const"
37+
pattern_mubi_const = re.compile(r'assign\s+([^=\s]*mubi_o[^=\s]*)\s*'
38+
f'=\s*(.*?)\'b[01]+;')
39+
40+
# Regex to find "assign test*_o[*] = " const"
41+
pattern_test_const = re.compile(r'assign\s+(test_.*_o.*?)\s*='
42+
r'\s*(.*?)\'b[01]+;')
43+
44+
# tie low/high pattern
45+
pattern_tie_low_high = re.compile(r"\'b[01]")
46+
47+
# pattern to find a module name
48+
pattern_module = re.compile(r'^\s*module\s+([a-zA-Z_][a-zA-Z0-9_]*)')
49+
50+
# valid mubi4, mubi8, mubi12 patterns
51+
mubi_allowed_patterns = ["12'b100101101001", "12'b11010010110",
52+
"8'b1101001", "8'b10010110",
53+
"4'b110", "4'b1001"]
54+
55+
# valid lc patterns
56+
lc_allowed_patterns = ["4'b101", "4'b1010"]
57+
58+
# A count of how many instances we have seen of each of the size_only
59+
# patterns (ordered the same as size_only_patterns).
60+
size_only_count: list[int] = [0 for pat in size_only_patterns]
61+
62+
# The name of the module (if known)
63+
module_name: str | None = None
64+
65+
# Number of errors seen so far
66+
errors: int = 0
67+
68+
# A list of module names where errors have been found
69+
error_modules: set[str] = set()
70+
71+
def describe_module(self) -> str:
72+
return ('unknown module' if self.module_name is None
73+
else f'module {self.module_name}')
74+
75+
def on_error(self,
76+
linenum: int, line: str,
77+
desc: str, bad_match: str | None) -> None:
78+
print(f"Error: {desc} in {self.describe_module()}, line {linenum}:")
79+
print(f" Full line: {line.strip()}")
80+
if bad_match is not None:
81+
print(f" Content parsed: {bad_match}")
82+
83+
hr('-')
84+
85+
self.errors += 1
86+
if self.module_name is not None:
87+
self.error_modules.add(self.module_name)
88+
89+
def take_line(self, line_number: int, line: str) -> None:
90+
# Parse for module name: "module xxx(..."
91+
match_module = Parser.pattern_module.match(line)
92+
if match_module:
93+
self.module_name = match_module.group(1)
94+
95+
# check for constant lc signals
96+
lc_match = Parser.pattern_lc_in.search(line)
97+
if lc_match:
98+
content = lc_match.group(1).strip()
99+
if Parser.pattern_tie_low_high.search(content):
100+
if content not in Parser.lc_allowed_patterns:
101+
self.on_error(line_number, line,
102+
'invalid constant lc_en_i', content)
103+
104+
# check for constant mubi_i signals
105+
mubi_i_match = Parser.pattern_mubi_in.search(line)
106+
if mubi_i_match:
107+
content = mubi_i_match.group(1).strip()
108+
if Parser.pattern_tie_low_high.search(content):
109+
if content not in Parser.mubi_allowed_patterns:
110+
self.on_error(line_number, line,
111+
'invalid constant mubi_i', content)
112+
113+
# check for constant mubi_o signals
114+
if Parser.pattern_mubi_const.search(line):
115+
self.on_error(line_number, line, 'tied low/high mubi bit', None)
116+
117+
# check for constant test-outputs in prim_sdc_example
118+
if ((self.module_name == "prim_sdc_example" and
119+
Parser.pattern_test_const.search(line))):
120+
self.on_error(line_number, line, 'tied low/high test*_o*', None)
121+
122+
# Count size_only instances
123+
for i, pattern in enumerate(Parser.size_only_patterns):
124+
if pattern in line:
125+
self.size_only_count[i] += 1
126+
127+
def report(self) -> None:
128+
hr('=')
129+
print("Final Summary:")
130+
hr('-')
131+
print("Found the following size_only instances:")
132+
hr('-')
133+
134+
# Print the number of specific size_only matches that we have seen
135+
# (with a suffix from _xor, _xnor, _only_and, ...)
136+
specific_count = 0
137+
for i, pattern in enumerate(Parser.size_only_patterns[1:], 1):
138+
print(f"{pattern:<30}{self.size_only_count[i]}")
139+
specific_count += self.size_only_count[i]
140+
141+
# Each of those specific hits will also have matched u_size_only (the
142+
# first pattern). Subtracting specific_count from size_only_count[0]
143+
# gives the number of lines that are match u_size_only but don't match
144+
# one of the specific patterns.
145+
other_count = self.size_only_count[0] - specific_count
146+
print(f"{'others':<30}{other_count}")
147+
148+
hr('-')
149+
150+
# Since we know that every match for a specific case will also match
151+
# the first pattern, the number of lines that match equals
152+
# size_only_count[0]
153+
print(f"{'Total':<30}{self.size_only_count[0]}")
154+
hr('=')
155+
156+
print(f"Found {self.errors} potential netlist problems "
157+
f"in {len(self.error_modules)} modules!")
158+
hr('=')
159+
160+
161+
def parse_expressions(filename):
162+
try:
163+
with open(filename, 'r') as file:
164+
lines = list(file)
165+
except FileNotFoundError:
166+
print(f"Error: The file '{filename}' was not found.")
167+
return 1
168+
169+
parser = Parser()
170+
for idx, line in enumerate(lines, 1):
171+
parser.take_line(idx, line)
172+
parser.report()
173+
174+
return parser.errors
175+
176+
177+
if __name__ == "__main__":
178+
179+
parser = argparse.ArgumentParser(
180+
description="Parses a synthesized netlist for suspicious constructs and counts all size_only "
181+
"instances.",
182+
epilog="Note: This script does not guarantee that there are no issues in the netlist (!)"
183+
)
184+
185+
parser.add_argument(
186+
"netlist_filename",
187+
type=str,
188+
help="The path to the netlist file (e.g., netlist.v) to be parsed."
189+
)
190+
191+
args = parser.parse_args()
192+
193+
194+
hr('=')
195+
print("DISCLAIMER:\n"
196+
"This script parses a synthesized netlist for suspicious constructs.\n"
197+
"It does not guarantee that there are no issues in the netlist(!)")
198+
hr('=')
199+
print('\n')
200+
201+
if len(sys.argv) < 2:
202+
print("Usage: ./check-netlist.py <netlist_filename>")
203+
sys.exit(2)
204+
205+
file_to_parse = sys.argv[1]
206+
errors = parse_expressions(file_to_parse)
207+
208+
if (errors > 0):
209+
sys.exit(1) # Exits with 1 on error
210+
else:
211+
sys.exit(0) # Exits with 0 on success

0 commit comments

Comments
 (0)