# Copyright 2020-2022 Efabless Corporation # # 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 # # http://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. # warn about deprecated configs and preserve backwards compatibility proc throw_error {} { if { [info exists ::env(EXIT_ON_ERROR)] && $::env(EXIT_ON_ERROR) } { flow_fail } else { return -code error } } proc handle_deprecated_config {old new {default ""}} { if { [info exists ::env($old)] } { puts_warn "The variable name $old was renamed to $new\. Update your configuration file." if { ! [info exists ::env($new)] } { set ::env($new) $::env($old) } if { $::env($new) != $::env($old) } { puts_err "Conflicting values of $new and $old; please remove $old from your design configurations" throw_error } } elseif { [info exists ::env($new)] } { # That's fine } elseif { $default != "" } { set ::env($new) $default } } proc handle_deprecated_pdk_config {old new} { if { [info exists ::env($old)] } { puts_warn "$old is now deprecated; use $new instead." set ::env($new) $::env($old) return 1 } return 0 } proc handle_diode_insertion_strategy {} { if { [info exists ::env(DIODE_INSERTION_STRATEGY)] } { puts_warn "DIODE_INSERTION_STRATEGY is now deprecated; use GRT_REPAIR_ANTENNAS, DIODE_ON_PORTS and RUN_HEURISTIC_DIODE_INSERTION instead." set strategy $::env(DIODE_INSERTION_STRATEGY) if { $strategy == 1 | $strategy == 5 | $strategy == 2 } { puts_err "DIODE_INSERTION_STRATEGY $strategy is no longer supported" throw_error } if { $strategy == 3 | $strategy == 6 } { puts_info "DIODE_INSERTION_STRATEGY set to $strategy. Setting GRT_REPAIR_ANTENNAS to 1" set ::env(GRT_REPAIR_ANTENNAS) 1 } if { $strategy == 4 | $strategy == 6 } { puts_info "DIODE_INSERTION_STRATEGY set to $strategy. Setting RUN_HEURISTIC_DIODE_INSERTION to 1" puts_info "DIODE_INSERTION_STRATEGY set to $strategy. Setting DIODE_ON_PORTS to in" set ::env(RUN_HEURISTIC_DIODE_INSERTION) 1 set ::env(DIODE_ON_PORTS) "in" } if { $strategy == 0 } { puts_info "DIODE_INSERTION_STRATEGY set to $strategy. Setting GRT_REPAIR_ANTENNAS to 0" set ::env(GRT_REPAIR_ANTENNAS) 0 set ::env(DIODE_ON_PORTS) "none" } } } proc find_all {ext} { if { ! [info exists ::env(RUN_DIR)] } { puts_err "You are not currently running a design. Perhaps you forgot to run 'prep'?" throw_error } return [exec find $::env(RUN_DIR) -name "*.$ext" | sort | xargs realpath --relative-to=$::env(PWD)] } proc handle_deprecated_command {args} { set new [lindex $args 0] set insert_args [lrange $args 1 end] set invocation [info level -1] set caller [lindex $invocation 0] set caller_args [lrange $invocation 1 end] set final_args [list {*}$insert_args {*}$caller_args] puts_warn "The command $caller is now deprecated; use $new instead." eval {$new {*}$final_args} } proc set_if_unset {var default_value} { upvar $var x if {! [info exists x] } { set x $default_value } } # create an array out of a list proc add_to_env {my_array} { foreach {key value} [array get my_array] { set $::env($key) $value } } # helper function for argument parsing proc is_keyword_arg { arg } { if { [string length $arg] >= 2 \ && [string index $arg 0] == "-" \ && [string is alpha [string index $arg 1]] } { return 1 } else { return 0 } } proc extract_pins_from_yosys_netlist {netlist_file} { # This sed command works because the module herader in a # yosys-generated netlist is on one line. return [list [exec sed -E -n {/^module/ s/module[[:space:]]+[^[:space:]]+[[:space:]]*\((.*)\);/\1/pg}\ $netlist_file \ | tr -d ',']] } # parse arguments # adopted from https://github.com/The-OpenROAD-Project/OpenSTA/blob/77f22e482e8d48d29f2810d871a22847f1bdd74a/tcl/Util.tcl#L31 proc parse_key_args {cmd arg_var key_var options {flag_var ""} {flags {}} {consume_args_flag "-consume"}} { upvar 1 $arg_var args upvar 1 $key_var key_value upvar 1 $flag_var flag_present set args_copy $args set keys {} foreach option $options { set option_name [lindex $option 0] if { [lsearch -exact $option required ] >= 0} { set key_index [lsearch -exact $args [lindex $option 0]] if {$key_index < 0} { puts_err "$cmd missing required $option_name" throw_error } } lappend keys $option_name } set args_rtn {} while { $args != "" } { set arg [lindex $args 0] if { [is_keyword_arg $arg] } { set key_index [lsearch -exact $keys $arg] if { $key_index >= 0 } { set key $arg if { [llength $args] == 1 } { puts_err "$cmd $key missing value." throw_error } set key_value($key) [lindex $args 1] set args [lrange $args 1 end] } else { set flag_index [lsearch -exact $flags $arg] if { $flag_index >= 0 } { set flag_present($arg) 1 } } } else { lappend args_rtn $arg } set args [lrange $args 1 end] } if { $consume_args_flag == "-no_consume" } { set args $args_copy } else { set args $args_rtn } return -code ok } # Sets a variable in a certain scope, # but also logs it to the global configuration file. proc set_and_log {var val} { set val_escaped [string map {"\\" "\\\\"} $val] set val_escaped [string map {"\$" "\\\$"} $val_escaped] set val_escaped [string map {"\"" "\\\""} $val_escaped] set val_escaped [string map {"\[" "\\\["} $val_escaped] set cmd "set ${var} \"${val_escaped}\"" uplevel #0 ${cmd} set global_cfg_file [open $::env(GLB_CFG_FILE) a+] puts $global_cfg_file $cmd close $global_cfg_file } proc log_exec {args} { if { ! [catch { set cmd_log_file [open $::env(RUN_DIR)/cmds.log a+] } ]} { set timestamp [clock format [clock seconds]] puts $cmd_log_file "$timestamp - Executing \"$args\"\n" close $cmd_log_file } } proc catch_exec {args} { # Logs invocation to command log. # Emplaces an array, "exec_result", in the calling context. # The array has two keys, exit_code and out. upvar exec_result exec_result log_exec $args set exec_result(exit_code) [catch {eval exec $args} exec_result(out)] } proc try_exec {args} { # Logs invocation to command log. # Fails on non-zero exit codes: prints the last 10 lines and throws an error. # Returns nothing on success. log_exec $args set exit_code [catch {eval exec $args} error_msg] if { $exit_code } { set tool [string range $args 0 [string first " " $args]] set print_error_msg "during executing: \"$args\"" puts_err "$print_error_msg" puts_err "Exit code: $exit_code" puts_err "Last 10 lines:\n[exec tail -10 << $error_msg]\n" throw_error } } proc try_catch {args} { handle_deprecated_command try_exec } proc relpath {args} { set from [lindex $args 0] set to [lindex $args 1] return [exec python3 -c "import os; print(os.path.relpath('$to', '$from'), end='')"] } proc run_yosys_script {args} { run_tcl_script -tool yosys -no_consume {*}$args } proc run_openroad_script {args} { run_tcl_script -tool openroad -no_consume {*}$args } proc run_sta_script {args} { run_tcl_script -tool sta -no_consume {*}$args } proc run_magic_script {args} { set options { {-indexed_log required} } set flags {} parse_key_args "run_magic_script" args arg_values $options flag_map $flags set ::env(MAGIC_SCRIPT) [lindex $args 0] if { ![file exists $::env(MAGIC_SCRIPT)] } { puts_err "Magic script $::env(MAGIC_SCRIPT) doesn't exist" } run_tcl_script -tool magic -no_consume $::env(SCRIPTS_DIR)/magic/wrapper.tcl -indexed_log $arg_values(-indexed_log) unset ::env(MAGIC_SCRIPT) } proc increment_index {args} { set ::env(CURRENT_INDEX) [expr 1 + $::env(CURRENT_INDEX)] puts "\[STEP $::env(CURRENT_INDEX)\]" } proc index_file {args} { set file_full_name [lindex $args 0] if { $file_full_name == "/dev/null" } { # Can't index that :) return $file_full_name } set file_path [file dirname $file_full_name] set fbasename [file tail $file_full_name] set fbasename "$::env(CURRENT_INDEX)-$fbasename" set new_file_full_name "$file_path/$fbasename" set replace [string map {/ \\/} $::env(CURRENT_INDEX)] if { [info exists ::env(GLB_CFG_FILE)]} { exec sed -i.bak -e "s/\\(set ::env(CURRENT_INDEX)\\).*/\\1 $replace/" "$::env(GLB_CFG_FILE)" exec rm -f "$::env(GLB_CFG_FILE).bak" } return $new_file_full_name } proc flow_fail {args} { if { ! [info exists ::env(FLOW_FAILED)] || ! $::env(FLOW_FAILED) } { set ::env(FLOW_FAILED) 1 calc_total_runtime -status "flow failed" save_final_views generate_final_summary_report save_state "Fail State" puts_err "Flow failed." show_warnings "The failure may have been because of the following warnings:" exit -1 } } proc calc_total_runtime {args} { ## Calculate Total Runtime if {[info exists ::env(timer_start)] && [info exists ::env(START_TIME)]} { puts_verbose "Calculating runtime..." set ::env(timer_end) [clock seconds] set options { {-report optional} {-status optional} } parse_key_args "calc_total_runtime" args arg_values $options set_if_unset arg_values(-report) $::env(REPORTS_DIR)/total_runtime.txt set_if_unset arg_values(-status) "flow completed" catch_exec python3 $::env(SCRIPTS_DIR)/write_runtime.py --conclude --seconds --time-in $::env(timer_end) $arg_values(-status) if { $exec_result(exit_code) } { puts_err "Failed to calculate total runtime:" puts_err "$exec_result(out)" } } } # Value Color # 0 Black # 1 Red ******* # 2 Green ******* # 3 Yellow ******* # 4 Blue # 5 Magenta # 6 Cyan ******* # 7 White # 8 Not used # 9 Reset to default color proc color_text {color txt} { if {[info exists ::env(TERM)] && $::env(TERM) != "" && $::env(TERM) != "dumb"} { return [exec tput setaf $color]$txt[exec tput setaf 9] } else { return $txt } } proc puts_err {txt} { set message "\[ERROR\]: $txt" puts "[color_text 1 "$message"]" if { [info exists ::env(RUN_DIR)] } { exec echo $message >> $::env(RUN_DIR)/openlane.log exec echo $message >> $::env(RUN_DIR)/errors.log } } proc puts_success {txt} { set message "\[SUCCESS\]: $txt" puts "[color_text 2 "$message"]" if { [info exists ::env(RUN_DIR)] } { exec echo $message >> $::env(RUN_DIR)/openlane.log } } proc puts_warn {txt} { set message "\[WARNING\]: $txt" puts "[color_text 3 "$message"]" if { [info exists ::env(RUN_DIR)] } { exec echo $message >> $::env(RUN_DIR)/openlane.log exec echo $message >> $::env(RUN_DIR)/warnings.log } } proc puts_info {txt} { set message "\[INFO\]: $txt" puts "[color_text 6 "$message"]" if { [info exists ::env(RUN_DIR)] } { exec echo $message >> $::env(RUN_DIR)/openlane.log } } proc puts_verbose {txt} { global global_verbose_level if { $global_verbose_level } { set message "\[INFO\]: $txt" puts "[color_text 6 "$message"]" if { [info exists ::env(RUN_DIR)] } { exec echo $message >> $::env(RUN_DIR)/openlane.log } } } proc show_warnings {msg} { if { [info exists ::env(RUN_DIR)] && [file exists $::env(RUN_DIR)/warnings.log] } { puts_info $msg set warnings_file [open $::env(RUN_DIR)/warnings.log "r"] set warnings [read $warnings_file] close $warnings_file puts "[color_text 3 "$warnings"]" } } proc generate_final_summary_report {args} { if { $::env(GENERATE_FINAL_SUMMARY_REPORT) == 0 } { return } puts_info "Generating final set of reports..." set options { {-output optional} {-man_report optional} } set flags {} parse_key_args "generate_final_summary_report" args arg_values $options flags_map $flags set_if_unset arg_values(-output) $::env(REPORTS_DIR)/metrics.csv set_if_unset arg_values(-man_report) $::env(REPORTS_DIR)/manufacturability.rpt catch_exec python3 $::env(OPENLANE_ROOT)/scripts/generate_reports.py \ -d $::env(DESIGN_DIR) \ --design_name $::env(DESIGN_NAME) \ --tag $::env(RUN_TAG) \ --output_file $arg_values(-output) \ --man_report $arg_values(-man_report) \ --run_path $::env(RUN_DIR) if { $exec_result(exit_code) } { puts_err "Failed to create manufacturability and metric reports:" puts_err "$exec_result(out)" } else { set man_report_rel [relpath . $arg_values(-man_report)] set metrics_report_rel [relpath . $arg_values(-output)] puts_info "Created manufacturability report at '$man_report_rel'." puts_info "Created metrics report at '$metrics_report_rel'." } } namespace eval TIMER { variable timer_start variable timer_end proc timer_start {} { variable timer_start set timer_start [clock milliseconds] } proc timer_stop {} { variable timer_end set timer_end [clock milliseconds] } proc get_runtime {} { variable timer_start variable timer_end set total_ms [expr {$timer_end - $timer_start }] set runtime_ms [expr { int($total_ms) % 1000 }] set runtime_s [expr { int(floor($total_ms) / 1000) % 60 }] set runtime_m [expr { int(floor($total_ms / (1000*60))) % 60 }] set runtime_h [expr { int(floor($total_ms / (1000*3600))) % 24 }] set runtime "${runtime_h}h${runtime_m}m${runtime_s}s${runtime_ms}ms" return $runtime } } proc assert_files_exist {files} { foreach f $files { if { ! [file exists $f] } { puts_err "$f doesn't exist." throw_error } else { puts_verbose "$f existence verified." } } } proc count_matches {pattern search_file} { set count [exec bash -c "grep $pattern $search_file | wc -l"] return $count } proc cat {args} { set res {} foreach file $args { set f [open $file r] set tmp [read $f] close $f append res $tmp } return $res } proc manipulate_layout {args} { # Requires at least one non-flag/option arg, which is the path to the script. set options { {-indexed_log optional} {-input optional} {-output optional} {-output_def optional} } set flags {} parse_key_args "manipulate_layout" args arg_values $options flag_map $flags set_if_unset arg_values(-indexed_log) /dev/null set_if_unset arg_values(-input) $::env(CURRENT_ODB) set_if_unset arg_values(-output) $arg_values(-input) set_if_unset arg_values(-output_def) /dev/null try_exec $::env(OPENROAD_BIN) -exit -no_init -python\ {*}$args \ --input-lef $::env(MERGED_LEF) \ --output-def $arg_values(-output_def) \ --output $arg_values(-output) \ $arg_values(-input) \ |& tee $::env(TERMINAL_OUTPUT) $arg_values(-indexed_log) } proc run_tcl_script {args} { # -tool: openroad/magic/yosys # -indexed_log: a log that is already pre-indexed # -save: # OpenROAD only. A list of commands to handle saving views. # The commands are comma delimited, and can either be in the format # command or command=value. Here is a list of commands: # * def/sdc/netlist/powered_netlist/sdf/spef/odb=: Saves a view to # the qualified path. # * def/sdc/netlist/powered_netlist/sdf/spef/odb: Saves a view to a # default path with a default name. # * to=: Replace the default save directory for unqualified views, # which is $::env(TMP_DIR) by default. Must be set before # any unqualified views. # * name=: Replace the default name for unqualified views, # which is $::env(DESIGN_NAME) by default. Must be set before # any unqualified views. # * index=: Turns running [index_file] for unqualified views on # or off. Must be set before any unqualified views. # * index: alias for index=1 # * noindex: alias for index=0 # set options { {-tool required} {-indexed_log optional} {-save optional} } # -netlist_in: Specify that the input is CURRENT_NETLIST and not the ODB file. # -def_in: Specify that the input is CURRENT_DEF and not the ODB file. # -gui: Launch the GUI (OpenROAD Only) # -no_update_current: See '-save' set flags {-def_in -netlist_in -gui -no_update_current} parse_key_args "run_tcl_script" args arg_values $options flag_map $flags set_if_unset arg_values(-indexed_log) /dev/null set_if_unset arg_values(-save) "" set create_reproducible 0 set script [lindex $args 0] set tool $arg_values(-tool) set save_list [list] set save_dir "$::env(TMP_DIR)" set metrics_path "" set index 1 set name $::env(DESIGN_NAME) if { [info exists flag_map(-def_in)] } { set ::env(IO_READ_DEF) 1 } set saved_values [split $arg_values(-save) ","] # C-style for loop because Tcl foreach cannot handle the list being # dynamically modified set layout_saved 0 set odb_saved 0 for {set i 0} {$i < [llength $saved_values]} {incr i} { set value [lindex $saved_values $i] set kv [split $value "="] set element [lindex $kv 0] set value [lindex $kv 1] if { $element == "to" } { set save_dir $value } elseif { $element == "name" } { set name $value } elseif { $element == "index" } { if { $value == "0" || $value == "1" } { set index $value } elseif { $value == "" } { set index "1" } else { puts_err "Invalid value $value for \"index\" command." throw_error } } elseif { $element == "noindex" } { set index 0 } elseif { $element != "" } { set extension $element if { $element == "netlist" } { set extension "nl.v" } elseif { $element == "powered_netlist" } { set extension "pnl.v" } elseif { $element == "metrics" } { set extension ".json" } elseif { $element == "odb" } { set odb_saved 1 } elseif { $element == "def" } { set layout_saved 1 } if { $value != "/dev/null" } { if { $value == "" } { set value "$save_dir/$name.$extension" if { $index } { set value [index_file $value] } } if { $element == "metrics" } { set metrics_path $value } else { lappend save_list $element $value } } } } if { $layout_saved && !$odb_saved } { puts_err "The layout was saved, but not the ODB format was not. This is a bug with OpenLane. Please file an issue." throw_error } if { $tool == "openroad" } { set args [list] lappend args $::env(OPENROAD_BIN) if { [info exists flag_map(-gui)] } { lappend args -gui } else { lappend args -exit } if { $metrics_path != "" } { lappend args -metrics $metrics_path } lappend args $script lappend args |& tee $::env(TERMINAL_OUTPUT) $arg_values(-indexed_log) foreach {element value} $save_list { set cap [string toupper $element] set ::env(SAVE_${cap}) $value } } elseif { $arg_values(-tool) == "magic" } { set args "magic -noconsole -dnull -rcfile $::env(MAGIC_MAGICRC) < $script |& tee $::env(TERMINAL_OUTPUT) $arg_values(-indexed_log)" } elseif { $arg_values(-tool) == "yosys" } { set args "$::env(SYNTH_BIN) -c $script |& tee $::env(TERMINAL_OUTPUT) $arg_values(-indexed_log)" } elseif { $arg_values(-tool) == "sta" } { set args "sta -exit -no_init $script |& tee $::env(TERMINAL_OUTPUT) $arg_values(-indexed_log)" foreach {element value} $save_list { set cap [string toupper $element] set ::env(SAVE_${cap}) $value } } else { puts_err "run_tcl_script only supports tools 'magic', 'yosys', 'sta', or 'openroad' for now." throw_error } if { ! [catch { set cmd_log_file [open $::env(RUN_DIR)/cmds.log a+] } ]} { set timestamp [clock format [clock seconds]] puts $cmd_log_file "$timestamp - Executing \"$args\"\n" close $cmd_log_file } set script_relative [relpath . $script] set exit_code 0 if { [info exists ::env(CREATE_REPRODUCIBLE_FROM_SCRIPT)] && [string match *$::env(CREATE_REPRODUCIBLE_FROM_SCRIPT) $script] } { puts_info "Script $script matches $::env(CREATE_REPRODUCIBLE_FROM_SCRIPT), creating reproducible..." set create_reproducible 1 } else { puts_verbose "Executing $tool with Tcl script '$script_relative'..." catch_exec {*}$args if { $exec_result(exit_code) } { set print_error_msg "during executing $tool script $script" set log_relpath [relpath $::env(PWD) $arg_values(-indexed_log)] puts_err "$print_error_msg" puts_err "Log: $log_relpath" puts_err "Last 10 lines:\n[exec tail -10 << $exec_result(out)]\n" set create_reproducible 1 puts_err "Creating issue reproducible..." } } if { [file exists $arg_values(-indexed_log)] \ && $arg_values(-indexed_log) ne "/dev/null" } { exec bash -c "grep -i warning $arg_values(-indexed_log) > \ [file rootname $arg_values(-indexed_log)].warnings || true" exec bash -c "grep -i error $arg_values(-indexed_log) > \ [file rootname $arg_values(-indexed_log)].errors || true" } if { $create_reproducible } { save_state set reproducible_dir $::env(RUN_DIR)/issue_reproducible set reproducible_dir_relative [relpath $::env(PWD) $reproducible_dir] set or_issue_arg_list [list] lappend or_issue_arg_list --tool $tool lappend or_issue_arg_list --output-dir $reproducible_dir lappend or_issue_arg_list --script $script lappend or_issue_arg_list --run-path $::env(RUN_DIR) if { $tool == "yosys" } { lappend or_issue_arg_list --input-type "n/a" "/dev/null" } elseif { [info exists flag_map(-netlist_in)] } { lappend or_issue_arg_list --input-type "netlist" $::env(CURRENT_NETLIST) } elseif { $tool != "openroad" || [info exists flag_map(-def_in)]} { lappend or_issue_arg_list --input-type "def" $::env(CURRENT_DEF) } elseif { $tool != "sta" } { lappend or_issue_arg_list --input-type "netlist" $::env(CURRENT_NETLIST) } else { lappend or_issue_arg_list --input-type "odb" $::env(CURRENT_ODB) } if {![catch {exec -ignorestderr python3 $::env(SCRIPTS_DIR)/or_issue.py {*}$or_issue_arg_list} result] == 0} { puts_err "Failed to package reproducible." throw_error } if { $exit_code } { puts_info "Reproducible packaged: Please tarball and upload '$reproducible_dir_relative' if you're going to submit an issue." throw_error } else { puts_info "Reproducible packaged at '$reproducible_dir_relative'." exit 0 } } if { [info exists flag_map(-def_in)] } { set ::env(IO_READ_DEF) 0 } if { ![info exist flag_map(-no_update_current)]} { foreach {element value} $save_list { set cap [string toupper $element] set save_env SAVE_$cap set current_env CURRENT_$cap if { $element == "def" } { set_def $::env(SAVE_DEF) } elseif { $element == "odb" } { set_odb $::env(SAVE_ODB) } elseif { $element == "sdc" } { set_sdc $::env(SAVE_SDC) } elseif { $element == "netlist" } { set_netlist -lec $::env(SAVE_NETLIST) } elseif { $element == "guide" } { set_guide $::env(SAVE_GUIDE) } else { set ::env(${current_env}) $::env(${save_env}) } unset ::env(${save_env}) } } } package provide openlane_utils 0.9