From 3376e5f897363bed322463e85ad251e90c4057c7 Mon Sep 17 00:00:00 2001 From: Kareem Farid Date: Thu, 31 Aug 2023 09:24:48 +0000 Subject: [PATCH] Overhaul Linter Handling of Black-boxed Verilog Models (#1929) + add `generate_blackbox_verilog` that generates a black-boxed header file from a list of Verilog models + add `scripts/synth/blackbox.tcl` for `generate_blackbox_verilog` + use `generate_blackbox_verilog` with `VERILOG_FILES_BLACKBOX` and PDK verilog models + disable `UNDRIVEN` and `UNUSEDSIGNAL` for PDK verilog files + add `YOSYS_IN`, `YOSYS_OUT`, `YOSYS_DEFINES` which are used in blackbox.tcl to internal variables in ci variables documentation workflow ~ enable `LINTER_INCLUDE_PDK_MODELS` Co-authored-by: Donn --- .github/scripts/variables_documentation.py | 3 + configuration/checkers.tcl | 2 +- designs/ci | 2 +- scripts/clean_models.py | 49 +++++++++++++ scripts/tcl_commands/checkers.tcl | 4 +- scripts/tcl_commands/synthesis.tcl | 80 ++++++++++++++++------ scripts/utils/utils.tcl | 50 +++++++++----- scripts/yosys/blackbox.tcl | 24 +++++++ 8 files changed, 173 insertions(+), 41 deletions(-) create mode 100644 scripts/clean_models.py create mode 100644 scripts/yosys/blackbox.tcl diff --git a/.github/scripts/variables_documentation.py b/.github/scripts/variables_documentation.py index 171cbbbbe..a35ad6b11 100644 --- a/.github/scripts/variables_documentation.py +++ b/.github/scripts/variables_documentation.py @@ -102,6 +102,9 @@ VCHECK_OUTPUT VDD_NET WRITE_VIEWS_NO_GLOBAL_CONNECT +YOSYS_IN +YOSYS_OUT +YOSYS_DEFINES TECH_METAL_LAYERS LIB_SYNTH_COMPLETE LIB_SYNTH_COMPLETE_NO_PG diff --git a/configuration/checkers.tcl b/configuration/checkers.tcl index 0ddcf65be..c8e249ba6 100755 --- a/configuration/checkers.tcl +++ b/configuration/checkers.tcl @@ -18,7 +18,7 @@ set ::env(QUIT_ON_UNMAPPED_CELLS) 1 set ::env(QUIT_ON_SYNTH_CHECKS) 1 set ::env(SYNTH_CHECKS_ALLOW_TRISTATE) 1 set ::env(LINTER_RELATIVE_INCLUDES) 1 -set ::env(LINTER_INCLUDE_PDK_MODELS) 0 +set ::env(LINTER_INCLUDE_PDK_MODELS) 1 set ::env(QUIT_ON_LINTER_WARNINGS) 0 set ::env(QUIT_ON_LINTER_ERRORS) 1 diff --git a/designs/ci b/designs/ci index 0150e998e..f7dd19be4 160000 --- a/designs/ci +++ b/designs/ci @@ -1 +1 @@ -Subproject commit 0150e998ea2db42c5380e871206535a4aa8e9d5a +Subproject commit f7dd19be498e354cada37f4fb6f2437663517606 diff --git a/scripts/clean_models.py b/scripts/clean_models.py new file mode 100644 index 000000000..5053c99b4 --- /dev/null +++ b/scripts/clean_models.py @@ -0,0 +1,49 @@ +# Copyright 2023 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. +import re +import click +from enum import Enum + + +@click.command() +@click.option("-o", "--output", default=None) +@click.argument( + "file_in", + type=click.Path(exists=True, file_okay=True, dir_okay=False), + required=True, +) +def clean_models(output, file_in): + class State(Enum): + output = 0 + primitive = 1 + + out_file = None + if output is not None: + out_file = open(output, "w", encoding="utf8") + bad_yosys_line = re.compile(r"^\s+\S+\s*\(.*\).*;") + state = State.output + for line in open(file_in, "r", encoding="utf8"): + if state == State.output: + if line.strip().startswith("primitive"): + state = State.primitive + elif bad_yosys_line.search(line) is None: + print(line.strip("\n"), file=out_file) + elif state == State.primitive: + if line.strip().startswith("endprimitive"): + print("/* removed primitive */", file=out_file) + state = State.output + + +if __name__ == "__main__": + clean_models() diff --git a/scripts/tcl_commands/checkers.tcl b/scripts/tcl_commands/checkers.tcl index 366501584..897bd4501 100755 --- a/scripts/tcl_commands/checkers.tcl +++ b/scripts/tcl_commands/checkers.tcl @@ -49,7 +49,7 @@ proc check_out_of_bound {log} { $log || true"] if { $checker ne "" } { - puts_err "Synthesis failed. Range select out of bounds on some signals. Search for '$match' in $log" + puts_err "Synthesis failed. Range select out of bounds on some signals. Search for '$match' in '[relpath . $log]'" throw_error } } @@ -60,7 +60,7 @@ proc check_resizing_cell_port {log} { $log || true"] if { $checker ne "" } { - puts_err "Synthesis failed. Signal not matching port size. Search for '$match' in $log" + puts_err "Synthesis failed. Signal not matching port size. Search for '$match' in '[relpath . $log]'" throw_error } } diff --git a/scripts/tcl_commands/synthesis.tcl b/scripts/tcl_commands/synthesis.tcl index 8f5f34b98..257e4b6d7 100755 --- a/scripts/tcl_commands/synthesis.tcl +++ b/scripts/tcl_commands/synthesis.tcl @@ -243,30 +243,71 @@ proc logic_equiv_check {args} { exec echo "[TIMER::get_runtime]" | python3 $::env(SCRIPTS_DIR)/write_runtime.py "logic equivalence check - yosys" } +proc generate_blackbox_verilog {inputs output {defines ""}} { + set defines_flag "" + set ::env(YOSYS_IN) $inputs + set ::env(YOSYS_OUT) $output + if { $defines != "" } { + set ::env(YOSYS_DEFINES) $defines + } + try_exec yosys -c $::env(SCRIPTS_DIR)/yosys/blackbox.tcl + + set out_str [cat $output] + set f [open $output w] + puts $f "/* verilator lint_off UNDRIVEN */\n/* verilator lint_off UNUSEDSIGNAL */\n$out_str\n/* verilator lint_on UNUSEDSIGNAL */\n/* verilator lint_on UNDRIVEN */\n" + close $f + + set inputs_rel [list] + foreach input $inputs { + lappend inputs_rel [relpath . $input] + } + + puts_verbose "Generated black-box model ([relpath . $output]) from ($inputs_rel)." +} + proc run_verilator {} { - set verilator_verified_pdks "sky130A sky130B" - set verilator_verified_scl "sky130_fd_sc_hd" - set includes "" - if { [string match *$::env(PDK)* $verilator_verified_pdks] == 0 || \ - [string match *$::env(STD_CELL_LIBRARY)* $verilator_verified_scl] == 0} { - puts_warn "PDK '$::env(PDK)', SCL '$::env(STD_CELL_LIBRARY)' will generate errors with instantiated stdcells in the design." - puts_warn "Either disable QUIT_ON_LINTER_ERRORS or remove the instantiated cells." + set bb_dir $::env(synthesis_tmpfiles)/blackbox + file mkdir $bb_dir + + set pdk_model_blackbox [list] + set included_blackbox_models [glob -nocomplain "$::env(PDK_ROOT)/$::env(PDK)/libs.ref/$::env(STD_CELL_LIBRARY)/verilog/*__blackbox.v"] + if { [llength $included_blackbox_models]} { + foreach model $included_blackbox_models { + set output_file "$bb_dir/[file rootname [file tail $model]].v" + generate_blackbox_verilog $model $output_file + lappend pdk_model_blackbox $output_file + } } else { - set pdk_verilog_models [glob $::env(PDK_ROOT)/$::env(PDK)/libs.ref/$::env(STD_CELL_LIBRARY_OPT)/verilog/*.v] - foreach model $pdk_verilog_models { - set includes "$includes $model" + # No black-box model in PDK: gotta try our best here + set pdk_models [glob -nocomplain "$::env(PDK_ROOT)/$::env(PDK)/libs.ref/$::env(STD_CELL_LIBRARY)/verilog/*.v"] + foreach model $pdk_models { + set output_file "$bb_dir/[file rootname [file tail $model]].v" + + set patched_file "$bb_dir/[file rootname [file tail $model]].patched.v" + try_exec python3 $::env(SCRIPTS_DIR)/clean_models.py\ + --output $patched_file\ + $model + + generate_blackbox_verilog $patched_file $output_file FUNCTIONAL + lappend pdk_model_blackbox $output_file } } set log $::env(synthesis_logs)/linter.log - puts_info "Running linter (Verilator) (log: [relpath . $log])..." set arg_list [list] if { $::env(LINTER_INCLUDE_PDK_MODELS) } { - lappend arg_list {*}$includes + lappend arg_list {*}$pdk_model_blackbox } - lappend arg_list {*}$::env(VERILOG_FILES) if { [info exists ::env(VERILOG_FILES_BLACKBOX)] } { - lappend arg_list {*}$::env(VERILOG_FILES_BLACKBOX) + set output_file "$bb_dir/extra.v" + if { [info exists ::env(LINTER_DEFINES)] } { + generate_blackbox_verilog $::env(VERILOG_FILES_BLACKBOX) $output_file "$::env(LINTER_DEFINES)" + } else { + generate_blackbox_verilog $::env(VERILOG_FILES_BLACKBOX) $output_file + } + + lappend arg_list {*}$output_file } + lappend arg_list {*}$::env(VERILOG_FILES) lappend arg_list -Wno-fatal if { $::env(LINTER_RELATIVE_INCLUDES) } { lappend arg_list "--relative-includes" @@ -284,18 +325,17 @@ proc run_verilator {} { } lappend arg_list {*}$defines - set arg "|& tee $log $::env(TERMINAL_OUTPUT)" - lappend arg_list {*}$arg - try_exec bash -c "verilator \ - --lint-only \ + puts_info "Running linter (Verilator) (log: [relpath . $log])..." + catch_exec verilator \ -Wall \ + --lint-only \ --Wno-DECLFILENAME \ --top-module $::env(DESIGN_NAME) \ - $arg_list" + {*}$arg_list |& tee $log $::env(TERMINAL_OUTPUT) set timing_errors [exec bash -c "grep -i 'Error-NEEDTIMINGOPT' $log || true"] if { $timing_errors ne "" } { - set msg "Timing constructs found in the RTL. Please remove them or wrap them around an ifdef. It heavily unrecommended to rely on timing constructs for synthesis." + set msg "Timing constructs found in the RTL. Please remove them or add a preprocessor guard. It is heavily discouraged to rely on timing constructs in synthesis." if { $::env(QUIT_ON_LINTER_ERRORS) } { puts_err $msg throw_error diff --git a/scripts/utils/utils.tcl b/scripts/utils/utils.tcl index 5ec7d7d7c..8e4e0dc54 100755 --- a/scripts/utils/utils.tcl +++ b/scripts/utils/utils.tcl @@ -197,13 +197,28 @@ proc set_and_log {var val} { close $global_cfg_file } -proc try_exec {args} { - # puts_info "Executing \"$args\"\n" +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]] @@ -306,9 +321,10 @@ proc calc_total_runtime {args} { set_if_unset arg_values(-report) $::env(REPORTS_DIR)/total_runtime.txt set_if_unset arg_values(-status) "flow completed" - if {[catch {exec python3 $::env(SCRIPTS_DIR)/write_runtime.py --conclude --seconds --time-in $::env(timer_end) $arg_values(-status)} err]} { + 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 "$err" + puts_err "$exec_result(out)" } } } @@ -402,16 +418,17 @@ proc generate_final_summary_report {args} { set_if_unset arg_values(-output) $::env(REPORTS_DIR)/metrics.csv set_if_unset arg_values(-man_report) $::env(REPORTS_DIR)/manufacturability.rpt - if { - [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)} err] - } { + 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 "$err" + puts_err "$exec_result(out)" } else { set man_report_rel [relpath . $arg_values(-man_report)] @@ -667,15 +684,14 @@ proc run_tcl_script {args} { } else { puts_verbose "Executing $tool with Tcl script '$script_relative'..." - set exit_code [catch {exec {*}$args} error_msg] - - if { $exit_code } { + 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 << $error_msg]\n" + puts_err "Last 10 lines:\n[exec tail -10 << $exec_result(out)]\n" set create_reproducible 1 puts_err "Creating issue reproducible..." diff --git a/scripts/yosys/blackbox.tcl b/scripts/yosys/blackbox.tcl new file mode 100644 index 000000000..7464d19f8 --- /dev/null +++ b/scripts/yosys/blackbox.tcl @@ -0,0 +1,24 @@ +# Copyright 2023 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. + +yosys -import +foreach input $::env(YOSYS_IN) { + if { [info exists ::env(YOSYS_DEFINES)] } { + read_verilog -lib -D$::env(YOSYS_DEFINES) $input + } else { + read_verilog -lib $input + } +} +blackbox * +write_verilog -noattr -noexpr -nohex -nodec -defparam -blackboxes $::env(YOSYS_OUT)