Skip to content

Commit 0562f19

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 0562f19

File tree

3 files changed

+187
-0
lines changed

3 files changed

+187
-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: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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 checks do *not*
8+
replace a final manual inspection of the netlist, but help to find problems early in the design!
9+
"""
10+
11+
import sys
12+
import re
13+
14+
def parse_expressions(filename):
15+
# list of all relevant preserved instances (Adjust to the names in the used prim implementation)
16+
size_only_patterns = ["u_size_only", "u_size_only_xor", "u_size_only_xnor", "u_size_only_and",
17+
"u_size_only_mux", "u_size_only_flop", "u_size_only_buf",
18+
"u_size_only_clock_gate"]
19+
20+
# Regex to find ".lc_en_i, ( ... )" and capture the content
21+
pattern_lc_in = re.compile(r'\.lc_en_i \((.*?)\)')
22+
# Regex to find ".mubi_i, ( ... )" and capture the content
23+
pattern_mubi_in = re.compile(r'\.mubi_i \((.*?)\)')
24+
# Regex to find "assign mubi_o[*] = " const"
25+
pattern_mubi_const = re.compile(r'assign\s+([^=\s]*mubi_o[^=\s]*)\s*=\s*(.*?)\'b[01]+;')
26+
# Regex to find "assign test*_o[*] = " const"
27+
pattern_test_const = re.compile(r'assign\s+(test_.*_o.*?)\s*=\s*(.*?)\'b[01]+;')
28+
# tie low/high pattern
29+
pattern_tie_low_high = re.compile(r"\'b[01]")
30+
# pattern to find a module name
31+
pattern_module = re.compile(r'^\s*module\s+([a-zA-Z_][a-zA-Z0-9_]*)')
32+
# valid mubi4, mubi8, mubi12, lc patterns
33+
mubi_allowed_patterns = ["12'b100101101001", "12'b11010010110", "8'b1101001", "8'b10010110",
34+
"4'b110", "4'b1001"]
35+
lc_allowed_patterns = ["4'b101", "4'b1010"]
36+
37+
errors = 0
38+
size_only_count = [0]*len(size_only_patterns)
39+
40+
module_name = "unknown"
41+
error_modules = [] # List to store module names when errors are found
42+
43+
try:
44+
with open(filename, 'r') as file:
45+
lines = list(file)
46+
for line_number, line in enumerate(lines, 1):
47+
# Parse for module name: "module xxx(..."
48+
match_module = pattern_module.match(line)
49+
if match_module:
50+
module_name = match_module.group(1)
51+
52+
# check for constant lc signals
53+
match = pattern_lc_in.search(line)
54+
if match:
55+
content = match.group(1).strip()
56+
if pattern_tie_low_high.search(content):
57+
if content not in lc_allowed_patterns:
58+
print(f"Error: Found invalid constant lc_en_i in module {module_name}"
59+
f"on line {line_number}:")
60+
print(f" Full line: {line.strip()}")
61+
print(f" Content parsed: {content}")
62+
print("-" * 80)
63+
errors += 1
64+
if module_name not in error_modules:
65+
error_modules.append(module_name)
66+
67+
# check for constant mubi signals
68+
match = pattern_mubi_in.search(line)
69+
if match:
70+
content = match.group(1).strip()
71+
if pattern_tie_low_high.search(content):
72+
if content not in mubi_allowed_patterns:
73+
print(f"Error: Found invalid constant mubi_i in module {module_name}"
74+
f"on line {line_number}:")
75+
print(f" Full line: {line.strip()}")
76+
print(f" Content parsed: {content}")
77+
print("-" * 80)
78+
errors += 1
79+
if module_name not in error_modules:
80+
error_modules.append(module_name)
81+
82+
# check for constant mubi signals
83+
match = pattern_mubi_const.search(line)
84+
if match:
85+
print(f"Error: Found tied low/high mubi bit in module {module_name}"
86+
f"on line {line_number}:")
87+
print(f" Full line: {line.strip()}")
88+
print("-" * 80)
89+
errors += 1
90+
if module_name not in error_modules:
91+
error_modules.append(module_name)
92+
93+
# check for constant test-outputs in prim_sdc_example
94+
if (module_name == "prim_sdc_example"):
95+
match = pattern_test_const.search(line)
96+
if match:
97+
print(f"Error: Found tied low/high test*_o* in module {module_name}"
98+
f"on line {line_number}:")
99+
print(f" Full line: {line.strip()}")
100+
print("-" * 80)
101+
errors += 1
102+
if module_name not in error_modules:
103+
error_modules.append(module_name)
104+
105+
# check for size_only instances
106+
for i in range(len(size_only_patterns)):
107+
if size_only_patterns[i] in line:
108+
size_only_count[i] += 1
109+
110+
111+
print("\n" + "="*80)
112+
print("Final Summary:")
113+
print("-"*80)
114+
print("Found the following size_only instances:")
115+
print("-"*80)
116+
for i in range(1, len(size_only_patterns)):
117+
print(f"{size_only_patterns[i]:<30}{size_only_count[i]}")
118+
other_count = size_only_count[0] - sum(size_only_count[1:len(size_only_count)])
119+
print(f"{'others':<30}{other_count}")
120+
print("-"*80)
121+
print(f"{'Total':<30}{size_only_count[0]}")
122+
print("="*80)
123+
print(f"Found {errors} potential netlist problems in {len(error_modules)} modules!")
124+
print("="*80)
125+
126+
except FileNotFoundError:
127+
print(f"Error: The file '{filename}' was not found.")
128+
129+
return errors
130+
131+
if __name__ == "__main__":
132+
print("="*80)
133+
print("DISCLAIMER:\nThis script parses a synthesized netlist for suspicious constructs.\nIt "\
134+
"does not guarantee that there are no issues in the netlist(!)")
135+
print("="*80+'\n\n')
136+
if len(sys.argv) < 2:
137+
print("Usage: ./check-netlist.py <netlist_filename>")
138+
sys.exit(2)
139+
file_to_parse = sys.argv[1]
140+
errors = parse_expressions(file_to_parse)
141+
if (errors > 0):
142+
sys.exit(1)

0 commit comments

Comments
 (0)