From ba5e4eb2d07a7480ab824ac4d3b93740bd58abf0 Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Thu, 23 Jul 2020 09:32:57 +0200 Subject: [PATCH 1/5] scripts/liberty: Send all 'messages' to stderr Since this script will be used in Makefile to generate machine parseable output, it's importatnt that all 'user' messages are sent to stderr to be differentiated from the 'machine' output Signed-off-by: Sylvain Munaut --- .../skywater_pdk/liberty.py | 68 +++++++++++-------- 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/scripts/python-skywater-pdk/skywater_pdk/liberty.py b/scripts/python-skywater-pdk/skywater_pdk/liberty.py index 77f2bb5..fc012f5 100755 --- a/scripts/python-skywater-pdk/skywater_pdk/liberty.py +++ b/scripts/python-skywater-pdk/skywater_pdk/liberty.py @@ -41,6 +41,20 @@ debug = False LOG2_10 = log2(10) +def msg_debug(*args, **kwargs): + if debug: + kwargs['file'] = sys.stderr + print("[d]", *args if args else "", **kwargs) + +def msg_info( *args, **kwargs): + kwargs['file'] = sys.stderr + print("[+]" if args else "", *args, **kwargs) + +def msg_err( *args, **kwargs): + kwargs['file'] = sys.stderr + print("[!]" if args else "", *args, **kwargs) + + class TimingType(enum.IntFlag): """ @@ -248,7 +262,7 @@ def collect(library_dir) -> Tuple[Dict[str, TimingType], List[str]]: if not missing: continue - print("Missing", ", ".join(missing), "from", corner, corner_types) + msg_err("Missing", ", ".join(missing), "from", corner, corner_types) return libname0, corners, all_cells @@ -257,7 +271,7 @@ def collect(library_dir) -> Tuple[Dict[str, TimingType], List[str]]: fname = cell_corner_file(libname0, cell_with_size, corner, corner_type) fpath = os.path.join(library_dir, fname) if not os.path.exists(fpath) and debug: - print("Missing", (fpath, corner, corner_type, corner_types)) + msg_err("Missing", (fpath, corner, corner_type, corner_types)) timing_dir = os.path.join(library_dir, "timing") assert os.path.exists(timing_dir), timing_dir @@ -266,7 +280,7 @@ def collect(library_dir) -> Tuple[Dict[str, TimingType], List[str]]: fname = top_corner_file(libname0, corner, corner_type) fpath = os.path.join(library_dir, fname) if not os.path.exists(fpath) and debug: - print("Missing", (fpath, corner, corner_type, corner_types)) + msg_err("Missing", (fpath, corner, corner_type, corner_types)) return libname0, corners, all_cells @@ -297,7 +311,7 @@ def remove_ccsnoise_from_dict(data, dataname): for k in ccsn_keys: if debug: - print("{:s}: Removing {}".format(dataname, k)) + msg_info("{:s}: Removing {}".format(dataname, k)) del data[k] @@ -332,7 +346,7 @@ def generate(library_dir, lib, corner, ocorner_type, icorner_type, cells, output print("\n".join(lines), file=top_fout) otype_str = "({} from {})".format(ocorner_type.name, icorner_type.names()) - print("Starting to write", top_fpath, otype_str, flush=True) + msg_info("Starting to write", top_fpath, otype_str) common_data = {} @@ -352,7 +366,7 @@ def generate(library_dir, lib, corner, ocorner_type, icorner_type, cells, output assert isinstance(d, dict) for k, v in d.items(): if k in common_data: - print("Overwriting", k, "with", v, "(existing value of", common_data[k], ")") + msg_info("Overwriting", k, "with", v, "(existing value of", common_data[k], ")") common_data[k] = v # Remove the ccsnoise if it exists @@ -388,8 +402,8 @@ def generate(library_dir, lib, corner, ocorner_type, icorner_type, cells, output top_write(['']) top_write(['}']) top_fout.close() - print(" Finish writing", top_fpath, flush=True) - print("") + msg_info(" Finish writing", top_fpath) + msg_info() # * The 'delay_model' should be the 1st attribute in the library @@ -997,16 +1011,16 @@ def liberty_dict(dtype, dvalue, data, indent=tuple(), attribute_types=None): di = [attr_sort_key(i) for i in data.items()] di.sort() - if debug: - print(" "*len(str(indent)), "s1 s2 ", "%-40s" % "ktype", '%-40r' % "kvalue", "value") - print("-"*len(str(indent)), "---- ---- ", "-"*40, "-"*40, "-"*44) - for sk, kt, skv, kv, k, v in di: - print(str(indent), "%4.0f %4.0f --" % sk, "%-40s" % kt, '%-40r' % kv, end=" ") - sv = str(v) - print(sv[:40], end=" ") - if len(sv) > 40: - print('...', end=" ") - print() + + msg_debug(" "*len(str(indent)), "s1 s2 ", "%-40s" % "ktype", '%-40r' % "kvalue", "value") + msg_debug("-"*len(str(indent)), "---- ---- ", "-"*40, "-"*40, "-"*44) + for sk, kt, skv, kv, k, v in di: + msg_debug(str(indent), "%4.0f %4.0f --" % sk, "%-40s" % kt, '%-40r' % kv, end=" ") + sv = str(v) + msg_debug(sv[:40], end=" ") + if len(sv) > 40: + msg_debug('...', end=" ") + msg_debug() # Output all the attributes @@ -1135,26 +1149,26 @@ def main(): for acorner in args.corner: if acorner in corners: continue - print() - print("Unknown corner:", acorner) + msg_err() + msg_err("Unknown corner:", acorner) retcode = 1 if retcode != 0: args.corner.clear() if not args.corner: - print() - print("Available corners for", lib+":") + msg_info() + msg_info("Available corners for", lib+":") for k, v in sorted(corners.items()): - print(" -", k, v[0].describe()) - print() + msg_info(" -", k, v[0].describe()) + msg_info() return retcode + msg_info("Generating", output_corner_type.name, "liberty timing files for", lib, "at", ", ".join(args.corner)) + msg_info() - print("Generating", output_corner_type.name, "liberty timing files for", lib, "at", ", ".join(args.corner)) - print() for corner in args.corner: input_corner_type, corner_cells = corners[corner] if output_corner_type not in input_corner_type: - print("Corner", corner, "doesn't support", output_corner_type, "(only {})".format(input_corner_type)) + msg_err("Corner", corner, "doesn't support", output_corner_type, "(only {})".format(input_corner_type)) return 1 if output_corner_type == TimingType.basic and TimingType.ccsnoise in input_corner_type: From 9a4821ec6c933d9885234b8e1c970ece57598ac1 Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Thu, 23 Jul 2020 09:39:05 +0200 Subject: [PATCH 2/5] scripts/liberty: Accept target output file as argument Instead of giving the library and corner to gnerate, you can provide the target .lib file you want generated and this will pick the right parameters to generate that file Signed-off-by: Sylvain Munaut --- .../skywater_pdk/liberty.py | 58 ++++++++++++++++--- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/scripts/python-skywater-pdk/skywater_pdk/liberty.py b/scripts/python-skywater-pdk/skywater_pdk/liberty.py index fc012f5..4c46b2a 100755 --- a/scripts/python-skywater-pdk/skywater_pdk/liberty.py +++ b/scripts/python-skywater-pdk/skywater_pdk/liberty.py @@ -194,6 +194,36 @@ def top_corner_file(libname, corner, corner_type: TimingType, directory_prefix = corner_type=corner_type.file) +def top_liberty_file(libname, corner, corner_type: TimingType, directory_prefix = "timing"): + """ + + >>> top_liberty_file("sky130_fd_sc_hd", "ff_100C_1v65", TimingType.ccsnoise) + 'timing/sky130_fd_sc_hd__ff_100C_1v65_ccsnoise.lib' + >>> top_liberty_file("sky130_fd_sc_hd", "ff_100C_1v65", TimingType.basic) + 'timing/sky130_fd_sc_hd__ff_100C_1v65.lib' + >>> top_liberty_file("sky130_fd_sc_hd", "ff_100C_1v65", TimingType.basic, "") + 'sky130_fd_sc_hd__ff_100C_1v65.lib' + + """ + return top_corner_file(libname, corner, corner_type, directory_prefix).replace('.lib.json', '.lib') + + +def top_liberty_file_split(fname): + """ + Splits a fully specified top liberty file name into library / corner / corner_type + + >>> top_liberty_file_split('timing/sky130_fd_sc_hd__ff_100C_1v65_ccsnoise.lib') + ('sky130_fd_sc_hd', 'ff_100C_1v65', ) + >>> top_liberty_file_split('timing/sky130_fd_sc_hd__ff_100C_1v65.lib') + ('sky130_fd_sc_hd', 'ff_100C_1v65', ) + + """ + m = re.match('(.*)__(.*)\.lib', os.path.basename(fname)) + if m is None: + return None + return ( m.group(1), *TimingType.parse(m.group(2)) ) + + def collect(library_dir) -> Tuple[Dict[str, TimingType], List[str]]: """Collect the available timing information in corners. @@ -337,7 +367,7 @@ remove_ccsnoise_from_library = remove_ccsnoise_from_dict def generate(library_dir, lib, corner, ocorner_type, icorner_type, cells, output_directory): output_directory_prefix = None if output_directory else "timing" - top_fname = top_corner_file(lib, corner, ocorner_type, output_directory_prefix).replace('.lib.json', '.lib') + top_fname = top_liberty_file(lib, corner, ocorner_type, output_directory_prefix) output_directory = output_directory if output_directory else library_dir top_fpath = os.path.join(output_directory, top_fname) @@ -1133,14 +1163,28 @@ def main(): retcode = 0 - lib, corners, all_cells = collect(libdir) + if libdir.is_dir(): + # libdir is a directory, collect available corners and process specified ones + if args.ccsnoise: + output_corner_type = TimingType.ccsnoise + elif args.leakage: + output_corner_type = TimingType.leakage + else: + output_corner_type = TimingType.basic + + elif libdir.suffix == '.lib': + # libdir is directly the liberty target file to generate, derive params from it + s = top_liberty_file_split(libdir.name) + + libdir = libdir.parents[1] + args.corner = [ s[1] ] + output_corner_type = s[2] - if args.ccsnoise: - output_corner_type = TimingType.ccsnoise - elif args.leakage: - output_corner_type = TimingType.leakage else: - output_corner_type = TimingType.basic + msg_err("Invalid library path (neither source directory or liberty library target") + return 1 + + lib, corners, all_cells = collect(libdir) if args.corner == ['all']: args.corner = list(sorted(k for k, (v0, v1) in corners.items() if output_corner_type in v0)) From 72d1fd69cea3a52d4cb6673b964a466178c12178 Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Thu, 23 Jul 2020 09:41:09 +0200 Subject: [PATCH 3/5] scripts/liberty: Add argument to get list of possible target liberty files Instead of a "human" aimed list of available corners, this generates a machine readable list of liberty files that can be generated for a given library. Signed-off-by: Sylvain Munaut --- .../skywater_pdk/liberty.py | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/scripts/python-skywater-pdk/skywater_pdk/liberty.py b/scripts/python-skywater-pdk/skywater_pdk/liberty.py index 4c46b2a..64318ed 100755 --- a/scripts/python-skywater-pdk/skywater_pdk/liberty.py +++ b/scripts/python-skywater-pdk/skywater_pdk/liberty.py @@ -1154,6 +1154,11 @@ def main(): help="Sets the parent directory of the liberty files", default="") + parser.add_argument( + "--list-targets", + help="List buildable target liberty files", + action='store_true', + default=False) args = parser.parse_args() if args.debug: global debug @@ -1200,14 +1205,19 @@ def main(): args.corner.clear() if not args.corner: - msg_info() - msg_info("Available corners for", lib+":") - for k, v in sorted(corners.items()): - msg_info(" -", k, v[0].describe()) - msg_info() - return retcode - msg_info("Generating", output_corner_type.name, "liberty timing files for", lib, "at", ", ".join(args.corner)) - msg_info() + if not args.list_targets: + msg_info() + msg_info("Available corners for", lib+":") + for k, v in sorted(corners.items()): + msg_info(" -", k, v[0].describe()) + msg_info() + return retcode + else: + for k, v in sorted(corners.items()): + for t in TimingType: + if t in v[0]: + print(libdir.joinpath(top_liberty_file(lib, k, t))) + return retcode for corner in args.corner: input_corner_type, corner_cells = corners[corner] From 711e294becf40272f445754c01c90bcde6746487 Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Thu, 23 Jul 2020 09:42:12 +0200 Subject: [PATCH 4/5] scripts/liberty: Add option to output Makefile dependency list Signed-off-by: Sylvain Munaut --- .../skywater_pdk/liberty.py | 41 ++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/scripts/python-skywater-pdk/skywater_pdk/liberty.py b/scripts/python-skywater-pdk/skywater_pdk/liberty.py index 64318ed..2840277 100755 --- a/scripts/python-skywater-pdk/skywater_pdk/liberty.py +++ b/scripts/python-skywater-pdk/skywater_pdk/liberty.py @@ -436,6 +436,19 @@ def generate(library_dir, lib, corner, ocorner_type, icorner_type, cells, output msg_info() +def deps(library_dir, lib, corner, ocorner_type, icorner_type, cells, output_directory): + output_directory_prefix = None if output_directory else "timing" + output_directory = output_directory if output_directory else library_dir + top_out_path = os.path.join(output_directory, top_liberty_file(lib, corner, ocorner_type, output_directory_prefix)) + common_data_path = os.path.join(output_directory, output_directory_prefix, "{}__common.lib.json".format(lib)) + top_data_path = os.path.join(output_directory, top_corner_file(lib, corner, icorner_type, output_directory_prefix)) + cell_paths_lst = [ + os.path.join(library_dir, cell_corner_file(lib, cell_with_size, corner, icorner_type)) + for cell_with_size in cells + ] + print(f"{top_out_path:s} {top_out_path.replace('.lib','.d'):s}: {common_data_path:s} {top_data_path:s} {' '.join(cell_paths_lst):s}") + + # * The 'delay_model' should be the 1st attribute in the library # * The 'technology' should be the 1st attribute in the library @@ -1159,6 +1172,12 @@ def main(): help="List buildable target liberty files", action='store_true', default=False) + parser.add_argument( + "--gen-deps", + help="Generate dependency file", + action='store_true', + default=False) + args = parser.parse_args() if args.debug: global debug @@ -1219,6 +1238,10 @@ def main(): print(libdir.joinpath(top_liberty_file(lib, k, t))) return retcode + if not args.gen_deps: + msg_info("Generating", output_corner_type.name, "liberty timing files for", lib, "at", ", ".join(args.corner)) + msg_info() + for corner in args.corner: input_corner_type, corner_cells = corners[corner] if output_corner_type not in input_corner_type: @@ -1230,11 +1253,19 @@ def main(): else: input_corner_type = output_corner_type - generate( - libdir, lib, - corner, output_corner_type, input_corner_type, - corner_cells, args.output_directory - ) + if args.gen_deps: + deps( + libdir, lib, + corner, output_corner_type, input_corner_type, + corner_cells, args.output_directory, + ) + else: + generate( + libdir, lib, + corner, output_corner_type, input_corner_type, + corner_cells, args.output_directory, + ) + return 0 From 7d9b9500c2d9d649ad6a0d9feb1ca5858c2669ea Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Sun, 5 Dec 2021 20:31:37 +0100 Subject: [PATCH 5/5] Makefile: Rework liberty timing generation logic This now uses the python script to list all possible liberty files that can be generated for all libraries that are checked out. Each has its own target so parallel build work as expected and each has its own dependency list that should trigger a rebuild if any of the source file is updated. Signed-off-by: Sylvain Munaut --- Makefile | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/Makefile b/Makefile index ae7d4f7..e83bf9a 100644 --- a/Makefile +++ b/Makefile @@ -78,29 +78,30 @@ check: check-licenses lint-python all: README.rst @true +.PHONY: all -SC_LIBS = $(sort $(notdir $(wildcard libraries/sky130_*_sc_*))) -$(SC_LIBS): | $(CONDA_ENV_PYTHON) - @$(IN_CONDA_ENV) for V in libraries/$@/*; do \ - if [ -d "$$V/cells" ]; then \ - python -m skywater_pdk.liberty $$V; \ - python -m skywater_pdk.liberty $$V all; \ - python -m skywater_pdk.liberty $$V all --ccsnoise; \ - fi \ - done - -sky130_fd_sc_ms-leakage: | $(CONDA_ENV_PYTHON) - @$(IN_CONDA_ENV) for V in libraries/sky130_fd_sc_ms/*; do \ - if [ -d "$$V/cells" ]; then \ - python -m skywater_pdk.liberty $$V all --leakage; \ - fi \ - done - -sky130_fd_sc_ms: sky130_fd_sc_ms-leakage +SC_LIBS = $(foreach lib, $(dir $(wildcard libraries/*/*/timing)), $(shell $(IN_CONDA_ENV) python -m skywater_pdk.liberty --list-targets $(lib))) timing: $(SC_LIBS) | $(CONDA_ENV_PYTHON) @true +.PHONY: timing -.PHONY: all +libraries/%.lib: + @$(IN_CONDA_ENV) python -m skywater_pdk.liberty $@ + +libraries/%.d: + @$(IN_CONDA_ENV) python -m skywater_pdk.liberty --gen-deps $(@:.d=.lib) > $@ + +include $(SC_LIBS:.lib=.d) + +clean:: + @rm -f $(SC_LIBS) $(SC_LIBS:.lib=.d) + +.PHONY: clean + +dist-clean:: + @rm -f $(SC_LIBS) $(SC_LIBS:.lib=.d) + +.PHONY: dist-clean