diff --git a/flow/Makefile b/flow/Makefile index 759e13578d..11e28f8878 100644 --- a/flow/Makefile +++ b/flow/Makefile @@ -292,6 +292,34 @@ $(RESULTS_DIR)/1_1_yosys_canonicalize.rtlil: $(RESULTS_DIR)/memories.json $(OBJECTS_DIR)/klayout.lyt: $(RESULTS_DIR)/memories.json endif +# OpenROAD stages read binary liberty (.blib) files from a cache shared by +# all designs in $(WORK_HOME)/objects/binary_libs, see +# scripts/write_binary_libs.tcl. Yosys reads the text libraries. +.PHONY: binary-libs +binary-libs: + $(UNSET_AND_MAKE) do-binary-libs + +.PHONY: do-binary-libs +do-binary-libs: + @mkdir -p $(LOG_DIR) + $(RUN_CMD) --log $(abspath $(LOG_DIR)/write_binary_libs.tmp.log) --tee -- $(OPENROAD_CMD) $(SCRIPTS_DIR)/write_binary_libs.tcl + mv $(LOG_DIR)/write_binary_libs.tmp.log $(LOG_DIR)/write_binary_libs.log + +# do-binary-libs renames its log into place only when it succeeds, so a +# failed conversion runs again. clean_all and clean_binary_libs delete the +# log with the cache it records, so the next make fills the cache again. +$(LOG_DIR)/write_binary_libs.log: $(LIB_FILES) \ + $(foreach corner,$(shell echo $(CORNERS) | tr '[:lower:]' '[:upper:]'),$($(corner)_LIB_FILES)) + $(UNSET_AND_MAKE) do-binary-libs + +# Order-only, so converting never reruns the flow. +$(RESULTS_DIR)/1_synth.odb: | $(LOG_DIR)/write_binary_libs.log + +.PHONY: clean_binary_libs +clean_binary_libs: + rm -rf $(WORK_HOME)/objects/binary_libs + rm -f $(LOG_DIR)/write_binary_libs.log + .PHONY: do-yosys do-yosys: yosys-dependencies $(SCRIPTS_DIR)/synth.sh $(SYNTH_SCRIPT) $(LOG_DIR)/1_2_yosys.log @@ -852,6 +880,7 @@ clean: .PHONY: clean_all clean_all: clean_synth clean_floorplan clean_place clean_cts clean_route clean_finish clean_metadata clean_abstract rm -rf $(OBJECTS_DIR) + rm -f $(LOG_DIR)/write_binary_libs*.log .PHONY: nuke nuke: clean_test clean_issues diff --git a/flow/scripts/read_liberty.tcl b/flow/scripts/read_liberty.tcl index 1c013611dd..60816842ff 100644 --- a/flow/scripts/read_liberty.tcl +++ b/flow/scripts/read_liberty.tcl @@ -5,7 +5,7 @@ if { [env_var_exists_and_non_empty CORNERS] } { foreach corner $::env(CORNERS) { set LIBKEY "[string toupper $corner]_LIB_FILES" foreach libFile $::env($LIBKEY) { - log_cmd read_liberty -corner $corner $libFile + log_cmd read_liberty -corner $corner [lib_file_to_read $libFile] } unset LIBKEY } @@ -13,7 +13,7 @@ if { [env_var_exists_and_non_empty CORNERS] } { } else { ## no corner foreach libFile $::env(LIB_FILES) { - log_cmd read_liberty $libFile + log_cmd read_liberty [lib_file_to_read $libFile] } } diff --git a/flow/scripts/util.tcl b/flow/scripts/util.tcl index 293572e39e..4f984cbee4 100644 --- a/flow/scripts/util.tcl +++ b/flow/scripts/util.tcl @@ -506,3 +506,46 @@ proc synth_verilog_files { } { } return $copies } + +# Entry of lib_file in the binary liberty (.blib) cache shared by all designs: +# objects/binary_libs//@--.blib, so a +# changed library gets a new entry and same-named libraries from different +# directories don't collide. Libraries in the design's directory and those the +# flow generates are cached per run. +proc binary_lib_path { lib_file } { + set source [file normalize $lib_file] + set objects [file join $::env(WORK_HOME) objects] + set root [file join $objects binary_libs $::env(PLATFORM)] + set run_dirs [list $::env(DESIGN_DIR) $objects [file join $::env(WORK_HOME) results]] + foreach dir $run_dirs { + if { [string first "[file normalize $dir]/" $source] == 0 } { + set root [file join $::env(OBJECTS_DIR) binary_libs] + break + } + } + set name [file tail $source] + regsub {\.gz$} $name "" name + set name [file rootname $name] + set dir_hash [format %08x [zlib crc32 [file dirname $source] 0]] + return [file join $root "$name@[file size $source]-[file mtime $source]-$dir_hash.blib"] +} + +# Whether this OpenROAD reads blib. An entry missing or written by another +# version is expected, so its error is not reported. +proc binary_lib_usable { blib } { + suppress_message STA 1900 + suppress_message STA 1902 + set usable [expr { ![catch { liberty_binary_info $blib }] }] + unsuppress_message STA 1900 + unsuppress_message STA 1902 + return $usable +} + +# The cached .blib for lib_file, or lib_file when there is no usable .blib +proc lib_file_to_read { lib_file } { + set blib [binary_lib_path $lib_file] + if { [binary_lib_usable $blib] } { + return $blib + } + return $lib_file +} diff --git a/flow/scripts/write_binary_libs.tcl b/flow/scripts/write_binary_libs.tcl new file mode 100644 index 0000000000..a663fdc10a --- /dev/null +++ b/flow/scripts/write_binary_libs.tcl @@ -0,0 +1,40 @@ +# Fill the binary liberty (.blib) cache with the libraries the OpenROAD stages read +source $::env(SCRIPTS_DIR)/util.tcl + +set lib_files {} +if { [env_var_exists_and_non_empty CORNERS] } { + foreach corner $::env(CORNERS) { + lappend lib_files {*}$::env([string toupper $corner]_LIB_FILES) + } +} else { + lappend lib_files {*}$::env(LIB_FILES) +} + +set written 0 +set cached 0 +array set seen {} +foreach lib_file $lib_files { + set blib [binary_lib_path $lib_file] + if { [info exists seen($blib)] } { + continue + } + set seen($blib) 1 + if { [binary_lib_usable $blib] } { + incr cached + continue + } + # Publish with a rename, so concurrent flows never read a partial file + set tmp "$blib.tmp.[info hostname].[pid]" + if { + [catch { + file mkdir [file dirname $blib] + log_cmd write_liberty_binary $lib_file $tmp + file rename -force $tmp $blib + } msg] + } { + file delete -force $tmp + error "Could not write $blib: $msg" + } + incr written +} +puts "Binary liberty files: $written written, $cached already cached" diff --git a/flow/util/genReport.py b/flow/util/genReport.py index 52ec6115dd..cf70e628bc 100755 --- a/flow/util/genReport.py +++ b/flow/util/genReport.py @@ -299,13 +299,15 @@ def write_summary(): # check if design ran to completion without errors or warnings d["log_errors"] = list() d["log_warnings"] = list() - d["last_log"] = "" + d["last_log"] = max( + (name for name in files if name.endswith(".log")), + key=lambda name: os.path.getmtime(os.path.join(log_dir, name)), + default="", + ) for name_ in sorted(files): temp_e, temp_w = parse_messages(os.path.join(log_dir, name_)) d["log_errors"] += temp_e d["log_warnings"] += temp_w - if name_.endswith(".log"): - d["last_log"] = name_ d["finished"] = any(name in files for name in LAST_EXPECTED_LOG) # check if metrics generation had issues