blob: 6a0792d3bb9092b035b5655f80d22a2790b1d8e9 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2020 The Skywater PDK Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import cairosvg
import re
import simplejson as json
#import split_inouts as split_io
import subprocess
from pathlib import Path
import argparse
from glob import glob
from common import files, lib_extract_from_path
debug = False
debug_print = lambda x: print(x) if debug else 0
dummy_verilog = os.path.dirname(os.path.realpath(__file__)) + "/dummy_verilog.v"
def call_with_retcode(cmd):
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
stderr = stderr.decode("utf-8")
retcode = p.wait()
return retcode, stderr
def use_netlistsvg(json_file, out_file):
"""Draw module schematics using netlistsvg. Operates on json files. returns retcode"""
cmd = ["netlistsvg", json_file, "-o", out_file]
return call_with_retcode(cmd)
def use_yosys(command, files):
"""Execute yosys command, returns retcode"""
actions = "read_verilog {}".format(" ".join(files)) + "; hierarchy -check -auto-top; " + command
cmd = ["yosys", "-f", "verilog", "-p", actions]
return call_with_retcode(cmd)
def connect_delayed_ports(verilog_file, out_file):
conn = {}
finish = False
out = ""
with open(verilog_file, 'r') as in_f:
lines = in_f.read()
lines = lines.split("\n")
for line in lines:
if line.find("endmodule") != -1:
for src, dest in conn.items():
out += f"assign {dest} = {src};\n"
out += "\n"
finish = True
if not finish:
if re.search(r"^\s*(\$recrem|\$setuphold)", line):
args = line[line.find("(") + 1: line.rfind(")")].split(",")
if len(args) <= 7:
continue
srcs = [x.strip().split(" ")[1] for x in args[:2]]
targts = [x.strip() for x in args[-2:]]
for x in range(len(srcs)):
if targts[x] not in conn:
conn[srcs[x]] = targts[x]
out += line + "\n"
with open(out_file, 'w') as out_f:
out_f.write(out)
def remove_loose_ports(json_file, out_file = None):
if out_file == None:
out_file = json_file
with open(json_file, 'r') as in_f:
design = json.load(in_f)
for mod_name in design["modules"]:
used_ports= []
mod = design["modules"][mod_name]
for cell in mod["cells"]:
for value in mod["cells"][cell]["connections"].values():
used_ports += value
# only first module should be checked - it is always top one
to_del = []
for port in mod["ports"]:
if mod["ports"][port]["bits"][0] not in used_ports:
to_del.append(port)
for x in to_del:
del design["modules"][mod_name]["ports"][x]
break
output = json.dumps(design, sort_keys=True, indent=2)
with open(out_file, 'w') as out_f:
out_f.write(output)
def base_includes(verilog_file):
version_path_pos = re.search(r"/V[0-2]\.[0-2]\.[0-2]", verilog_file)
if version_path_pos is None:
return []
base_list = verilog_file[:version_path_pos.end()] +"/.base"
debug_print(base_list)
modules_to_include = []
with open(base_list, 'r') as in_f:
modules_to_include = in_f.read().split("\n")
# Guard from file duplication
try:
modules_to_include.remove(verilog_file)
except ValueError:
pass
return modules_to_include
def draw_schematics(verilog_file):
"""Draw module schematics using yosys+netlistsvg. Operates on verilog files.
Output file is placed in same directory as input"""
connect_delayed_ports(verilog_file, "temp.v")
retcode, err = use_yosys("prep; splitinout; write_json temp.json",
["temp.v" , dummy_verilog])
if retcode > 0:
if err.rfind("is not part of the design") != -1:
# add missing modules
retcode, err = use_yosys("prep; splitinout; write_json temp.json",
["temp.v" , dummy_verilog] + base_includes(verilog_file))
if retcode > 0:
debug_print(err)
return retcode
else:
debug_print(err)
return retcode
lib = lib_extract_from_path(verilog_file)
json_correct("temp.json", lib)
remove_loose_ports("temp.json")
dest_path = verilog_file.replace(".simple.v", ".sch.png")
if not retcode:
retcode, err = use_netlistsvg("temp.json", "temp.svg")
if retcode != 0:
# inout are not split when they are not connected to anything - empty design
if err.find('direction"') == -1:
debug_print(err)
# check if design contains any connections
with open("temp.svg", 'r') as in_f:
is_empty = in_f.read().find('<line x1="') == -1
if not is_empty:
cairosvg.svg2png(url="temp.svg", write_to=dest_path)
else:
return 2
return retcode
def json_correct(json_file, lib):
with open(json_file, 'r') as in_f:
file_as_text = in_f.read()
output = re.sub(r'"top": "00000000000000000000000000000001"', '"top": 1', file_as_text)
# replace floating lines with unknown
output = re.sub(r'\[ "z" \]', '[ "x" ]', output)
# remove libname from modules name
output = re.sub(rf"{lib}_", "", output)
with open(json_file, 'w') as out_f:
out_f.write(output)
# def split_inouts(in_file, out_file = None):
# """Split inout ports into inputs and outputs. Operates on yosys json files"""
# if out_file == None:
# out_file = in_file
# with open(in_file, 'r') as in_f:
# design = json.load(in_f)
# top_name = split_io.find_top_module(design)
# port_map, net_map = split_io.find_and_split_inout_ports_and_nets(design, top_name)
# output = json.dumps(design, sort_keys=True, indent=2)
# output = re.sub(r'"00000000000000000000000000000001"', "1", output)
# with open(out_file, 'w') as out_f:
# out_f.write(output)
if __name__ == "__main__":
debug = False
parser = argparse.ArgumentParser()
parser.add_argument(
"input",
help="The path to the source directory/file. If file is passed it will run in debug mode with no output file" +
"If dir is passed it will find '.simple.v' files and generate '.sch.png' in the same directory",
type=Path)
args = parser.parse_args()
if not os.path.isdir(args.input):
debug = True
draw_schematics(args.input)
else:
files = args.input.rglob('*.simple.v')
count = 0
empty = []
failed = []
for f in files:
f = str(f)
count+=1
retcode = draw_schematics(f)
if retcode == 0:
print("Drawn:", f)
elif retcode == 2:
print("Design_empty:", f)
empty.append(f)
else:
# creating schematics failed due to lines stripped from .v.full
print("Design incomplete:", f)
failed.append(f)
if debug:
print("*"*60)
print("Done")
print("*"*60)
print("Empty")
for x in empty:
print(f"\t{x}")
print("*"*60)
print("Failed")
for x in failed:
print(f"\t{x}")
print("*"*60)
print(f"Failed for { len(failed)} - {len(failed)*1.0 /count}")
print(f"Empty for { len(empty)} - {len(empty)*1.0 /count}")
# remove temp files
os.remove("temp.json")
os.remove("temp.v")
os.remove("temp.svg")