diff --git a/docs/user/FlowVariables.md b/docs/user/FlowVariables.md index c575653135..fcf1d6fab8 100644 --- a/docs/user/FlowVariables.md +++ b/docs/user/FlowVariables.md @@ -313,6 +313,8 @@ configuration file. | SYNTH_SKIP_KEEP| Only meaningful together with SYNTH_CHECKPOINT. When set, signals that the supplied checkpoint is still canonical RTLIL (coarse synth and `keep_hierarchy` have not been run yet), so synth.tcl runs the full coarse+fine synthesis flattened. When unset and SYNTH_CHECKPOINT is used, synth.tcl assumes the checkpoint already has coarse synth + `keep_hierarchy` done and resumes from `coarse:fine`.| 0| | SYNTH_SLANG_ARGS| Additional arguments passed to the slang frontend during synthesis.| | | SYNTH_USE_SYN| If set to 1, run synthesis using the "syn" tool built into OpenROAD (the synth_syn.tcl flow) instead of the default Yosys-based flow. Defaults to 0 (Yosys flow).| 0| +| SYNTH_VERILOG_SURGERY| RTL surgery as a step of the flow: a script that copies VERILOG_FILES and edits the copies before synthesis reads them, so vendored RTL stays byte-identical to upstream and the OpenROAD- and PDK-specific changes live in one script next to the design's config.mk. Typical surgery gives a memory or register file a module of its own that AUTO_MEMORIES can convert, maps a clock gate onto the platform's, rewrites a pragma for the frontend, or removes a simulation construct. A new upstream version is then a plain copy: the surgery reapplies, or stops where upstream moved. Best on generated or regular RTL (firtool, sv2v, netlists); a text edit cannot see the meaning of include files, macros, packages or `bind`. As the RTL is read (canonicalization, memory extraction, and the SYNTH_USE_SYN frontend), it runs with SYNTH_VERILOG_SURGERY_ARGS, then `--out-dir $(OBJECTS_DIR)/surgery --` and the VERILOG_FILES (by PYTHON_EXE when it ends in .py), and writes one copy per source under the same file name; synthesis reads the copies. Input file names must be unique. Diff a copy against its original to see exactly what the surgery changed. The script should fail when it does not recognise its input; a failing script, or a copy it did not write, stops synthesis.| | +| SYNTH_VERILOG_SURGERY_ARGS| Arguments to SYNTH_VERILOG_SURGERY, before the ones the flow adds.| | | SYNTH_WRAPPED_ADDERS| Specify the adder modules that can be used for synthesis, separated by commas. The default adder module is determined by the first element of this variable.| | | SYNTH_WRAPPED_MULTIPLIERS| Specify the multiplier modules that can be used for synthesis, separated by commas. The default multiplier module is determined by the first element of this variable.| | | SYNTH_WRAPPED_OPERATORS| Synthesize multiple architectural options for each arithmetic operator in the design. These options are available for switching among in later stages of the flow.| 0| @@ -379,6 +381,8 @@ configuration file. - [SYNTH_SKIP_KEEP](#SYNTH_SKIP_KEEP) - [SYNTH_SLANG_ARGS](#SYNTH_SLANG_ARGS) - [SYNTH_USE_SYN](#SYNTH_USE_SYN) +- [SYNTH_VERILOG_SURGERY](#SYNTH_VERILOG_SURGERY) +- [SYNTH_VERILOG_SURGERY_ARGS](#SYNTH_VERILOG_SURGERY_ARGS) - [SYNTH_WRAPPED_ADDERS](#SYNTH_WRAPPED_ADDERS) - [SYNTH_WRAPPED_MULTIPLIERS](#SYNTH_WRAPPED_MULTIPLIERS) - [TIEHI_CELL_AND_PORT](#TIEHI_CELL_AND_PORT) diff --git a/flow/scripts/synth_preamble.tcl b/flow/scripts/synth_preamble.tcl index 53353ee46a..b1eccb1c58 100644 --- a/flow/scripts/synth_preamble.tcl +++ b/flow/scripts/synth_preamble.tcl @@ -82,7 +82,7 @@ proc read_design_sources { } { {*}$vIdirsArgs {*}[env_var_or_empty VERILOG_DEFINES]] # slang requires all files at once - lappend slang_args {*}$::env(VERILOG_FILES) + lappend slang_args {*}[synth_verilog_files] # Add clock gate cell definition, if available if { [env_var_exists_and_non_empty CLKGATE_MAP_FILE] } { @@ -133,7 +133,7 @@ proc read_design_sources { } { if { [env_var_exists_and_non_empty VERILOG_DEFINES] } { verific -vlog-define {*}$::env(VERILOG_DEFINES) } - verific -sv2012 {*}$::env(VERILOG_FILES) + verific -sv2012 {*}[synth_verilog_files] verific -import -no-split-complex-ports $::env(DESIGN_NAME) dict for {key value} [env_var_or_empty VERILOG_TOP_PARAMS] { @@ -152,7 +152,7 @@ proc read_design_sources { } { if { [env_var_exists_and_non_empty VERILOG_DEFINES] } { verilog_defaults -add {*}$::env(VERILOG_DEFINES) } - foreach file $::env(VERILOG_FILES) { + foreach file [synth_verilog_files] { read_verilog -defer -sv {*}$vIdirsArgs $file } # Read platform specific mapfile for OPENROAD_CLKGATE cells diff --git a/flow/scripts/synth_syn.tcl b/flow/scripts/synth_syn.tcl index e00b6ff10b..52617bd5ae 100644 --- a/flow/scripts/synth_syn.tcl +++ b/flow/scripts/synth_syn.tcl @@ -30,7 +30,8 @@ set elaborate_args [list \ -D SYNTHESIS --compat=vcs --ignore-assertions --no-implicit-memories --top $::env(DESIGN_NAME) \ {*}$vIdirsArgs {*}[env_var_or_empty VERILOG_DEFINES]] -lappend elaborate_args {*}$::env(VERILOG_FILES) +set verilog_files [synth_verilog_files] +lappend elaborate_args {*}$verilog_files # Apply top-level parameters dict for {key value} [env_var_or_empty VERILOG_TOP_PARAMS] { @@ -50,7 +51,7 @@ lappend elaborate_args {*}$::env(SYNTH_SLANG_ARGS) # If the sources are solely .v files, enable Verilog compatibility set has_non_v_files false -foreach fn $::env(VERILOG_FILES) { +foreach fn $verilog_files { if { [file extension [string trim $fn]] != ".v" } { set has_non_v_files true } diff --git a/flow/scripts/util.tcl b/flow/scripts/util.tcl index 912762199a..0fa432acf4 100644 --- a/flow/scripts/util.tcl +++ b/flow/scripts/util.tcl @@ -336,3 +336,39 @@ proc source_step_tcl { hook_type step_name } { set env_var "${hook_type}_${step_name}_TCL" source_env_var_if_exists $env_var } + +# The Verilog files synthesis reads: VERILOG_FILES or, with +# SYNTH_VERILOG_SURGERY, the copies its script writes. The script runs +# here, as the RTL is read, into $(OBJECTS_DIR)/surgery: one copy per +# source under the same file name, kept so a diff against the original +# shows what the surgery changed. They are no make target and no build +# output. +proc synth_verilog_files { } { + if { ![env_var_exists_and_non_empty SYNTH_VERILOG_SURGERY] } { + return $::env(VERILOG_FILES) + } + set names [lmap f $::env(VERILOG_FILES) { file tail $f }] + if { [llength [lsort -unique $names]] != [llength $names] } { + error "SYNTH_VERILOG_SURGERY: VERILOG_FILES has two files of one name;\ + the copies are named after the originals" + } + set dir [file join $::env(OBJECTS_DIR) surgery] + file delete -force $dir + file mkdir $dir + set cmd {} + if { [file extension [lindex $::env(SYNTH_VERILOG_SURGERY) 0]] eq ".py" } { + lappend cmd $::env(PYTHON_EXE) + } + lappend cmd {*}$::env(SYNTH_VERILOG_SURGERY) \ + {*}[env_var_or_empty SYNTH_VERILOG_SURGERY_ARGS] \ + --out-dir $dir -- {*}$::env(VERILOG_FILES) + puts "SYNTH_VERILOG_SURGERY: [join $cmd]" + exec {*}$cmd >@ stdout 2>@ stderr + set copies [lmap name $names { file join $dir $name }] + foreach copy $copies { + if { ![file exists $copy] } { + error "SYNTH_VERILOG_SURGERY wrote no $copy" + } + } + return $copies +} diff --git a/flow/scripts/variables.json b/flow/scripts/variables.json index 36a165116c..af0d60e8c9 100644 --- a/flow/scripts/variables.json +++ b/flow/scripts/variables.json @@ -1407,6 +1407,18 @@ "synth" ] }, + "SYNTH_VERILOG_SURGERY": { + "description": "RTL surgery as a step of the flow: a script that copies VERILOG_FILES and edits the copies before synthesis reads them, so vendored RTL stays byte-identical to upstream and the OpenROAD- and PDK-specific changes live in one script next to the design's config.mk. Typical surgery gives a memory or register file a module of its own that AUTO_MEMORIES can convert, maps a clock gate onto the platform's, rewrites a pragma for the frontend, or removes a simulation construct. A new upstream version is then a plain copy: the surgery reapplies, or stops where upstream moved. Best on generated or regular RTL (firtool, sv2v, netlists); a text edit cannot see the meaning of include files, macros, packages or `bind`. As the RTL is read (canonicalization, memory extraction, and the SYNTH_USE_SYN frontend), it runs with SYNTH_VERILOG_SURGERY_ARGS, then `--out-dir $(OBJECTS_DIR)/surgery --` and the VERILOG_FILES (by PYTHON_EXE when it ends in .py), and writes one copy per source under the same file name; synthesis reads the copies. Input file names must be unique. Diff a copy against its original to see exactly what the surgery changed. The script should fail when it does not recognise its input; a failing script, or a copy it did not write, stops synthesis.\n", + "stages": [ + "synth" + ] + }, + "SYNTH_VERILOG_SURGERY_ARGS": { + "description": "Arguments to SYNTH_VERILOG_SURGERY, before the ones the flow adds.\n", + "stages": [ + "synth" + ] + }, "SYNTH_WRAPPED_ADDERS": { "description": "Specify the adder modules that can be used for synthesis, separated by commas. The default adder module is determined by the first element of this variable.\n", "stages": [ diff --git a/flow/scripts/variables.yaml b/flow/scripts/variables.yaml index 644427f34c..75f8c4ca42 100644 --- a/flow/scripts/variables.yaml +++ b/flow/scripts/variables.yaml @@ -211,6 +211,35 @@ ADDITIONAL_MEMORIES: docs/user/AutoMemories.md. stages: - synth +SYNTH_VERILOG_SURGERY: + description: > + RTL surgery as a step of the flow: a script that copies VERILOG_FILES + and edits the copies before synthesis reads them, so vendored RTL + stays byte-identical to upstream and the OpenROAD- and PDK-specific + changes live in one script next to the design's config.mk. Typical + surgery gives a memory or register file a module of its own that + AUTO_MEMORIES can convert, maps a clock gate onto the platform's, + rewrites a pragma for the frontend, or removes a simulation + construct. A new upstream version is then a plain copy: the surgery + reapplies, or stops where upstream moved. Best on generated or + regular RTL (firtool, sv2v, netlists); a text edit cannot see the + meaning of include files, macros, packages or `bind`. As the RTL is + read (canonicalization, memory extraction, and the SYNTH_USE_SYN + frontend), it runs with SYNTH_VERILOG_SURGERY_ARGS, then + `--out-dir $(OBJECTS_DIR)/surgery --` and the VERILOG_FILES (by + PYTHON_EXE when it ends in .py), and writes one copy per source under + the same file name; synthesis reads the copies. Input file names must + be unique. Diff a copy against its original to see exactly what the + surgery changed. The script should fail when it does not recognise + its input; a failing script, or a copy it did not write, stops + synthesis. + stages: + - synth +SYNTH_VERILOG_SURGERY_ARGS: + description: > + Arguments to SYNTH_VERILOG_SURGERY, before the ones the flow adds. + stages: + - synth SYNTH_MEMORY_MAX_BITS: description: > Maximum number of bits for memory synthesis.